diff --git a/halibot/halauth.py b/halibot/halauth.py index 00cbf8f..2d5883e 100644 --- a/halibot/halauth.py +++ b/halibot/halauth.py @@ -1,11 +1,21 @@ import json import logging +from halibot.message import Message def hasPermission(perm, reply=False): def real_dec(func): - def wrapper(self, msg, *args, **kwargs): + def wrapper(self, *args, **kwargs): + msg = None + for i in list(args) + list(kwargs.values()): + if i.__class__ == Message: + msg = i + break + else: + self.log.error("Probable module bug! -- hasPermission decorator called on a function that doesn't have a Message argument!") + return + if self._hal.auth.hasPermission(msg.origin, msg.identity, perm): - func(self, msg, *args, **kwargs) + func(self, *args, **kwargs) elif reply: self.reply(msg, body="Permission Denied") return wrapper @@ -47,20 +57,24 @@ def write_perms(self): def grantPermission(self, ri, identity, perm): if not self.enabled: - return + return False t = (ri, identity, perm) if t not in self.perms: self.perms.append(t) + return True + return False def revokePermission(self, ri, identity, perm): if not self.enabled: - return + return False try: self.perms.remove((ri,identity, perm)) + return True except Exception as e: self.log.error("Revocation failed: {}".format(e)) + return False def hasPermission(self, ri, identity, perm): if not self.enabled: diff --git a/packages/core/__init__.py b/packages/core/__init__.py index 9b350ed..af88ac3 100644 --- a/packages/core/__init__.py +++ b/packages/core/__init__.py @@ -1,2 +1,3 @@ from .help import Help +from .perm import PermissionManager diff --git a/packages/core/perm.py b/packages/core/perm.py new file mode 100644 index 0000000..90448aa --- /dev/null +++ b/packages/core/perm.py @@ -0,0 +1,31 @@ +from halibot import CommandModule +from halibot.halauth import hasPermission + +class PermissionManager(CommandModule): + def init(self): + self.commands = { + "grant": self.grant_, + "revoke": self.revoke_, + } + + @hasPermission("PERM_GRANT", reply=True) + def grant_(self, argv, msg=None): + try: + ri, identity, perm = argv.split(" ") + except: + self.reply(msg, body="Must be in the form ' '") + return + + if self._hal.auth.grantPermission(ri, identity, perm): + self._hal.auth.write_perms() + + @hasPermission("PERM_REVOKE", reply=True) + def revoke_(self, argv, msg=None): + try: + ri, identity, perm = argv.split(" ") + except: + self.reply(msg, body="Must be in the form ' '") + return + + if self._hal.auth.revokePermission(ri, identity, perm): + self._hal.auth.write_perms() diff --git a/tests/test_auth.py b/tests/test_auth.py index 4d194b6..ca2311e 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -30,38 +30,56 @@ def function(self, msg): def receive(self, msg): self.function(msg) +# This is needed to test the catch-all in the Decorator +# if the module developer didn't include a Message object +class StubModuleDecFail(halibot.HalModule): + + def init(self): + self.called = False + + @halibot.halauth.hasPermission("Foo", reply=True) + def function(self): + self.called = True + + def receive(self, msg): + self.function() + class TestAuth(util.HalibotTestCase): def test_grantperm(self): self.bot.auth.perms = [] self.bot.auth.enabled = False - self.bot.auth.grantPermission("foo", "bar", "baz") + self.assertFalse(self.bot.auth.grantPermission("foo", "bar", "baz")) self.assertEqual(len(self.bot.auth.perms), 0) self.bot.auth.enabled = True - self.bot.auth.grantPermission("foo", "bar", "baz") + self.assertTrue(self.bot.auth.grantPermission("foo", "bar", "baz")) self.assertEqual(len(self.bot.auth.perms), 1) self.assertEqual(self.bot.auth.perms[0][0], "foo") self.assertEqual(self.bot.auth.perms[0][1], "bar") self.assertEqual(self.bot.auth.perms[0][2], "baz") + # Ensure we do not grant the same permission multiple times + self.assertFalse(self.bot.auth.grantPermission("foo", "bar", "baz")) + self.assertEqual(len(self.bot.auth.perms), 1) + def test_revokeperm(self): self.bot.auth.perms = [("foo", "bar", "baz")] self.bot.auth.enabled = False - self.bot.auth.revokePermission("foo", "bar", "baz") + self.assertFalse(self.bot.auth.revokePermission("foo", "bar", "baz")) # Permissions aren't enabled, so we should ignore revocations self.assertEqual(len(self.bot.auth.perms), 1) self.bot.auth.enabled = True - self.bot.auth.revokePermission("foo", "bar", "baz") + self.assertTrue(self.bot.auth.revokePermission("foo", "bar", "baz")) self.assertEqual(len(self.bot.auth.perms), 0) # This should remain empty, and fail to find the perm to revoke - self.bot.auth.revokePermission("foo", "bar", "baz") + self.assertFalse(self.bot.auth.revokePermission("foo", "bar", "baz")) self.assertEqual(len(self.bot.auth.perms), 0) def test_hasperm_func(self): @@ -116,6 +134,28 @@ def test_hasperm_dec(self): stub.receive(msg) self.assertFalse(stub.called) + def test_hasperm_dec_fail(self): + self.bot.auth.enabled = True + stub = StubModuleDecFail(self.bot) + self.bot.add_instance('stub_mod', stub) + + ri = "test/foobar" + user = "tester" + perm = "Foo" + msg = halibot.Message(body="", origin=ri, identity=user) + + stub.receive(msg) + self.assertFalse(stub.called) + + self.bot.auth.grantPermission(ri, user, perm) + stub.receive(msg) + self.assertFalse(stub.called) + + stub.called = False + self.bot.auth.revokePermission(ri, user, perm) + stub.receive(msg) + self.assertFalse(stub.called) + def test_load_perms(self): with open("testperms.json", "w") as f: f.write("[]")