Skip to content

Commit

Permalink
Merge branch 'feature/security' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
icorderi committed Jun 21, 2014
2 parents dd87896 + 6208eb6 commit daea609
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 34 deletions.
9 changes: 7 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
Changes since 0.7.0
===================
Changes since 0.7.1
===========================
This section will document changes to the library since the last release

Changes from 0.7.0 to 0.7.1
===========================

## New features
- Added setSecurity on AdminClient
- Added getVersion and getVersionAsync to the library.

## Bug Fixes
- Fixed tests not running and testcases with hardcoded 'localhost'
- Fixed flush operation build parameters (Merge #5, contributed by @rpcope1).
- AsyncClient returns NotConnected exception when an operation is attempted on a client before calling connect().
- Lowered default number of keys asked on ranges to 200 (ASKOVAD-287).
Expand Down
2 changes: 1 addition & 1 deletion kinetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

# Protocol version
protocol_version = '2.0.3'
version = '0.7.0'
version = '0.7.1'

#utils
from utils import buildRange
Expand Down
8 changes: 8 additions & 0 deletions kinetic/admin/adminclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def setClusterVersion(self, cluster_version, pin=None):
def updateFirmware(self, binary, pin=None):
return self._process(operations.Setup, firmware=binary, pin=pin)

def setSecurity(self, acls):
"""
Set the access control lists to lock users out of different permissions.
Arguments: aclList -> A list of ACL (Access Control List) objects.
"""
return self._process(operations.Security, acls=acls)


def main():
from kinetic.common import LogTypes
from kinetic import Client
Expand Down
62 changes: 62 additions & 0 deletions kinetic/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
MAX_KEY_SIZE = 4*1024
MAX_VALUE_SIZE = 1024*1024


class Entry(object):

#RPC: Note, you could build this as a class method, if you wanted the fromMessage to build
Expand Down Expand Up @@ -51,6 +52,7 @@ def __str__(self):
else:
return self.key


class EntryMetadata(object):

@staticmethod
Expand All @@ -67,6 +69,7 @@ def __init__(self, version=None, tag=None, algorithm=None):
def __str__(self):
return self.version or "None"


class KeyRange(object):

def __init__(self, startKey, endKey, startKeyInclusive=True,
Expand All @@ -79,6 +82,7 @@ def __init__(self, startKey, endKey, startKeyInclusive=True,
def getFrom(self, client, max=1024):
return client.getKeyRange(self.startKey, self.endKey, self.startKeyInclusive, self.endKeyInclusive, max)


class P2pOp(object):

def __init__(self, key, version=None, newKey=None, force=None):
Expand All @@ -87,6 +91,7 @@ def __init__(self, key, version=None, newKey=None, force=None):
self.newKey = newKey
self.force = force


class Peer(object):

def __init__(self, hostname='localhost', port=8123, tls=None):
Expand Down Expand Up @@ -133,12 +138,68 @@ def __init__(self, status):
def __str__(self):
return self.code + (': %s' % self.value if self.value else '')


class HmacAlgorithms:
INVALID_HMAC_ALGORITHM = -1 # Must come first, so default is invalid
HmacSHA1 = 1 # this is the default


class ACL(object):

DEFAULT_IDENTITY=1
DEFAULT_KEY = "asdfasdf"

def __init__(self, identity=DEFAULT_IDENTITY, key=DEFAULT_KEY, algorithm=HmacAlgorithms.HmacSHA1):
self.identity = identity
self.key = key
self.hmacAlgorithm = algorithm
self.domains = set()


class Domain(object):
"""
Domain object, which corresponds to the domain object in the Java client,
and is the Scope object in the protobuf.
"""
def __init__(self, roles=None, tlsRequried=False, offset=None, value=None):
if roles:
self.roles = set(roles)
else:
self.roles = set()
self.tlsRequired = tlsRequried
self.offset = offset
self.value = value


class Roles(object):
"""
Role enumeration, which is the same thing as the permission field for each
scope in the protobuf ACL list.
"""
READ = 0
WRITE = 1
DELETE = 2
RANGE = 3
SETUP = 4
P2POP = 5
GETLOG = 7
SECURITY = 8

@classmethod
def all(cls):
"""
Return the set of all possible roles.
"""
return [cls.READ, cls.WRITE, cls.DELETE, cls.RANGE, cls.SETUP, cls.P2POP, cls.GETLOG, cls.SECURITY]


class Synchronization:
INVALID_SYNCHRONIZATION = -1
WRITETHROUGH = 1 # Sync
WRITEBACK = 2 # Async
FLUSH = 3


class IntegrityAlgorithms:
SHA1 = 1
SHA2 = 2
Expand All @@ -148,6 +209,7 @@ class IntegrityAlgorithms:
# 6-99 are reserverd.
# 100-inf are private algorithms


class LogTypes:
INVALID_TYPE = -1
UTILIZATIONS = 0
Expand Down
47 changes: 46 additions & 1 deletion kinetic/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,52 @@ def build(**kwargs):
@staticmethod
def parse(m, value):
Entry.fromResponse(m, value)
return True
return

@staticmethod
def onError(e):
raise e

class Security(object):

@staticmethod
def build(acls):
m = messages.Message()
m.command.header.messageType = messages.Message.SECURITY

proto_acls = []

for acl in acls:
proto_acl = messages.Message.Security.ACL(identity=acl.identity,
key=acl.key,
hmacAlgorithm=acl.hmacAlgorithm)

proto_domains = []

for domain in acl.domains:
proto_d = messages.Message.Security.ACL.Scope(
TlsRequired=domain.tlsRequired)

proto_d.permission.extend(domain.roles)

if domain.offset:
proto_d.offset = domain.offset
if domain.value:
proto_d.value = domain.value

proto_domains.append(proto_d)

proto_acl.scope.extend(proto_domains)
proto_acls.append(proto_acl)

m.command.body.security.acl.extend(proto_acls)

return (m, None)

@staticmethod
def parse(m, value):
Entry.fromResponse(m, value)
return

@staticmethod
def onError(e):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
with open('requirements.txt', 'r') as f:
requires = [x.strip() for x in f if x.strip()]

version = '0.7.0'
version = '0.7.1'

setup(
name='kinetic',
Expand Down
8 changes: 4 additions & 4 deletions test/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ def _find_kinetic_jar(jar_path=None):
jar_path = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
# /src/main/python/test_kinetic
'../../../../target',
'Kinetic-0.2.0.1-SNAPSHOT-jar-with-dependencies.jar',
# /kinetic-py/test/.
'../../kinetic-java/kinetic-simulator/target/',
'kinetic-simulator-0.6.0.3-SNAPSHOT-jar-with-dependencies.jar',
)
)
if not os.path.exists(jar_path):
Expand Down Expand Up @@ -98,7 +98,7 @@ class BaseTestCase(unittest.TestCase):
If the .jar is not readily locatable you will get an error and need to
ensure that the KINETIC_JAR environment variable points to the real
path of Kinetic-1.0-SNAPSHOT-jar-with-dependencies.jar.
path for kinetic-simulator.
"""

Expand Down
51 changes: 51 additions & 0 deletions test/test_adminclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (C) 2014 Seagate Technology.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

#@author: Robert Cope

import unittest

from kinetic import Client
from kinetic import AdminClient
from kinetic import KineticMessageException
from base import BaseTestCase
from kinetic import common

class AdminClientTestCase(BaseTestCase):

def setUp(self):
super(AdminClientTestCase, self).setUp()
self.adminClient = AdminClient(self.host, self.port)
self.adminClient.connect()

def tearDown(self):
self.adminClient.close()

def test_setSecurity(self):

self.client.put(self.buildKey(1), "test_value_1")

acl = common.ACL(identity=100)
domain = common.Domain(roles=[common.Roles.READ])
acl.domains = [domain]

self.adminClient.setSecurity([acl])

# Verify user 100 can only read
read_only_client = Client(self.host, self.port, identity=100)
read_only_client.get(self.buildKey(1)) # Should be OK.
args = (self.buildKey(2), 'test_value_2')
self.assertRaises(KineticMessageException, read_only_client.put, *args)
2 changes: 1 addition & 1 deletion test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class KineticBasicTestCase(BaseTestCase):

def setUp(self):
super(KineticBasicTestCase, self).setUp()
self.client = Client("localhost", self.port)
self.client = Client(self.host, self.port)

def test_command_put(self):
self.client.put(self.buildKey(0),"test_value")
Expand Down
6 changes: 3 additions & 3 deletions test/test_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class BaseCommandTestCase(BaseTestCase):
def setUp(self):
super(BaseCommandTestCase, self).setUp()
self.test_key = self.buildKey('test')
self.client = client.Client("localhost", self.port)
self.conn_args = '-H localhost -P %s ' % self.port
self.client = client.Client(self.host, self.port)
self.conn_args = '-H %s -P %s ' % (self.host, self.port)

@contextlib.contextmanager
def capture_stdout(self):
Expand Down Expand Up @@ -159,7 +159,7 @@ def test_list_prefix(self):
# because the cmd output uses text/line based delimiters it's hard to
# reason about keynames with a new line in them in this test
bad_characters = [ord(c) for c in ('\n', '\r')]
keys = [self.test_key + chr(ord_) for ord_ in range(256) if ord_ not
keys = [self.test_key + chr(ord_) for ord_ in range(200) if ord_ not
in bad_characters]
for i, key in enumerate(keys):
self.client.put(key, 'myvalue.%s' % i)
Expand Down
Loading

0 comments on commit daea609

Please sign in to comment.