diff options
author | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-14 21:20:37 (GMT) |
---|---|---|
committer | Aleksey Lim <alsroot@sugarlabs.org> | 2012-01-14 21:20:37 (GMT) |
commit | f78bf4a43a6b6e0acacfa55419c3c0e9ec983516 (patch) | |
tree | 377fa054cc06a4b348d9807957617bfc10fa1d47 | |
parent | f7397765c3d98b52ae3f0123ebd7b970ba2bb1bd (diff) |
Support authentication
-rw-r--r-- | restful_document/env.py | 19 | ||||
-rw-r--r-- | restful_document/router.py | 5 | ||||
-rw-r--r-- | restful_document/user.py | 21 | ||||
-rw-r--r-- | tests/units/__main__.py | 1 | ||||
-rwxr-xr-x | tests/units/user.py | 68 |
5 files changed, 97 insertions, 17 deletions
diff --git a/restful_document/env.py b/restful_document/env.py index d0ba7b8..bb4d991 100644 --- a/restful_document/env.py +++ b/restful_document/env.py @@ -156,5 +156,24 @@ class Responce(threading.local): return key in self._headers +class Principal(threading.local): + """Authenticated user.""" + + _cache = {} + + @property + def user(self): + enforce('sugar_user' in request and 'sugar_user_signature' in request, + Unauthorized, _('Sugar user credentials were not specified')) + signature = request['sugar_user_signature'] + user = self._cache.get(signature) + if user is None: + from restful_document.user import User + User.verify(request['sugar_user'], signature) + user = self._cache[signature] = request['sugar_user'] + return user + + request = Request() responce = Responce() +principal = Principal() diff --git a/restful_document/router.py b/restful_document/router.py index 43e2806..367e335 100644 --- a/restful_document/router.py +++ b/restful_document/router.py @@ -13,6 +13,7 @@ # 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 json import types import logging @@ -34,6 +35,10 @@ class Router(object): ad.init(classes) self.metadata = Metadata(classes) + if 'SSH_ASKPASS' in os.environ: + # Otherwise ssh-keygen will popup auth dialogs on registeration + del os.environ['SSH_ASKPASS'] + def __call__(self, environ, start_response): env.request.set(environ) env.responce.set() diff --git a/restful_document/user.py b/restful_document/user.py index 3a7f770..300c0ea 100644 --- a/restful_document/user.py +++ b/restful_document/user.py @@ -13,8 +13,8 @@ # 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 os.path import exists from gettext import gettext as _ from M2Crypto import DSA @@ -65,21 +65,26 @@ class User(Document): doc = cls.create(env.request.content) return {'guid': doc.guid} + @classmethod + def verify(cls, guid, signature): + # TODO Avoid direct access to pubkey property + pubkey_path = cls.metadata.path(guid[:2], guid, 'pubkey') + enforce(exists(pubkey_path), env.Forbidden, + _('Principal user does not exist')) + pubkey = DSA.load_pub_key(pubkey_path) + data = hashlib.sha1(guid).digest() + enforce(pubkey.verify_asn1(data, signature.decode('hex')), + env.Forbidden, _('Wrong principal credentials')) -def _load_pubkey(pubkey): - if 'SSH_ASKPASS' in os.environ: - # Otherwise ssh-keygen will popup auth dialog - del os.environ['SSH_ASKPASS'] +def _load_pubkey(pubkey): try: src_path = util.TempFilePath(text=pubkey) + # SSH key needs to be converted to PKCS8 to ket M2Crypto read it 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/__main__.py b/tests/units/__main__.py index 2524a13..83f6854 100644 --- a/tests/units/__main__.py +++ b/tests/units/__main__.py @@ -3,5 +3,6 @@ from __init__ import tests from document import * +from user import * tests.main() diff --git a/tests/units/user.py b/tests/units/user.py index a237611..f436aed 100755 --- a/tests/units/user.py +++ b/tests/units/user.py @@ -2,26 +2,25 @@ # sugar-lint: disable import json +import hashlib import restkit +from M2Crypto import DSA 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' +from restful_document import env, util class UserTest(tests.Test): + def setUp(self): + tests.Test.setUp(self) + env.principal._cache.clear() + env.request.environ = {} + def test_Register(self): self.httpd(8000, [rd.User]) rest = Resource('http://localhost:8000') @@ -41,6 +40,57 @@ class UserTest(tests.Test): rest.post('/user', payload=json.dumps(props)).body_string()) self.assertEqual(UID_FOR_PUBKEY, reply['guid']) + def test_Authenticate(self): + rd.User.init() + + def set_headers(user): + key_path = util.TempFilePath(text=DSS_PRIVKEY) + key = DSA.load_key(key_path) + signature = key.sign_asn1(hashlib.sha1(user).digest()).encode('hex') + env.request.environ['HTTP_sugar_user'] = user + env.request.environ['HTTP_sugar_user_signature'] = signature + + set_headers('foo') + self.assertRaises(env.Forbidden, lambda: env.principal.user) + + self.httpd(8000, [rd.User]) + rest = Resource('http://localhost:8000') + + props = { + 'nickname': 'foo', + 'color': '', + 'machine_sn': 'machine_sn', + 'machine_uuid': 'machine_uuid', + 'pubkey': VALID_DSS_PUBKEY, + } + rest.post('/user', payload=json.dumps(props)) + + set_headers(UID_FOR_PUBKEY) + self.assertEqual(UID_FOR_PUBKEY, env.principal.user) + + +VALID_DSS_PUBKEY = """\ +ssh-dss AAAAB3NzaC1kc3MAAACBANuYoFH3uvJGoQFMeW6M3CCJQlPrSv6sqd9dGQlwnnNxLBrq6KgY63e10ULtyYzq9UjiIUowqbtheGrtPCtL5w7qmFcCnq1cFzAk6Xxfe6ytJDx1fql5Y1wKqa+zxOKF6SGNnglyxvf78mZXt2G6wx22AjW+1fEhAOr+g8kRiUbBAAAAFQDA/W3LfD5NBB4vlZFcT10jU4B8QwAAAIBHh1U2B71memu/TsatwOo9+CyUyvF0FHHsXwQDkeRjqY3dcfeV38YoU/EbOZtHIQgdfGrzy7m5osnpBwUtHLunZJuwCt5tBNrpU8CAF7nEXOJ4n2FnoNiWO1IsbWdhkh9Hd7+TBM9hLGmOqlqTIx3TmUG0e4F2X33VVJ8UsrJ3mwAAAIEAm29WVw9zkRbv6CTFhPlLjJ71l/2GE9XFbdznJFRmPNBBWF2J452okRWywzeDMIIoi/z0wmNSr2B6P9wduxSxp8eIWQhKVQa4V4lJyqX/A2tE5SQtFULtw3yiYOUaCjvB2s46ZM6/9K3r8o7FSKHDpYlqAbBKURNCot5zDAu6RgE= +""" +INVALID_DSS_PUBKEY = """\ +ssh-dss ____B3NzaC1kc3MAAACBANuYoFH3uvJGoQFMeW6M3CCJQlPrSv6sqd9dGQlwnnNxLBrq6KgY63e10ULtyYzq9UjiIUowqbtheGrtPCtL5w7qmFcCnq1cFzAk6Xxfe6ytJDx1fql5Y1wKqa+zxOKF6SGNnglyxvf78mZXt2G6wx22AjW+1fEhAOr+g8kRiUbBAAAAFQDA/W3LfD5NBB4vlZFcT10jU4B8QwAAAIBHh1U2B71memu/TsatwOo9+CyUyvF0FHHsXwQDkeRjqY3dcfeV38YoU/EbOZtHIQgdfGrzy7m5osnpBwUtHLunZJuwCt5tBNrpU8CAF7nEXOJ4n2FnoNiWO1IsbWdhkh9Hd7+TBM9hLGmOqlqTIx3TmUG0e4F2X33VVJ8UsrJ3mwAAAIEAm29WVw9zkRbv6CTFhPlLjJ71l/2GE9XFbdznJFRmPNBBWF2J452okRWywzeDMIIoi/z0wmNSr2B6P9wduxSxp8eIWQhKVQa4V4lJyqX/A2tE5SQtFULtw3yiYOUaCjvB2s46ZM6/9K3r8o7FSKHDpYlqAbBKURNCot5zDAu6RgE= +""" +DSS_PRIVKEY = """\ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQDbmKBR97ryRqEBTHlujNwgiUJT60r+rKnfXRkJcJ5zcSwa6uio +GOt3tdFC7cmM6vVI4iFKMKm7YXhq7TwrS+cO6phXAp6tXBcwJOl8X3usrSQ8dX6p +eWNcCqmvs8TihekhjZ4Jcsb3+/JmV7dhusMdtgI1vtXxIQDq/oPJEYlGwQIVAMD9 +bct8Pk0EHi+VkVxPXSNTgHxDAoGAR4dVNge9Znprv07GrcDqPfgslMrxdBRx7F8E +A5HkY6mN3XH3ld/GKFPxGzmbRyEIHXxq88u5uaLJ6QcFLRy7p2SbsArebQTa6VPA +gBe5xFzieJ9hZ6DYljtSLG1nYZIfR3e/kwTPYSxpjqpakyMd05lBtHuBdl991VSf +FLKyd5sCgYEAm29WVw9zkRbv6CTFhPlLjJ71l/2GE9XFbdznJFRmPNBBWF2J452o +kRWywzeDMIIoi/z0wmNSr2B6P9wduxSxp8eIWQhKVQa4V4lJyqX/A2tE5SQtFULt +w3yiYOUaCjvB2s46ZM6/9K3r8o7FSKHDpYlqAbBKURNCot5zDAu6RgECFQC6wU/U +6uUSSSw8Apr+eJQlSFhA+Q== +-----END DSA PRIVATE KEY----- +""" +UID_FOR_PUBKEY = '25c081e29242cf7a19ae893a420ab3de56e9e989' + if __name__ == '__main__': tests.main() |