diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index 80a6ee11e707fb3776fc96b42a16b649ac575f66..5913ad72af20f3c040ab168082dc6b87e2769c1a 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -269,14 +269,72 @@ class Entity(object): self.__pickup = new_pickup def grant(self, realm=None, username=None, role=None, - permission=None, priority=False): + permission=None, priority=False, revoke_denial=True): + """Grant a permission to a user or role for this entity. + + You must specify either only the username and the realm, or only the + role. + + By default a previously existing denial rule would be revoked, because + otherwise this grant wouldn't have any effect. However, for keeping + contradicting rules pass revoke_denial=False. + + Parameters + ---------- + permission: str + The permission to be granted. + username : str, optional + The username. Exactly one is required, either the `username` or the + `role`. + realm: str, optional + The user's realm. Required when username is not None. + role: str, optional + The role (as in Role-Based Access Control). Exactly one is + required, either the `username` or the `role`. + priority: bool, default False + Whether this permission is granted with priority over non-priority + rules. + revoke_denial: bool, default True + Whether a contradicting denial (with same priority flag) in this + ACL will be revoked. + """ self.acl.grant(realm=realm, username=username, role=role, - permission=permission, priority=priority) + permission=permission, priority=priority, + revoke_denial=revoke_denial) def deny(self, realm=None, username=None, role=None, - permission=None, priority=False): + permission=None, priority=False, revoke_grant=True): + """Deny a permission to a user or role for this entity. + + You must specify either only the username and the realm, or only the + role. + + By default a previously existing grant rule would be revoked, because + otherwise this denial would override the grant rules anyways. However, + for keeping contradicting rules pass revoke_grant=False. + + Parameters + ---------- + permission: str + The permission to be denied. + username : str, optional + The username. Exactly one is required, either the `username` or the + `role`. + realm: str, optional + The user's realm. Required when username is not None. + role: str, optional + The role (as in Role-Based Access Control). Exactly one is + required, either the `username` or the `role`. + priority: bool, default False + Whether this permission is denied with priority over non-priority + rules. + revoke_grant: bool, default True + Whether a contradicting grant (with same priority flag) in this + ACL will be revoked. + """ self.acl.deny(realm=realm, username=username, role=role, - permission=permission, priority=priority) + permission=permission, priority=priority, + revoke_grant=revoke_grant) def revoke_denial(self, realm=None, username=None, role=None, permission=None, priority=False): @@ -3636,13 +3694,15 @@ class ACI(): self.permission = permission def __hash__(self): - return hash(str(self.realm) + ":" + str(self.username) + - ":" + str(self.role) + ":" + str(self.permission)) + return hash(self.__repr__()) def __eq__(self, other): return isinstance(other, ACI) and (self.role is None and self.username == other.username and self.realm == other.realm) or self.role == other.role and self.permission == other.permission + def __repr__(self): + return str(self.realm) + ":" + str(self.username) + ":" + str(self.role) + ":" + str(self.permission) + def add_to_element(self, e): if self.role is not None: e.set("role", self.role) @@ -3667,10 +3727,34 @@ class ACL(): self.clear() def parse_xml(self, xml): + """Clear this ACL and parse the xml. + + Iterate over the rules in the xml and add each rule to this ACL. + + Contradicting rules will both be kept. + + Parameters + ---------- + xml : lxml.etree.Element + The xml element containing the ACL rules, i.e. <Grant> and <Deny> + rules. + """ self.clear() self._parse_xml(xml) def _parse_xml(self, xml): + """Parse the xml. + + Iterate over the rules in the xml and add each rule to this ACL. + + Contradicting rules will both be kept. + + Parameters + ---------- + xml : lxml.etree.Element + The xml element containing the ACL rules, i.e. <Grant> and <Deny> + rules. + """ for e in xml: role = e.get("role") username = e.get("username") @@ -3683,10 +3767,12 @@ class ACL(): if e.tag == "Grant": self.grant(username=username, realm=realm, role=role, - permission=permission, priority=priority) + permission=permission, priority=priority, + revoke_denial=False) elif e.tag == "Deny": self.deny(username=username, realm=realm, role=role, - permission=permission, priority=priority) + permission=permission, priority=priority, + revoke_grant=False) def combine(self, other): """ Combine and return new instance.""" @@ -3764,12 +3850,41 @@ class ACL(): if item in self._denials: self._denials.remove(item) - def grant(self, username=None, realm=None, role=None, - permission=None, priority=False): + def grant(self, permission, username=None, realm=None, role=None, + priority=False, revoke_denial=True): + """Grant a permission to a user or role. + + You must specify either only the username and the realm, or only the + role. + + By default a previously existing denial rule would be revoked, because + otherwise this grant wouldn't have any effect. However, for keeping + contradicting rules pass revoke_denial=False. + + Parameters + ---------- + permission: str + The permission to be granted. + username : str, optional + The username. Exactly one is required, either the `username` or the + `role`. + realm: str, optional + The user's realm. Required when username is not None. + role: str, optional + The role (as in Role-Based Access Control). Exactly one is + required, either the `username` or the `role`. + priority: bool, default False + Whether this permission is granted with priority over non-priority + rules. + revoke_denial: bool, default True + Whether a contradicting denial (with same priority flag) in this + ACL will be revoked. + """ priority = self._get_boolean_priority(priority) item = ACI(role=role, username=username, realm=realm, permission=permission) - self._remove_item(item, priority) + if revoke_denial: + self._remove_item(item, priority) if priority is True: self._priority_grants.add(item) @@ -3777,11 +3892,40 @@ class ACL(): self._grants.add(item) def deny(self, username=None, realm=None, role=None, - permission=None, priority=False): + permission=None, priority=False, revoke_grant=True): + """Deny a permission to a user or role for this entity. + + You must specify either only the username and the realm, or only the + role. + + By default a previously existing grant rule would be revoked, because + otherwise this denial would override the grant rules anyways. However, + for keeping contradicting rules pass revoke_grant=False. + + Parameters + ---------- + permission: str + The permission to be denied. + username : str, optional + The username. Exactly one is required, either the `username` or the + `role`. + realm: str, optional + The user's realm. Required when username is not None. + role: str, optional + The role (as in Role-Based Access Control). Exactly one is + required, either the `username` or the `role`. + priority: bool, default False + Whether this permission is denied with priority over non-priority + rules. + revoke_grant: bool, default True + Whether a contradicting grant (with same priority flag) in this + ACL will be revoked. + """ priority = self._get_boolean_priority(priority) item = ACI(role=role, username=username, realm=realm, permission=permission) - self._remove_item(item, priority) + if revoke_grant: + self._remove_item(item, priority) if priority is True: self._priority_denials.add(item)