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-01-14 21:20:37 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-01-14 21:20:37 (GMT)
commitf78bf4a43a6b6e0acacfa55419c3c0e9ec983516 (patch)
tree377fa054cc06a4b348d9807957617bfc10fa1d47
parentf7397765c3d98b52ae3f0123ebd7b970ba2bb1bd (diff)
Support authentication
-rw-r--r--restful_document/env.py19
-rw-r--r--restful_document/router.py5
-rw-r--r--restful_document/user.py21
-rw-r--r--tests/units/__main__.py1
-rwxr-xr-xtests/units/user.py68
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()