kopia lustrzana https://github.com/Yakifo/amqtt
updating ldap custom schema to include three ACL attributes, retrieve the correct topic list and check if topic is allowed
rodzic
8c6b6da9ad
commit
ae9ecd074b
|
@ -6,6 +6,7 @@ import ldap
|
|||
from amqtt.broker import BrokerContext
|
||||
from amqtt.contexts import Action
|
||||
from amqtt.errors import PluginInitError
|
||||
from amqtt.plugins import TopicMatcher
|
||||
from amqtt.plugins.base import BaseAuthPlugin, BaseTopicPlugin
|
||||
from amqtt.session import Session
|
||||
|
||||
|
@ -72,6 +73,12 @@ class LDAPAuthPlugin(BaseAuthPlugin):
|
|||
class LDAPTopicPlugin(BaseTopicPlugin):
|
||||
"""Plugin to authenticate a user with an LDAP directory server."""
|
||||
|
||||
_action_attr_map = {
|
||||
Action.PUBLISH: 'publish_attribute',
|
||||
Action.SUBSCRIBE: 'subscribe_attribute',
|
||||
Action.RECEIVE: 'receive_attribute'
|
||||
}
|
||||
|
||||
def __init__(self, context: BrokerContext) -> None:
|
||||
super().__init__(context)
|
||||
|
||||
|
@ -82,13 +89,20 @@ class LDAPTopicPlugin(BaseTopicPlugin):
|
|||
except ldap.INVALID_CREDENTIALS as e: # pylint: disable=E1101
|
||||
raise PluginInitError(self.__class__) from e
|
||||
|
||||
self.topic_matcher = TopicMatcher()
|
||||
|
||||
|
||||
async def topic_filtering(
|
||||
self, *, session: Session | None = None, topic: str | None = None, action: Action | None = None
|
||||
) -> bool | None:
|
||||
# search_filter = f"({self.config.user_attribute}={session.username})"
|
||||
search_filter = "(uid=jdoe)"
|
||||
attrs = ["cn", "userType", "roleName", "isActive"]
|
||||
attrs = [
|
||||
"cn",
|
||||
self.config.publish_attribute,
|
||||
self.config.subscribe_attribute,
|
||||
self.config.receive_attribute
|
||||
]
|
||||
results = self.conn.search_s(self.config.base_dn, ldap.SCOPE_SUBTREE, search_filter, attrs)
|
||||
|
||||
|
||||
|
@ -96,13 +110,22 @@ class LDAPTopicPlugin(BaseTopicPlugin):
|
|||
logger.debug(f"user not found: {session.username}")
|
||||
return False
|
||||
|
||||
for dn, entry in results:
|
||||
print("DN:", dn)
|
||||
print("publishACL:", entry.get("userType", []))
|
||||
print("subscribeACL:", entry.get("roleName", []))
|
||||
print("receiveACL:", entry.get("isActive", []))
|
||||
if len(results) > 1:
|
||||
found_users = [dn for dn, _ in results]
|
||||
logger.debug(f"multiple users found: {', '.join(found_users)}")
|
||||
return False
|
||||
|
||||
dn, entry = results[0]
|
||||
|
||||
ldap_attribute = getattr(self.config, self._action_attr_map[action])
|
||||
allowed_topics = [t.decode("utf-8") for t in entry.get(ldap_attribute, [])]
|
||||
logger.debug(f"DN: {dn} - {ldap_attribute}={allowed_topics}")
|
||||
|
||||
return self.topic_matcher.are_topics_allowed(topic, allowed_topics)
|
||||
# print(f"{self.config.publish_attribute} : ", entry.get(self.config.publish_attribute, []))
|
||||
# print(f"{self.config.subscribe_attribute} : ", entry.get(self.config.subscribe_attribute, []))
|
||||
# print(f"{self.config.receive_attribute} : ", entry.get(self.config.receive_attribute, []))
|
||||
|
||||
return None
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
|
|
|
@ -32,3 +32,7 @@ class TopicMatcher:
|
|||
.lstrip("?"))
|
||||
match_pattern = self._topic_filter_matchers[a_filter]
|
||||
return bool(match_pattern.fullmatch(topic))
|
||||
|
||||
def are_topics_allowed(self, topic: str, many_filters: list[str]) -> bool:
|
||||
|
||||
return any([self.is_topic_allowed(topic, a_filter) for a_filter in many_filters])
|
||||
|
|
|
@ -121,9 +121,9 @@ async def test_topic_ldap_plugin():
|
|||
ctx.config = LDAPTopicPlugin.Config(
|
||||
server="ldap://localhost:1389",
|
||||
# server=ldap_service,
|
||||
base_dn="dc=example,dc=org",
|
||||
base_dn="dc=amqtt,dc=io",
|
||||
user_attribute="uid",
|
||||
bind_dn="cn=admin,dc=example,dc=org",
|
||||
bind_dn="cn=admin,dc=amqtt,dc=io",
|
||||
bind_password="adminpassword",
|
||||
publish_attribute="publishACL",
|
||||
subscribe_attribute="subscribeACL",
|
||||
|
@ -135,4 +135,4 @@ async def test_topic_ldap_plugin():
|
|||
s.username = "testuser"
|
||||
s.password = "testpassword"
|
||||
|
||||
assert await ldap_plugin.topic_filtering(session=s, topic='my/topic', action=Action.PUBLISH), "access not granted"
|
||||
assert await ldap_plugin.topic_filtering(session=s, topic='my/topic/one', action=Action.PUBLISH), "access not granted"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
attributetype ( 1.3.6.1.4.1.4203.666.1.1 NAME 'userType' DESC 'Type of user' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
|
||||
attributetype ( 1.3.6.1.4.1.4203.666.1.1 NAME 'publishACL' DESC 'topics for publishing' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.4203.666.1.2 NAME 'roleName' DESC 'Role of the user' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
|
||||
attributetype ( 1.3.6.1.4.1.4203.666.1.2 NAME 'subscribeACL' DESC 'topics for subscribing' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.4203.666.1.3 NAME 'isActive' DESC 'Account status' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
|
||||
attributetype ( 1.3.6.1.4.1.4203.666.1.3 NAME 'receiveACL' DESC 'topics for receiving' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.4203.666.2.1 NAME 'customuserinfo' DESC 'User info with custom attributes' SUP inetOrgPerson STRUCTURAL MUST ( cn $ sn $ isActive ) MAY ( userType $ roleName ) )
|
||||
objectclass ( 1.3.6.1.4.1.4203.666.2.1 NAME 'customuserinfo' DESC 'User info with custom attributes' SUP inetOrgPerson STRUCTURAL MUST ( cn $ sn ) MAY ( publishACL $ subscribeACL $ receiveACL ) )
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
dn: ou=users,dc=example,dc=org
|
||||
dn: ou=users,dc=amqtt,dc=io
|
||||
objectClass: organizationalUnit
|
||||
ou: users
|
||||
description: Organizational Unit for storing user entries
|
||||
|
||||
dn: uid=jdoe,ou=users,dc=example,dc=org
|
||||
dn: uid=jdoe,ou=users,dc=amqtt,dc=io
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: customuserinfo
|
||||
cn: John Doe
|
||||
sn: Doe
|
||||
uid: jdoe
|
||||
mail: jdoe@example.org
|
||||
mail: jdoe@amqtt.io
|
||||
userPassword: {SSHA}w0RfYQaFGMQq7c5QnW7xvXb+iXG0P5gB
|
||||
userType: Admin
|
||||
roleName: Manager
|
||||
isActive: TRUE
|
||||
publishACL: my/topic/one
|
||||
subscribeACL: my/topic/two
|
||||
receiveACL: my/topic/three
|
||||
|
|
|
@ -15,7 +15,7 @@ services:
|
|||
- "1636:636"
|
||||
environment:
|
||||
- LDAP_ADMIN_PASSWORD=adminpassword
|
||||
- LDAP_DOMAIN=example.org
|
||||
- LDAP_DOMAIN=amqtt.io
|
||||
|
||||
volumes:
|
||||
ldap-data:
|
||||
|
|
Ładowanie…
Reference in New Issue