Merge lp:~hazmat/pyjuju/security-principal-def into lp:pyjuju

Proposed by Kapil Thangavelu
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 270
Merged at revision: 275
Proposed branch: lp:~hazmat/pyjuju/security-principal-def
Merge into: lp:pyjuju
Diff against target: 166 lines (+157/-0)
2 files modified
ensemble/state/security.py (+62/-0)
ensemble/state/tests/test_security.py (+95/-0)
To merge this branch: bzr merge lp:~hazmat/pyjuju/security-principal-def
Reviewer Review Type Date Requested Status
William Reade (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+67618@code.launchpad.net

Description of the change

Basic security constructs (principals and token db), as a basis for future security integration.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

That looks awesome. +1!

review: Approve
Revision history for this message
William Reade (fwereade) wrote :

Needs updating from trunk before tests will run on my machine, but otherwise looks good to me :).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'ensemble/state/security.py'
2--- ensemble/state/security.py 1970-01-01 00:00:00 +0000
3+++ ensemble/state/security.py 2011-07-11 20:16:06 +0000
4@@ -0,0 +1,62 @@
5+from twisted.internet.defer import inlineCallbacks, returnValue
6+from ensemble.state.auth import make_identity
7+from ensemble.state.utils import YAMLState
8+
9+
10+class Principal(object):
11+ """An ensemble/zookeeper principal.
12+ """
13+
14+ def __init__(self, name, password):
15+ self._name = name
16+ self._password = password
17+
18+ @property
19+ def name(self):
20+ """A principal has a login name."""
21+ return self._name
22+
23+ def get_token(self):
24+ """A principal identity token can be retrieved.
25+
26+ An identity token is used to construct ACLs.
27+ """
28+ return make_identity("%s:%s" % (self._name, self._password))
29+
30+ def attach(self, connection):
31+ """A princpal can be attached to a connection."""
32+ return connection.add_auth(
33+ "digest", "%s:%s" % (self._name, self._password))
34+
35+
36+class TokenDatabase(object):
37+ """A hash map of principal names to their identity tokens.
38+
39+ Identity tokens are used to construct node ACLs.
40+ """
41+ def __init__(self, client, path="/auth-tokens"):
42+ self._state = YAMLState(client, path)
43+
44+ @inlineCallbacks
45+ def add(self, principal):
46+ """Add a principal to the token database.
47+ """
48+ yield self._state.read()
49+ self._state[principal.name] = principal.get_token()
50+ yield self._state.write()
51+
52+ @inlineCallbacks
53+ def get(self, name):
54+ """Return the identity token for a principal name.
55+ """
56+ yield self._state.read()
57+ returnValue(self._state[name])
58+
59+ @inlineCallbacks
60+ def remove(self, name):
61+ """Remove a principal by name from the token database.
62+ """
63+ yield self._state.read()
64+ if name in self._state:
65+ del self._state[name]
66+ yield self._state.write()
67
68=== added file 'ensemble/state/tests/test_security.py'
69--- ensemble/state/tests/test_security.py 1970-01-01 00:00:00 +0000
70+++ ensemble/state/tests/test_security.py 2011-07-11 20:16:06 +0000
71@@ -0,0 +1,95 @@
72+import yaml
73+import zookeeper
74+
75+from twisted.internet.defer import inlineCallbacks
76+
77+from ensemble.state.auth import make_identity, make_ace
78+from ensemble.state.security import Principal, TokenDatabase
79+
80+from ensemble.lib.testing import TestCase
81+from txzookeeper.tests.utils import deleteTree
82+
83+
84+class PrincipalTests(TestCase):
85+
86+ @inlineCallbacks
87+ def setUp(self):
88+ zookeeper.set_debug_level(0)
89+ self.client = yield self.get_zookeeper_client().connect()
90+
91+ def tearDown(self):
92+ deleteTree(handle=self.client.handle)
93+ self.client.close()
94+
95+ def test_name(self):
96+ """Principals have names."""
97+ principal = Principal("foobar", "secret")
98+ self.assertEqual(principal.name, "foobar")
99+
100+ def test_get_token(self):
101+ """An identity token can be gotten from a Principal."""
102+ principal = Principal("foobar", "secret")
103+ self.assertEqual(principal.get_token(),
104+ make_identity("foobar:secret"))
105+
106+ @inlineCallbacks
107+ def test_activate(self):
108+ """A principal can be used with a client connection."""
109+ client = yield self.get_zookeeper_client().connect()
110+ self.addCleanup(lambda: client.close())
111+ admin_credentials = "admin:admin"
112+ test_credentials = "test:test"
113+ self.client.add_auth("digest", admin_credentials)
114+
115+ acl = [make_ace(make_identity(admin_credentials), all=True),
116+ make_ace(make_identity(
117+ test_credentials), read=True, create=True)]
118+
119+ yield client.create("/acl-test", "content", acls=acl)
120+
121+ # Verify the acl is active
122+ yield self.assertFailure(
123+ client.get("/acl-test"), zookeeper.NoAuthException)
124+
125+ # Attach the principal to the connection
126+ principal = Principal("test", "test")
127+ yield principal.attach(client)
128+ content, stat = yield client.get("/acl-test")
129+ self.assertEqual(content, "content")
130+
131+
132+class TokenDatabaseTest(TestCase):
133+
134+ @inlineCallbacks
135+ def setUp(self):
136+ zookeeper.set_debug_level(0)
137+ self.client = yield self.get_zookeeper_client().connect()
138+ self.db = TokenDatabase(self.client, "/token-test")
139+
140+ def tearDown(self):
141+ deleteTree(handle=self.client.handle)
142+ self.client.close()
143+
144+ @inlineCallbacks
145+ def test_add(self):
146+ principal = Principal("zebra", "zoo")
147+ yield self.db.add(principal)
148+ content, stat = yield self.client.get("/token-test")
149+ data = yaml.load(content)
150+ self.assertEqual(data, {"zebra": principal.get_token()})
151+
152+ @inlineCallbacks
153+ def test_remove(self):
154+ principal = Principal("zebra", "zoo")
155+ yield self.db.add(principal)
156+ yield self.db.remove(principal)
157+ content, stat = yield self.client.get("/token-test")
158+ data = yaml.load(content)
159+ self.assertEqual(data, {"zebra": principal.get_token()})
160+
161+ @inlineCallbacks
162+ def test_get(self):
163+ principal = Principal("zebra", "zoo")
164+ yield self.db.add(principal)
165+ token = yield self.db.get(principal.name)
166+ self.assertEqual(token, principal.get_token())

Subscribers

People subscribed via source and target branches

to status/vote changes: