diff options
author | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-14 19:40:10 (GMT) |
---|---|---|
committer | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-14 19:40:10 (GMT) |
commit | f7397765c3d98b52ae3f0123ebd7b970ba2bb1bd (patch) | |
tree | 8973ad0db0b5a4320beb3700ec05149b4224c23c | |
parent | 745f38cfd5bbe82de191e49b9ff1a66651f96542 (diff) |
Create common User document; support registration
-rw-r--r-- | restful_document/__init__.py | 1 | ||||
-rw-r--r-- | restful_document/document.py | 10 | ||||
-rw-r--r-- | restful_document/router.py | 13 | ||||
-rw-r--r-- | restful_document/user.py | 85 | ||||
-rwxr-xr-x | tests/units/user.py | 46 |
5 files changed, 148 insertions, 7 deletions
diff --git a/restful_document/__init__.py b/restful_document/__init__.py index 9c09d54..22f427e 100644 --- a/restful_document/__init__.py +++ b/restful_document/__init__.py @@ -16,3 +16,4 @@ from restful_document.document import Document, restful_method from restful_document.metadata import Metadata, Method from restful_document.router import Router +from restful_document.user import User diff --git a/restful_document/document.py b/restful_document/document.py index 792f80e..2f9adca 100644 --- a/restful_document/document.py +++ b/restful_document/document.py @@ -27,13 +27,13 @@ class Document(ad.Document): @classmethod @restful_method(method='POST') - def _restful_create(cls): + def restful_post(cls): doc = cls.create(env.request.content) return {'guid': doc.guid} @classmethod @restful_method(method='GET') - def _restful_find(cls, **kwargs): + def restful_get(cls, **kwargs): offset = env.pop_int('offset', kwargs, None) limit = env.pop_int('limit', kwargs, None) query = env.pop_str('query', kwargs, None) @@ -46,7 +46,7 @@ class Document(ad.Document): 'documents': [i.all_properties(reply) for i in documents]} @restful_method(method='PUT') - def _restful_update(self, prop=None): + def restful_put(self, prop=None): if prop is None: self.update(self.guid, env.request.content) elif isinstance(self.metadata[prop], ad.BlobProperty): @@ -57,13 +57,13 @@ class Document(ad.Document): self.post() @restful_method(method='DELETE') - def _restful_delete(self, prop=None): + def restful_delete(self, prop=None): enforce(prop is None, env.Forbidden, _('Properties cannot be deleted')) self.delete(self.guid) @restful_method(method='GET') - def _restful_get(self, prop=None): + def restful_get_document(self, prop=None): if prop is None: reply = [] for name, prop in self.metadata.items(): diff --git a/restful_document/router.py b/restful_document/router.py index f1cd360..43e2806 100644 --- a/restful_document/router.py +++ b/restful_document/router.py @@ -15,6 +15,7 @@ import json import types +import logging from gettext import gettext as _ import active_document as ad @@ -24,6 +25,9 @@ from restful_document.metadata import Metadata from restful_document.util import enforce +_logger = logging.getLogger('rd.router') + + class Router(object): def __init__(self, classes): @@ -34,6 +38,9 @@ class Router(object): env.request.set(environ) env.responce.set() + _logger.debug('Processing request %s: %s', + env.request.url, env.request.content) + try: method = self.metadata.get_method() enforce(method is not None and \ @@ -42,16 +49,18 @@ class Router(object): result = method() except Exception, error: - util.exception(_('Error while processing %s request'), - env.request.url) if isinstance(error, ad.Unauthorized): + util.exception() env.responce.status = env.Unauthorized.status env.responce.update(env.Unauthorized.headers) elif isinstance(error, env.HTTPError): env.responce.status = error.status env.responce.update(error.headers) else: + util.exception(_('Error while processing "%s" request'), + env.request.url) env.responce.status = '500 Internal Server Error' + env.responce['Content-Type'] = 'application/json' result = {'error': str(error), 'request': env.request.url} diff --git a/restful_document/user.py b/restful_document/user.py new file mode 100644 index 0000000..3a7f770 --- /dev/null +++ b/restful_document/user.py @@ -0,0 +1,85 @@ +# 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/>. + +import os +import hashlib +from gettext import gettext as _ + +from M2Crypto import DSA + +import active_document as ad + +from restful_document import env, util +from restful_document.metadata import restful_method +from restful_document.document import Document +from restful_document.util import enforce + + +class User(Document): + + def __init__(self, guid=None, manual_guid=None, *args, **kwargs): + Document.__init__(self, guid, *args, **kwargs) + if guid is None and manual_guid is not None: + self._guid = manual_guid + + @ad.active_property(slot=1, prefix='N', full_text=True) + def nickname(self, value): + return value + + @ad.active_property(ad.StoredProperty) + def color(self, value): + return value + + @ad.active_property(slot=2, prefix='S', permissions=ad.ACCESS_CREATE) + def machine_sn(self, value): + return value + + @ad.active_property(slot=3, prefix='U', permissions=ad.ACCESS_CREATE) + def machine_uuid(self, value): + return value + + @ad.active_property(ad.StoredProperty, permissions=ad.ACCESS_CREATE) + def pubkey(self, value): + return value + + @classmethod + @restful_method(method='POST') + def restful_post(cls): + props = env.request.content + enforce('pubkey' in props, + _('Property "pubkey" is required for user registeration')) + props['manual_guid'], props['pubkey'] = \ + _load_pubkey(props['pubkey'].strip()) + doc = cls.create(env.request.content) + return {'guid': doc.guid} + + +def _load_pubkey(pubkey): + if 'SSH_ASKPASS' in os.environ: + # Otherwise ssh-keygen will popup auth dialog + del os.environ['SSH_ASKPASS'] + + try: + src_path = util.TempFilePath(text=pubkey) + dst_pubkey = util.assert_call( + ['ssh-keygen', '-f', src_path, '-e', '-m', 'PKCS8']) + dst_path = util.TempFilePath(text=dst_pubkey) + DSA.load_pub_key(dst_path) + except Exception: + message = _('Cannot read DSS public key gotten for registeration') + util.exception(message) + raise env.Forbidden(message) + + return str(hashlib.sha1(pubkey.split()[1]).hexdigest()), dst_pubkey diff --git a/tests/units/user.py b/tests/units/user.py new file mode 100755 index 0000000..a237611 --- /dev/null +++ b/tests/units/user.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# sugar-lint: disable + +import json + +import restkit + +from __init__ import tests +from tests import Resource + +import restful_document as rd + + +VALID_DSS_PUBKEY = """\ +ssh-dss AAAAB3NzaC1kc3MAAACBAOl6rwXgjVrsGAry6rFHljYE94pQAVynFJPFbzvP9zy5oLg58WvMTr9LvYzS1kB0Y3ym6yQl0RRS8CUtbGpuDaD0mIXjbkfukE7CkvQ3v+lIybk30QgvkxO7nQa4+NW5QwvASU6E1ODFa2QTW15/Fq3MhMYH+3UqZv5g/bBrikoXAAAAFQDctWzPfX1I4hL2wYfR2U2M4bnXaQAAAIEAl5fWWWX2OiYz9i4aza4PGUFUAc3RI+ksrmGrL0FZ5hTiTx/iqc2uhGiQY5CqVyb+/E76flQVkSvlghRmGh4lwrbFPj4PIMP23M5lqU/Dk2srnB1WC+huK/wLHw11NJrRIAsufcycIowe9w09GMsfCzVXcETttvKB+l35KoF1irYAAACBAL0vRSHl+bnTHW97FUkh+ycDTKM/eZSi0zI8Ptcw7DU81T/d5fU7aJtUqx7Inxot0YKeZx2ZeWAVzE9opoK8cnvawDkomRhL1m8IUiC0uCNAEG2C1a3uioY+OJbvq/J1QP6KIwidlzu8m536o8odgTow29j5exw9+i3cbr+KiT4R +""" +INVALID_DSS_PUBKEY = """\ +ssh-dss _AAAB3NzaC1kc3MAAACBAOl6rwXgjVrsGAry6rFHljYE94pQAVynFJPFbzvP9zy5oLg58WvMTr9LvYzS1kB0Y3ym6yQl0RRS8CUtbGpuDaD0mIXjbkfukE7CkvQ3v+lIybk30QgvkxO7nQa4+NW5QwvASU6E1ODFa2QTW15/Fq3MhMYH+3UqZv5g/bBrikoXAAAAFQDctWzPfX1I4hL2wYfR2U2M4bnXaQAAAIEAl5fWWWX2OiYz9i4aza4PGUFUAc3RI+ksrmGrL0FZ5hTiTx/iqc2uhGiQY5CqVyb+/E76flQVkSvlghRmGh4lwrbFPj4PIMP23M5lqU/Dk2srnB1WC+huK/wLHw11NJrRIAsufcycIowe9w09GMsfCzVXcETttvKB+l35KoF1irYAAACBAL0vRSHl+bnTHW97FUkh+ycDTKM/eZSi0zI8Ptcw7DU81T/d5fU7aJtUqx7Inxot0YKeZx2ZeWAVzE9opoK8cnvawDkomRhL1m8IUiC0uCNAEG2C1a3uioY+OJbvq/J1QP6KIwidlzu8m536o8odgTow29j5exw9+i3cbr+KiT4R +""" +UID_FOR_PUBKEY = 'd26cef70447160f31a7497cc0320f23a4e383cc3' + + +class UserTest(tests.Test): + + def test_Register(self): + self.httpd(8000, [rd.User]) + rest = Resource('http://localhost:8000') + + props = { + 'nickname': 'foo', + 'color': '', + 'machine_sn': 'machine_sn', + 'machine_uuid': 'machine_uuid', + } + + props['pubkey'] = INVALID_DSS_PUBKEY + self.assertRaises(restkit.Unauthorized, rest.post, '/user', payload=json.dumps(props)) + + props['pubkey'] = VALID_DSS_PUBKEY + reply = json.loads( + rest.post('/user', payload=json.dumps(props)).body_string()) + self.assertEqual(UID_FOR_PUBKEY, reply['guid']) + + +if __name__ == '__main__': + tests.main() |