Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2012-10-01 18:05:35 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-10-01 18:05:35 (GMT)
commitad06135abee6b60aedc0c0799e5f02d00fed1fa2 (patch)
tree3e5c52ff52342560b82fa37aa8173a4702f05405
parenta0d71feffd4e5fec671ff8f9dede4973dc998854 (diff)
Initial implemenation for auth roles
-rw-r--r--sugar_network/node/auth.py47
-rw-r--r--sugar_network/node/commands.py5
-rw-r--r--sugar_network/resources/context.py30
-rw-r--r--sugar_network/resources/volume.py1
-rw-r--r--sugar_network/toolkit/router.py1
-rw-r--r--tests/__init__.py13
-rw-r--r--tests/units/__main__.py1
-rwxr-xr-xtests/units/auth.py70
8 files changed, 148 insertions, 20 deletions
diff --git a/sugar_network/node/auth.py b/sugar_network/node/auth.py
new file mode 100644
index 0000000..fdb7975
--- /dev/null
+++ b/sugar_network/node/auth.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2012 Aleksey Lim
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ConfigParser import ConfigParser
+from os.path import join, exists
+
+import active_document as ad
+from sugar_network import node
+from active_toolkit import enforce
+
+
+_config = None
+
+
+def validate(request, role):
+ enforce(_validate(request.principal, role), ad.Forbidden,
+ 'No enough permissions to proceed the operation')
+
+
+def try_validate(request, role):
+ return _validate(request.principal, role) or False
+
+
+def _validate(user, role):
+ global _config
+
+ if _config is None:
+ _config = ConfigParser()
+ config_path = join(node.data_root.value, 'authorization.conf')
+ if exists(config_path):
+ _config.read(config_path)
+
+ if _config.has_option(user, role):
+ return _config.get(user, role).strip().lower() in \
+ ('true', 'on', '1', 'allow')
diff --git a/sugar_network/node/commands.py b/sugar_network/node/commands.py
index ba5b6ed..1df1a13 100644
--- a/sugar_network/node/commands.py
+++ b/sugar_network/node/commands.py
@@ -21,6 +21,7 @@ from os.path import exists, join
import active_document as ad
from sugar_network import node
from sugar_network.node.sync_master import SyncCommands
+from sugar_network.node import auth
from sugar_network.resources.volume import Commands
from sugar_network.toolkit import router
from active_toolkit import util, enforce
@@ -95,7 +96,8 @@ class NodeCommands(ad.VolumeCommands, Commands):
if cmd.permissions & ad.ACCESS_AUTHOR and 'guid' in request:
doc = self.volume[request['document']].get(request['guid'])
- enforce(request.principal in doc['user'], ad.Forbidden,
+ enforce(request.principal in doc['user'] or
+ auth.try_validate(request, 'root'), ad.Forbidden,
'Operation is permitted only for authors')
return cmd
@@ -166,6 +168,7 @@ class MasterCommands(NodeCommands, SyncCommands):
@ad.document_command(method='PUT', cmd='merge',
permissions=ad.ACCESS_AUTH)
def merge(self, document, guid, request):
+ auth.validate(request, 'root')
directory = self.volume[document]
directory.merge(guid, request.content)
diff --git a/sugar_network/resources/context.py b/sugar_network/resources/context.py
index 5683664..8af986c 100644
--- a/sugar_network/resources/context.py
+++ b/sugar_network/resources/context.py
@@ -20,7 +20,7 @@ from sugar_network import resources, static
from sugar_network.local import activities
from sugar_network.resources.volume import Resource
from sugar_network.zerosugar import Spec
-from sugar_network.node import obs
+from sugar_network.node import obs, auth
from active_toolkit import coroutine, util
@@ -158,19 +158,21 @@ class Context(Resource):
def presolve(self, value):
return value
- @classmethod
- @ad.directory_command(method='PUT', cmd='include',
- arguments={'layers': ad.to_list})
- def include(cls, directory, layers, request):
- import logging
- logging.error('include> %r %r', layers, request.content)
-
- @classmethod
- @ad.directory_command(method='PUT', cmd='exclude',
- arguments={'layers': ad.to_list})
- def exclude(cls, directory, layers, request):
- import logging
- logging.error('exclude> %r %r', layers, request.content)
+ @ad.document_command(method='PUT', cmd='attach',
+ permissions=ad.ACCESS_AUTH)
+ def attach(self, request):
+ auth.validate(request, 'root')
+ # TODO Reading layer here is a race
+ layer = list(set(self['layer']) | set(request.content))
+ request.volume['context'].update(self.guid, {'layer': layer})
+
+ @ad.document_command(method='PUT', cmd='detach',
+ permissions=ad.ACCESS_AUTH)
+ def detach(self, request):
+ auth.validate(request, 'root')
+ # TODO Reading layer here is a race
+ layer = list(set(self['layer']) - set(request.content))
+ request.volume['context'].update(self.guid, {'layer': layer})
def _process_aliases(self, aliases):
packages = {}
diff --git a/sugar_network/resources/volume.py b/sugar_network/resources/volume.py
index b7680a1..bf5d122 100644
--- a/sugar_network/resources/volume.py
+++ b/sugar_network/resources/volume.py
@@ -36,6 +36,7 @@ _logger = logging.getLogger('resources.volume')
class Request(ad.Request):
+ principal = None
mountpoint = None
diff --git a/sugar_network/toolkit/router.py b/sugar_network/toolkit/router.py
index 88113cd..29c8192 100644
--- a/sugar_network/toolkit/router.py
+++ b/sugar_network/toolkit/router.py
@@ -249,7 +249,6 @@ class _Request(Request):
environ = None
url = None
path = None
- principal = None
def __init__(self, environ=None):
Request.__init__(self)
diff --git a/tests/__init__.py b/tests/__init__.py
index 9d7359a..30193fc 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -27,7 +27,7 @@ from sugar_network.resources.user import User
from sugar_network.resources.context import Context
from sugar_network.node.commands import NodeCommands, MasterCommands
from sugar_network.node.router import Router as MasterRouter
-from sugar_network.node import stats, obs
+from sugar_network.node import stats, obs, auth
from sugar_network.resources.volume import Volume
@@ -90,6 +90,7 @@ class Test(unittest.TestCase):
stats._cache.clear()
obs._client = None
http._RECONNECTION_NUMBER = 0
+ auth._config = None
Volume.RESOURCES = [
'sugar_network.resources.user',
@@ -114,6 +115,7 @@ class Test(unittest.TestCase):
self.server = None
self.mounts = None
+ self.volume = None
self.forks = []
self.fork_num = fork_num
@@ -134,6 +136,9 @@ class Test(unittest.TestCase):
pid = self.forks.pop()
self.assertEqual(0, self.waitpid(pid))
coroutine.shutdown()
+ if self.volume is not None:
+ self.volume.close()
+ self.volume = None
def waitpid(self, pid, sig=signal.SIGTERM):
if pid in self.forks:
@@ -273,12 +278,12 @@ class Test(unittest.TestCase):
if classes is None:
classes = [User, Context]
self.touch('master/master')
- volume = Volume('master', classes)
- cp = MasterCommands(volume)
+ self.volume = Volume('master', classes)
+ cp = MasterCommands(self.volume)
self.server = coroutine.WSGIServer(('localhost', 8800), MasterRouter(cp))
coroutine.spawn(self.server.serve_forever)
coroutine.dispatch()
- return volume
+ return self.volume
def sign(privkey, data):
diff --git a/tests/units/__main__.py b/tests/units/__main__.py
index da752b3..5e3f83d 100644
--- a/tests/units/__main__.py
+++ b/tests/units/__main__.py
@@ -22,6 +22,7 @@ from remote_mount import *
from node_mount import *
from injector import *
from mountset import *
+from auth import *
from context import *
from implementation import *
from obs import *
diff --git a/tests/units/auth.py b/tests/units/auth.py
new file mode 100755
index 0000000..e499f35
--- /dev/null
+++ b/tests/units/auth.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# sugar-lint: disable
+
+from __init__ import tests
+
+import active_document as ad
+from sugar_network.node import auth
+from sugar_network import IPCClient, Client
+from sugar_network.resources.volume import Request
+from active_toolkit import enforce
+
+
+class AuthTest(tests.Test):
+
+ def test_Config(self):
+ self.touch(('authorization.conf', [
+ '[user_1]',
+ 'role_1 = True',
+ '[user_2]',
+ 'role_2 = False',
+ ]))
+
+ request = Request()
+ request.principal = 'user_1'
+ self.assertEqual(True, auth.try_validate(request, 'role_1'))
+ auth.validate(request, 'role_1')
+
+ request.principal = 'user_2'
+ self.assertEqual(False, auth.try_validate(request, 'role_2'))
+ self.assertRaises(ad.Forbidden, auth.validate, request, 'role_2')
+
+ request.principal = 'user_3'
+ self.assertEqual(False, auth.try_validate(request, 'role_1'))
+ self.assertEqual(False, auth.try_validate(request, 'role_2'))
+ self.assertRaises(ad.Forbidden, auth.validate, request, 'role_1')
+ self.assertRaises(ad.Forbidden, auth.validate, request, 'role_2')
+
+ def test_FullWriteForRoot(self):
+ client = Client()
+
+ self.start_master()
+ client.post(['context'], {
+ 'implement': 'guid',
+ 'type': 'package',
+ 'title': 'title',
+ 'summary': 'summary',
+ 'description': 'description',
+ })
+ self.assertNotEqual('probe', client.get(['context', 'guid', 'title']))
+ self.stop_servers()
+
+ self.touch((
+ 'master/context/gu/guid/user',
+ '{"seqno": 1, "value": ["fake"]}',
+ ))
+
+ self.start_master()
+ self.assertRaises(RuntimeError, client.put, ['context', 'guid'], {'title': 'probe'})
+
+ self.touch(('authorization.conf', [
+ '[%s]' % tests.UID,
+ 'root = True',
+ ]))
+ auth._config = None
+ client.put(['context', 'guid'], {'title': 'probe'})
+ self.assertEqual('probe', client.get(['context', 'guid', 'title']))
+
+
+if __name__ == '__main__':
+ tests.main()