This project is no longer maintained.

The contents of this repository are still available in the Git
source code management system.  To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".

http://lists.openstack.org/pipermail/openstack-dev/2016-March/090409.html

See I78a16ad052072feba7670aaea144216875ddc0d1 for kite project
removal.

Change-Id: I0461da5039e2357f19a86de3922b86dfabb79b05
Depends-On: If9c42d2cec35b68c4de85b750c8540d86322f3e3
This commit is contained in:
Ronald Bradford 2016-03-25 11:50:18 -04:00
parent dee7b5b07d
commit 1b34b559bb
54 changed files with 6 additions and 3411 deletions

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/python-kiteclient

View File

@ -1,4 +0,0 @@
python-kiteclient Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

175
LICENSE
View File

@ -1,175 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@ -1,15 +1,8 @@
===============================
python-kiteclient
===============================
This project is no longer maintained.
A client library for interacting with the Kite secure message service.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
* Free software: Apache license
* Documentation: http://docs.openstack.org/developer/python-kiteclient
* Source: http://git.openstack.org/cgit/openstack/python-kiteclient
* Bugs: http://bugs.launchpad.net/python-kiteclient
Features
--------
* TODO
http://lists.openstack.org/pipermail/openstack-dev/2016-March/090409.html

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'oslosphinx'
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'python-kiteclient'
copyright = u'2014, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['static']
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}

View File

@ -1 +0,0 @@
.. include:: ../../CONTRIBUTING.rst

View File

@ -1,24 +0,0 @@
.. python-kiteclient documentation master file, created by
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to python-kiteclient's documentation!
========================================================
Contents:
.. toctree::
:maxdepth: 2
readme
installation
usage
contributing
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,12 +0,0 @@
============
Installation
============
At the command line::
$ pip install python-kiteclient
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv python-kiteclient
$ pip install python-kiteclient

View File

@ -1 +0,0 @@
.. include:: ../../README.rst

View File

@ -1,7 +0,0 @@
========
Usage
========
To use python-kiteclient in a project::
import kiteclient

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'kiteclient').version_string()

View File

@ -1,159 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"KDS Client"
import logging
import os
from cliff import command
from cliff import show
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth import token_endpoint
from keystoneclient import session
from openstackclient.common import utils
from kiteclient import v1
LOG = logging.getLogger(__name__)
DEFAULT_KDS_API_VERSION = '1'
API_VERSION_OPTION = 'os_kds_api_version'
API_NAME = 'kds'
API_VERSIONS = {
'1': 'kiteclient.cli.v1.Client'
}
def make_client(instance):
client_class = utils.get_client_class(API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('instantiating KDS client: %s' % client_class)
s = session.Session.construct({'verify': instance._verify,
'cacert': instance._cacert,
'insecure': instance._insecure})
if instance._url:
s.auth = token_endpoint.Token(instance._url, instance._token)
else:
s.auth = v2_auth.Password(instance._auth_url,
instance._username,
instance._password,
tenant_id=instance._project_id,
tenant_name=instance._project_name)
return client_class(s)
def build_option_parser(parser):
parser.add_argument(
'--os-kds-api-version',
metavar='<kds-api-version>',
default=os.getenv('OS_KDS_API_VERSION', DEFAULT_KDS_API_VERSION),
help='KDS API version, default=%s (Env: OS_KDS_API_VERSION)' %
DEFAULT_KDS_API_VERSION)
return parser
class Client(object):
log = logging.getLogger(__name__ + '.Client')
def __init__(self, sess):
self.session = sess
def set_key(self, name, key_data):
if key_data:
key = v1.Key(name, key_data, self.session)
else:
key = v1.Key.generate(name, self.session)
return key.key_name, key.generation, key.b64_key
def create_group(self, name):
# TODO(tkelsey): implement this when KDS group work is done.
self.log.warn("create_group not implemented")
raise NotImplementedError
def delete_group(self, name):
# TODO(tkelsey): implement this when KDS group work is done.
self.log.warn("delete_group not implemented")
raise NotImplementedError
class KeySet(show.ShowOne):
"""Set a messaging symmetric key in the KDS."""
log = logging.getLogger(__name__ + '.KeySet')
columns = ('Name', 'Generation', 'Key')
def get_parser(self, prog_name):
parser = super(KeySet, self).get_parser(prog_name)
parser.add_argument('name',
metavar='<name>',
help='Name of host')
parser.add_argument('key',
nargs='?',
metavar='<key>',
help='Base64 key to set')
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
kds_client = self.app.client_manager.kds
data = kds_client.set_key(parsed_args.name, parsed_args.key)
return self.columns, data
class GroupCreate(show.ShowOne):
"""Create a Group in the KDS."""
log = logging.getLogger(__name__ + '.GroupCreate')
columns = ('Name',)
def get_parser(self, prog_name):
parser = super(GroupCreate, self).get_parser(prog_name)
parser.add_argument('name',
metavar='<name>',
help='Name of the Group to create')
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
kds_client = self.app.client_manager.kds
data = kds_client.create_group(parsed_args.name)
return self.columns, (data, )
class GroupDelete(command.Command):
"""Delete a Group from the KDS."""
log = logging.getLogger(__name__ + '.GroupDelete')
def get_parser(self, prog_name):
parser = super(GroupDelete, self).get_parser(prog_name)
parser.add_argument('name',
metavar='<name>',
help='Name of the Group to delete')
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
kds_client = self.app.client_manager.kds
kds_client.delete_group(parsed_args.name)

View File

@ -1,43 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import struct
from Crypto import Random
import six
from kiteclient.openstack.common import jsonutils
from kiteclient.openstack.common import timeutils
class Metadata(object):
def __init__(self, source, destination, timestamp=None, nonce=None):
self.source = source
self.destination = destination
self.timestamp = timestamp or timeutils.utcnow()
self.nonce = nonce or self.gen_nonce()
@classmethod
def gen_nonce(cls):
return struct.unpack('Q', Random.new().read(8))[0]
def get_data(self):
return {'source': self.source,
'destination': self.destination,
'timestamp': timeutils.strtime(self.timestamp),
'nonce': self.nonce}
def encode(self):
data = self.get_data()
return base64.b64encode(six.b(jsonutils.dumps(data)))

View File

@ -1,57 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from kiteclient.common import utils
class Resource(object):
base_path = None
@classmethod
def _http_request(cls, session, method, path=None, **kwargs):
headers = kwargs.setdefault('headers', {})
headers['Accept'] = 'application/json'
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('service_type', 'kds')
if path:
path = utils.urljoin(cls.base_path, path)
else:
path = cls.base_path
return session.request(path, method, **kwargs)
@classmethod
def _http_get(cls, session, *args, **kwargs):
return cls._http_request(session, 'GET', *args, **kwargs)
@classmethod
def _http_post(cls, session, *args, **kwargs):
return cls._http_request(session, 'POST', *args, **kwargs)
@classmethod
def _http_put(cls, session, *args, **kwargs):
return cls._http_request(session, 'PUT', *args, **kwargs)
@classmethod
def _http_patch(cls, session, *args, **kwargs):
return cls._http_request(session, 'PATCH', *args, **kwargs)
@classmethod
def _http_delete(cls, session, *args, **kwargs):
return cls._http_request(session, 'DELETE', *args, **kwargs)
@classmethod
def _http_head(cls, session, *args, **kwargs):
return cls._http_request(session, 'HEAD', *args, **kwargs)

View File

@ -1,21 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
def urljoin(*args):
"""A custom version of urljoin that simply joins strings into a path.
The real urljoin takes into account web semantics like when joining a url
like /path this should be joined to http://host/path as it is an anchored
link. We generally won't care about that in client.
"""
return '/'.join(str(a).strip('/') for a in args)

View File

@ -1,45 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
"""
try:
import oslo_i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate
# repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message
# catalog.
_translators = oslo_i18n.TranslatorFactory(domain='kiteclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
except ImportError:
# NOTE(dims): Support for cases where a project wants to use
# code from oslo-incubator, but is not ready to be internationalized
# (like tempest)
_ = _LI = _LW = _LE = _LC = lambda x: x

View File

@ -1,197 +0,0 @@
# Copyright 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
########################################################################
#
# THIS MODULE IS DEPRECATED
#
# Please refer to
# https://etherpad.openstack.org/p/kilo-kiteclient-library-proposals for
# the discussion leading to this deprecation.
#
# We recommend checking out Barbican or the cryptography.py project
# (https://pypi.python.org/pypi/cryptography) instead of this module.
#
########################################################################
import base64
from Crypto.Hash import HMAC
from Crypto import Random
from oslo_utils import importutils
import six
from kiteclient.openstack.common._i18n import _
bchr = six.int2byte
class CryptoutilsException(Exception):
"""Generic Exception for Crypto utilities."""
message = _("An unknown error occurred in crypto utils.")
class CipherBlockLengthTooBig(CryptoutilsException):
"""The block size is too big."""
def __init__(self, requested, permitted):
msg = _("Block size of %(given)d is too big, max = %(maximum)d")
message = msg % {'given': requested, 'maximum': permitted}
super(CryptoutilsException, self).__init__(message)
class HKDFOutputLengthTooLong(CryptoutilsException):
"""The amount of Key Material asked is too much."""
def __init__(self, requested, permitted):
msg = _("Length of %(given)d is too long, max = %(maximum)d")
message = msg % {'given': requested, 'maximum': permitted}
super(CryptoutilsException, self).__init__(message)
class HKDF(object):
"""An HMAC-based Key Derivation Function implementation (RFC5869)
This class creates an object that allows to use HKDF to derive keys.
"""
def __init__(self, hashtype='SHA256'):
self.hashfn = importutils.import_module('Crypto.Hash.' + hashtype)
self.max_okm_length = 255 * self.hashfn.digest_size
def extract(self, ikm, salt=None):
"""An extract function that can be used to derive a robust key given
weak Input Key Material (IKM) which could be a password.
Returns a pseudorandom key (of HashLen octets)
:param ikm: input keying material (ex a password)
:param salt: optional salt value (a non-secret random value)
"""
if salt is None:
salt = b'\x00' * self.hashfn.digest_size
return HMAC.new(salt, ikm, self.hashfn).digest()
def expand(self, prk, info, length):
"""An expand function that will return arbitrary length output that can
be used as keys.
Returns a buffer usable as key material.
:param prk: a pseudorandom key of at least HashLen octets
:param info: optional string (can be a zero-length string)
:param length: length of output keying material (<= 255 * HashLen)
"""
if length > self.max_okm_length:
raise HKDFOutputLengthTooLong(length, self.max_okm_length)
N = (length + self.hashfn.digest_size - 1) // self.hashfn.digest_size
okm = b""
tmp = b""
for block in range(1, N + 1):
tmp = HMAC.new(prk, tmp + info + bchr(block), self.hashfn).digest()
okm += tmp
return okm[:length]
MAX_CB_SIZE = 256
class SymmetricCrypto(object):
"""Symmetric Key Crypto object.
This class creates a Symmetric Key Crypto object that can be used
to encrypt, decrypt, or sign arbitrary data.
:param enctype: Encryption Cipher name (default: AES)
:param hashtype: Hash/HMAC type name (default: SHA256)
"""
def __init__(self, enctype='AES', hashtype='SHA256'):
self.cipher = importutils.import_module('Crypto.Cipher.' + enctype)
self.hashfn = importutils.import_module('Crypto.Hash.' + hashtype)
def new_key(self, size):
return Random.new().read(size)
def encrypt(self, key, msg, b64encode=True):
"""Encrypt the provided msg and returns the cyphertext optionally
base64 encoded.
Uses AES-128-CBC with a Random IV by default.
The plaintext is padded to reach blocksize length.
The last byte of the block is the length of the padding.
The length of the padding does not include the length byte itself.
:param key: The Encryption key.
:param msg: the plain text.
:returns enc: a block of encrypted data.
"""
iv = Random.new().read(self.cipher.block_size)
cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv)
# CBC mode requires a fixed block size. Append padding and length of
# padding.
if self.cipher.block_size > MAX_CB_SIZE:
raise CipherBlockLengthTooBig(self.cipher.block_size, MAX_CB_SIZE)
r = len(msg) % self.cipher.block_size
padlen = self.cipher.block_size - r - 1
msg += b'\x00' * padlen
msg += bchr(padlen)
enc = iv + cipher.encrypt(msg)
if b64encode:
enc = base64.b64encode(enc)
return enc
def decrypt(self, key, msg, b64decode=True):
"""Decrypts the provided ciphertext, optionally base64 encoded, and
returns the plaintext message, after padding is removed.
Uses AES-128-CBC with an IV by default.
:param key: The Encryption key.
:param msg: the ciphetext, the first block is the IV
:returns plain: the plaintext message.
"""
if b64decode:
msg = base64.b64decode(msg)
iv = msg[:self.cipher.block_size]
cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv)
padded = cipher.decrypt(msg[self.cipher.block_size:])
l = ord(padded[-1:]) + 1
plain = padded[:-l]
return plain
def sign(self, key, msg, b64encode=True):
"""Signs a message string and returns a base64 encoded signature.
Uses HMAC-SHA-256 by default.
:param key: The Signing key.
:param msg: the message to sign.
:returns out: a base64 encoded signature.
"""
h = HMAC.new(key, msg, self.hashfn)
out = h.digest()
if b64encode:
out = base64.b64encode(out)
return out

View File

@ -1,498 +0,0 @@
# Copyright 2012 Red Hat, Inc.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
gettext for openstack-common modules.
Usual usage in an openstack.common module:
from kiteclient.openstack.common.gettextutils import _
"""
import copy
import functools
import gettext
import locale
from logging import handlers
import os
from babel import localedata
import six
_AVAILABLE_LANGUAGES = {}
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
USE_LAZY = False
class TranslatorFactory(object):
"""Create translator functions
"""
def __init__(self, domain, lazy=False, localedir=None):
"""Establish a set of translation functions for the domain.
:param domain: Name of translation domain,
specifying a message catalog.
:type domain: str
:param lazy: Delays translation until a message is emitted.
Defaults to False.
:type lazy: Boolean
:param localedir: Directory with translation catalogs.
:type localedir: str
"""
self.domain = domain
self.lazy = lazy
if localedir is None:
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
self.localedir = localedir
def _make_translation_func(self, domain=None):
"""Return a new translation function ready for use.
Takes into account whether or not lazy translation is being
done.
The domain can be specified to override the default from the
factory, but the localedir from the factory is always used
because we assume the log-level translation catalogs are
installed in the same directory as the main application
catalog.
"""
if domain is None:
domain = self.domain
if self.lazy:
return functools.partial(Message, domain=domain)
t = gettext.translation(
domain,
localedir=self.localedir,
fallback=True,
)
if six.PY3:
return t.gettext
return t.ugettext
@property
def primary(self):
"The default translation function."
return self._make_translation_func()
def _make_log_translation_func(self, level):
return self._make_translation_func(self.domain + '-log-' + level)
@property
def log_info(self):
"Translate info-level log messages."
return self._make_log_translation_func('info')
@property
def log_warning(self):
"Translate warning-level log messages."
return self._make_log_translation_func('warning')
@property
def log_error(self):
"Translate error-level log messages."
return self._make_log_translation_func('error')
@property
def log_critical(self):
"Translate critical-level log messages."
return self._make_log_translation_func('critical')
# NOTE(dhellmann): When this module moves out of the incubator into
# oslo.i18n, these global variables can be moved to an integration
# module within each application.
# Create the global translation functions.
_translators = TranslatorFactory('kiteclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
# NOTE(dhellmann): End of globals that will move to the application's
# integration module.
def enable_lazy():
"""Convenience function for configuring _() to use lazy gettext
Call this at the start of execution to enable the gettextutils._
function to use lazy gettext functionality. This is useful if
your project is importing _ directly instead of using the
gettextutils.install() way of importing the _ function.
"""
# FIXME(dhellmann): This function will be removed in oslo.i18n,
# because the TranslatorFactory makes it superfluous.
global _, _LI, _LW, _LE, _LC, USE_LAZY
tf = TranslatorFactory('kiteclient', lazy=True)
_ = tf.primary
_LI = tf.log_info
_LW = tf.log_warning
_LE = tf.log_error
_LC = tf.log_critical
USE_LAZY = True
def install(domain, lazy=False):
"""Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's
install() function.
The main difference from gettext.install() is that we allow
overriding the default localedir (e.g. /usr/share/locale) using
a translation-domain-specific environment variable (e.g.
NOVA_LOCALEDIR).
:param domain: the translation domain
:param lazy: indicates whether or not to install the lazy _() function.
The lazy _() introduces a way to do deferred translation
of messages by installing a _ that builds Message objects,
instead of strings, which can then be lazily translated into
any available locale.
"""
if lazy:
from six import moves
tf = TranslatorFactory(domain, lazy=True)
moves.builtins.__dict__['_'] = tf.primary
else:
localedir = '%s_LOCALEDIR' % domain.upper()
if six.PY3:
gettext.install(domain,
localedir=os.environ.get(localedir))
else:
gettext.install(domain,
localedir=os.environ.get(localedir),
unicode=True)
class Message(six.text_type):
"""A Message object is a unicode object that can be translated.
Translation of Message is done explicitly using the translate() method.
For all non-translation intents and purposes, a Message is simply unicode,
and can be treated as such.
"""
def __new__(cls, msgid, msgtext=None, params=None,
domain='kiteclient', *args):
"""Create a new Message object.
In order for translation to work gettext requires a message ID, this
msgid will be used as the base unicode text. It is also possible
for the msgid and the base unicode text to be different by passing
the msgtext parameter.
"""
# If the base msgtext is not given, we use the default translation
# of the msgid (which is in English) just in case the system locale is
# not English, so that the base text will be in that locale by default.
if not msgtext:
msgtext = Message._translate_msgid(msgid, domain)
# We want to initialize the parent unicode with the actual object that
# would have been plain unicode if 'Message' was not enabled.
msg = super(Message, cls).__new__(cls, msgtext)
msg.msgid = msgid
msg.domain = domain
msg.params = params
return msg
def translate(self, desired_locale=None):
"""Translate this message to the desired locale.
:param desired_locale: The desired locale to translate the message to,
if no locale is provided the message will be
translated to the system's default locale.
:returns: the translated message in unicode
"""
translated_message = Message._translate_msgid(self.msgid,
self.domain,
desired_locale)
if self.params is None:
# No need for more translation
return translated_message
# This Message object may have been formatted with one or more
# Message objects as substitution arguments, given either as a single
# argument, part of a tuple, or as one or more values in a dictionary.
# When translating this Message we need to translate those Messages too
translated_params = _translate_args(self.params, desired_locale)
translated_message = translated_message % translated_params
return translated_message
@staticmethod
def _translate_msgid(msgid, domain, desired_locale=None):
if not desired_locale:
system_locale = locale.getdefaultlocale()
# If the system locale is not available to the runtime use English
if not system_locale[0]:
desired_locale = 'en_US'
else:
desired_locale = system_locale[0]
locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
lang = gettext.translation(domain,
localedir=locale_dir,
languages=[desired_locale],
fallback=True)
if six.PY3:
translator = lang.gettext
else:
translator = lang.ugettext
translated_message = translator(msgid)
return translated_message
def __mod__(self, other):
# When we mod a Message we want the actual operation to be performed
# by the parent class (i.e. unicode()), the only thing we do here is
# save the original msgid and the parameters in case of a translation
params = self._sanitize_mod_params(other)
unicode_mod = super(Message, self).__mod__(params)
modded = Message(self.msgid,
msgtext=unicode_mod,
params=params,
domain=self.domain)
return modded
def _sanitize_mod_params(self, other):
"""Sanitize the object being modded with this Message.
- Add support for modding 'None' so translation supports it
- Trim the modded object, which can be a large dictionary, to only
those keys that would actually be used in a translation
- Snapshot the object being modded, in case the message is
translated, it will be used as it was when the Message was created
"""
if other is None:
params = (other,)
elif isinstance(other, dict):
# Merge the dictionaries
# Copy each item in case one does not support deep copy.
params = {}
if isinstance(self.params, dict):
for key, val in self.params.items():
params[key] = self._copy_param(val)
for key, val in other.items():
params[key] = self._copy_param(val)
else:
params = self._copy_param(other)
return params
def _copy_param(self, param):
try:
return copy.deepcopy(param)
except Exception:
# Fallback to casting to unicode this will handle the
# python code-like objects that can't be deep-copied
return six.text_type(param)
def __add__(self, other):
msg = _('Message objects do not support addition.')
raise TypeError(msg)
def __radd__(self, other):
return self.__add__(other)
if six.PY2:
def __str__(self):
# NOTE(luisg): Logging in python 2.6 tries to str() log records,
# and it expects specifically a UnicodeError in order to proceed.
msg = _('Message objects do not support str() because they may '
'contain non-ascii characters. '
'Please use unicode() or translate() instead.')
raise UnicodeError(msg)
def get_available_languages(domain):
"""Lists the available languages for the given translation domain.
:param domain: the domain to get languages for
"""
if domain in _AVAILABLE_LANGUAGES:
return copy.copy(_AVAILABLE_LANGUAGES[domain])
localedir = '%s_LOCALEDIR' % domain.upper()
find = lambda x: gettext.find(domain,
localedir=os.environ.get(localedir),
languages=[x])
# NOTE(mrodden): en_US should always be available (and first in case
# order matters) since our in-line message strings are en_US
language_list = ['en_US']
# NOTE(luisg): Babel <1.0 used a function called list(), which was
# renamed to locale_identifiers() in >=1.0, the requirements master list
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
# this check when the master list updates to >=1.0, and update all projects
list_identifiers = (getattr(localedata, 'list', None) or
getattr(localedata, 'locale_identifiers'))
locale_identifiers = list_identifiers()
for i in locale_identifiers:
if find(i) is not None:
language_list.append(i)
# NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
# locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
# are perfectly legitimate locales:
# https://github.com/mitsuhiko/babel/issues/37
# In Babel 1.3 they fixed the bug and they support these locales, but
# they are still not explicitly "listed" by locale_identifiers().
# That is why we add the locales here explicitly if necessary so that
# they are listed as supported.
aliases = {'zh': 'zh_CN',
'zh_Hant_HK': 'zh_HK',
'zh_Hant': 'zh_TW',
'fil': 'tl_PH'}
for (locale_, alias) in six.iteritems(aliases):
if locale_ in language_list and alias not in language_list:
language_list.append(alias)
_AVAILABLE_LANGUAGES[domain] = language_list
return copy.copy(language_list)
def translate(obj, desired_locale=None):
"""Gets the translated unicode representation of the given object.
If the object is not translatable it is returned as-is.
If the locale is None the object is translated to the system locale.
:param obj: the object to translate
:param desired_locale: the locale to translate the message to, if None the
default system locale will be used
:returns: the translated object in unicode, or the original object if
it could not be translated
"""
message = obj
if not isinstance(message, Message):
# If the object to translate is not already translatable,
# let's first get its unicode representation
message = six.text_type(obj)
if isinstance(message, Message):
# Even after unicoding() we still need to check if we are
# running with translatable unicode before translating
return message.translate(desired_locale)
return obj
def _translate_args(args, desired_locale=None):
"""Translates all the translatable elements of the given arguments object.
This method is used for translating the translatable values in method
arguments which include values of tuples or dictionaries.
If the object is not a tuple or a dictionary the object itself is
translated if it is translatable.
If the locale is None the object is translated to the system locale.
:param args: the args to translate
:param desired_locale: the locale to translate the args to, if None the
default system locale will be used
:returns: a new args object with the translated contents of the original
"""
if isinstance(args, tuple):
return tuple(translate(v, desired_locale) for v in args)
if isinstance(args, dict):
translated_dict = {}
for (k, v) in six.iteritems(args):
translated_v = translate(v, desired_locale)
translated_dict[k] = translated_v
return translated_dict
return translate(args, desired_locale)
class TranslationHandler(handlers.MemoryHandler):
"""Handler that translates records before logging them.
The TranslationHandler takes a locale and a target logging.Handler object
to forward LogRecord objects to after translating them. This handler
depends on Message objects being logged, instead of regular strings.
The handler can be configured declaratively in the logging.conf as follows:
[handlers]
keys = translatedlog, translator
[handler_translatedlog]
class = handlers.WatchedFileHandler
args = ('/var/log/api-localized.log',)
formatter = context
[handler_translator]
class = openstack.common.log.TranslationHandler
target = translatedlog
args = ('zh_CN',)
If the specified locale is not available in the system, the handler will
log in the default locale.
"""
def __init__(self, locale=None, target=None):
"""Initialize a TranslationHandler
:param locale: locale to use for translating messages
:param target: logging.Handler object to forward
LogRecord objects to after translation
"""
# NOTE(luisg): In order to allow this handler to be a wrapper for
# other handlers, such as a FileHandler, and still be able to
# configure it using logging.conf, this handler has to extend
# MemoryHandler because only the MemoryHandlers' logging.conf
# parsing is implemented such that it accepts a target handler.
handlers.MemoryHandler.__init__(self, capacity=0, target=target)
self.locale = locale
def setFormatter(self, fmt):
self.target.setFormatter(fmt)
def emit(self, record):
# We save the message from the original record to restore it
# after translation, so other handlers are not affected by this
original_msg = record.msg
original_args = record.args
try:
self._translate_and_log_record(record)
finally:
record.msg = original_msg
record.args = original_args
def _translate_and_log_record(self, record):
record.msg = translate(record.msg, self.locale)
# In addition to translating the message, we also need to translate
# arguments that were passed to the log method that were not part
# of the main message e.g., log.info(_('Some message %s'), this_one))
record.args = _translate_args(record.args, self.locale)
self.target.emit(record)

View File

@ -1,73 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Import related utilities and helper functions.
"""
import sys
import traceback
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
__import__(mod_str)
try:
return getattr(sys.modules[mod_str], class_str)
except AttributeError:
raise ImportError('Class %s cannot be found (%s)' %
(class_str,
traceback.format_exception(*sys.exc_info())))
def import_object(import_str, *args, **kwargs):
"""Import a class and return an instance of it."""
return import_class(import_str)(*args, **kwargs)
def import_object_ns(name_space, import_str, *args, **kwargs):
"""Tries to import object from default namespace.
Imports a class and return an instance of it, first by trying
to find the class in a default namespace, then failing back to
a full path if not found in the default namespace.
"""
import_value = "%s.%s" % (name_space, import_str)
try:
return import_class(import_value)(*args, **kwargs)
except ImportError:
return import_class(import_str)(*args, **kwargs)
def import_module(import_str):
"""Import a module."""
__import__(import_str)
return sys.modules[import_str]
def import_versioned_module(version, submodule=None):
module = 'kiteclient.v%s' % version
if submodule:
module = '.'.join((module, submodule))
return import_module(module)
def try_import(import_str, default=None):
"""Try to import a module and if it fails return default."""
try:
return import_module(import_str)
except ImportError:
return default

View File

@ -1,186 +0,0 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
'''
JSON related utilities.
This module provides a few things:
1) A handy function for getting an object down to something that can be
JSON serialized. See to_primitive().
2) Wrappers around loads() and dumps(). The dumps() wrapper will
automatically use to_primitive() for you if needed.
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
is available.
'''
import codecs
import datetime
import functools
import inspect
import itertools
import sys
if sys.version_info < (2, 7):
# On Python <= 2.6, json module is not C boosted, so try to use
# simplejson module if available
try:
import simplejson as json
except ImportError:
import json
else:
import json
import six
import six.moves.xmlrpc_client as xmlrpclib
from kiteclient.openstack.common import gettextutils
from kiteclient.openstack.common import importutils
from kiteclient.openstack.common import strutils
from kiteclient.openstack.common import timeutils
netaddr = importutils.try_import("netaddr")
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
inspect.isfunction, inspect.isgeneratorfunction,
inspect.isgenerator, inspect.istraceback, inspect.isframe,
inspect.iscode, inspect.isbuiltin, inspect.isroutine,
inspect.isabstract]
_simple_types = (six.string_types + six.integer_types
+ (type(None), bool, float))
def to_primitive(value, convert_instances=False, convert_datetime=True,
level=0, max_depth=3):
"""Convert a complex object into primitives.
Handy for JSON serialization. We can optionally handle instances,
but since this is a recursive function, we could have cyclical
data structures.
To handle cyclical data structures we could track the actual objects
visited in a set, but not all objects are hashable. Instead we just
track the depth of the object inspections and don't go too deep.
Therefore, convert_instances=True is lossy ... be aware.
"""
# handle obvious types first - order of basic types determined by running
# full tests on nova project, resulting in the following counts:
# 572754 <type 'NoneType'>
# 460353 <type 'int'>
# 379632 <type 'unicode'>
# 274610 <type 'str'>
# 199918 <type 'dict'>
# 114200 <type 'datetime.datetime'>
# 51817 <type 'bool'>
# 26164 <type 'list'>
# 6491 <type 'float'>
# 283 <type 'tuple'>
# 19 <type 'long'>
if isinstance(value, _simple_types):
return value
if isinstance(value, datetime.datetime):
if convert_datetime:
return timeutils.strtime(value)
else:
return value
# value of itertools.count doesn't get caught by nasty_type_tests
# and results in infinite loop when list(value) is called.
if type(value) == itertools.count:
return six.text_type(value)
# FIXME(vish): Workaround for LP bug 852095. Without this workaround,
# tests that raise an exception in a mocked method that
# has a @wrap_exception with a notifier will fail. If
# we up the dependency to 0.5.4 (when it is released) we
# can remove this workaround.
if getattr(value, '__module__', None) == 'mox':
return 'mock'
if level > max_depth:
return '?'
# The try block may not be necessary after the class check above,
# but just in case ...
try:
recursive = functools.partial(to_primitive,
convert_instances=convert_instances,
convert_datetime=convert_datetime,
level=level,
max_depth=max_depth)
if isinstance(value, dict):
return dict((k, recursive(v)) for k, v in six.iteritems(value))
elif isinstance(value, (list, tuple)):
return [recursive(lv) for lv in value]
# It's not clear why xmlrpclib created their own DateTime type, but
# for our purposes, make it a datetime type which is explicitly
# handled
if isinstance(value, xmlrpclib.DateTime):
value = datetime.datetime(*tuple(value.timetuple())[:6])
if convert_datetime and isinstance(value, datetime.datetime):
return timeutils.strtime(value)
elif isinstance(value, gettextutils.Message):
return value.data
elif hasattr(value, 'iteritems'):
return recursive(dict(value.iteritems()), level=level + 1)
elif hasattr(value, '__iter__'):
return recursive(list(value))
elif convert_instances and hasattr(value, '__dict__'):
# Likely an instance of something. Watch for cycles.
# Ignore class member vars.
return recursive(value.__dict__, level=level + 1)
elif netaddr and isinstance(value, netaddr.IPAddress):
return six.text_type(value)
else:
if any(test(value) for test in _nasty_type_tests):
return six.text_type(value)
return value
except TypeError:
# Class objects are tricky since they may define something like
# __iter__ defined but it isn't callable as list().
return six.text_type(value)
def dumps(value, default=to_primitive, **kwargs):
return json.dumps(value, default=default, **kwargs)
def loads(s, encoding='utf-8', **kwargs):
return json.loads(strutils.safe_decode(s, encoding), **kwargs)
def load(fp, encoding='utf-8', **kwargs):
return json.load(codecs.getreader(encoding)(fp), **kwargs)
try:
import anyjson
except ImportError:
pass
else:
anyjson._modules.append((__name__, 'dumps', TypeError,
'loads', ValueError, 'load'))
anyjson.force_implementation(__name__)

View File

@ -1,239 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
System-level utilities and helper functions.
"""
import math
import re
import sys
import unicodedata
import six
from kiteclient.openstack.common.gettextutils import _
UNIT_PREFIX_EXPONENT = {
'k': 1,
'K': 1,
'Ki': 1,
'M': 2,
'Mi': 2,
'G': 3,
'Gi': 3,
'T': 4,
'Ti': 4,
}
UNIT_SYSTEM_INFO = {
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
}
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
def int_from_bool_as_string(subject):
"""Interpret a string as a boolean and return either 1 or 0.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
return bool_from_string(subject) and 1 or 0
def bool_from_string(subject, strict=False, default=False):
"""Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when
`strict=False`, anything else returns the value specified by 'default'.
Useful for JSON-decoded stuff and config file parsing.
If `strict=True`, unrecognized values, including None, will raise a
ValueError which is useful when parsing values passed in from an API call.
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
"""
if not isinstance(subject, six.string_types):
subject = six.text_type(subject)
lowered = subject.strip().lower()
if lowered in TRUE_STRINGS:
return True
elif lowered in FALSE_STRINGS:
return False
elif strict:
acceptable = ', '.join(
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
msg = _("Unrecognized value '%(val)s', acceptable values are:"
" %(acceptable)s") % {'val': subject,
'acceptable': acceptable}
raise ValueError(msg)
else:
return default
def safe_decode(text, incoming=None, errors='strict'):
"""Decodes incoming text/bytes string using `incoming` if they're not
already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
return text
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
try:
return text.decode(incoming, errors)
except UnicodeDecodeError:
# Note(flaper87) If we get here, it means that
# sys.stdin.encoding / sys.getdefaultencoding
# didn't return a suitable encoding to decode
# text. This happens mostly when global LANG
# var is not set correctly and there's no
# default encoding. In this case, most likely
# python will use ASCII or ANSI encoders as
# default encodings but they won't be capable
# of decoding non-ASCII characters.
#
# Also, UTF-8 is being used since it's an ASCII
# extension.
return text.decode('utf-8', errors)
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
"""Encodes incoming text/bytes string using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
:param incoming: Text's current encoding
:param encoding: Expected encoding for text (Default UTF-8)
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be encoded" % type(text))
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
if isinstance(text, six.text_type):
return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
return text.encode(encoding, errors)
else:
return text
def string_to_bytes(text, unit_system='IEC', return_int=False):
"""Converts a string into an float representation of bytes.
The units supported for IEC ::
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
KB, KiB, MB, MiB, GB, GiB, TB, TiB
The units supported for SI ::
kb(it), Mb(it), Gb(it), Tb(it)
kB, MB, GB, TB
Note that the SI unit system does not support capital letter 'K'
:param text: String input for bytes size conversion.
:param unit_system: Unit system for byte size conversion.
:param return_int: If True, returns integer representation of text
in bytes. (default: decimal)
:returns: Numerical representation of text in bytes.
:raises ValueError: If text has an invalid value.
"""
try:
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
except KeyError:
msg = _('Invalid unit system: "%s"') % unit_system
raise ValueError(msg)
match = reg_ex.match(text)
if match:
magnitude = float(match.group(1))
unit_prefix = match.group(2)
if match.group(3) in ['b', 'bit']:
magnitude /= 8
else:
msg = _('Invalid string format: %s') % text
raise ValueError(msg)
if not unit_prefix:
res = magnitude
else:
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
if return_int:
return int(math.ceil(res))
return res
def to_slug(value, incoming=None, errors="strict"):
"""Normalize string.
Convert to lowercase, remove non-word characters, and convert spaces
to hyphens.
Inspired by Django's `slugify` filter.
:param value: Text to slugify
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: slugified unicode representation of `value`
:raises TypeError: If text is not an instance of str
"""
value = safe_decode(value, incoming, errors)
# NOTE(aababilov): no need to use safe_(encode|decode) here:
# encodings are always "ascii", error handling is always "ignore"
# and types are always known (first: unicode; second: str)
value = unicodedata.normalize("NFKD", value).encode(
"ascii", "ignore").decode("ascii")
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
return SLUGIFY_HYPHENATE_RE.sub("-", value)

View File

@ -1,210 +0,0 @@
# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Time related utilities and helper functions.
"""
import calendar
import datetime
import time
import iso8601
import six
# ISO 8601 extended time format with microseconds
_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
def isotime(at=None, subsecond=False):
"""Stringify time in ISO 8601 format."""
if not at:
at = utcnow()
st = at.strftime(_ISO8601_TIME_FORMAT
if not subsecond
else _ISO8601_TIME_FORMAT_SUBSECOND)
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
st += ('Z' if tz == 'UTC' else tz)
return st
def parse_isotime(timestr):
"""Parse time from ISO 8601 format."""
try:
return iso8601.parse_date(timestr)
except iso8601.ParseError as e:
raise ValueError(six.text_type(e))
except TypeError as e:
raise ValueError(six.text_type(e))
def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
"""Returns formatted utcnow."""
if not at:
at = utcnow()
return at.strftime(fmt)
def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
"""Turn a formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, fmt)
def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset()
if offset is None:
return timestamp
return timestamp.replace(tzinfo=None) - offset
def is_older_than(before, seconds):
"""Return True if before is older than seconds."""
if isinstance(before, six.string_types):
before = parse_strtime(before).replace(tzinfo=None)
else:
before = before.replace(tzinfo=None)
return utcnow() - before > datetime.timedelta(seconds=seconds)
def is_newer_than(after, seconds):
"""Return True if after is newer than seconds."""
if isinstance(after, six.string_types):
after = parse_strtime(after).replace(tzinfo=None)
else:
after = after.replace(tzinfo=None)
return after - utcnow() > datetime.timedelta(seconds=seconds)
def utcnow_ts():
"""Timestamp version of our utcnow function."""
if utcnow.override_time is None:
# NOTE(kgriffs): This is several times faster
# than going through calendar.timegm(...)
return int(time.time())
return calendar.timegm(utcnow().timetuple())
def utcnow():
"""Overridable version of utils.utcnow."""
if utcnow.override_time:
try:
return utcnow.override_time.pop(0)
except AttributeError:
return utcnow.override_time
return datetime.datetime.utcnow()
def iso8601_from_timestamp(timestamp):
"""Returns an iso8601 formatted date from timestamp."""
return isotime(datetime.datetime.utcfromtimestamp(timestamp))
utcnow.override_time = None
def set_time_override(override_time=None):
"""Overrides utils.utcnow.
Make it return a constant time or a list thereof, one at a time.
:param override_time: datetime instance or list thereof. If not
given, defaults to the current UTC time.
"""
utcnow.override_time = override_time or datetime.datetime.utcnow()
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
assert utcnow.override_time is not None
try:
for dt in utcnow.override_time:
dt += timedelta
except TypeError:
utcnow.override_time += timedelta
def advance_time_seconds(seconds):
"""Advance overridden time by seconds."""
advance_time_delta(datetime.timedelta(0, seconds))
def clear_time_override():
"""Remove the overridden time."""
utcnow.override_time = None
def marshall_now(now=None):
"""Make an rpc-safe datetime with microseconds.
Note: tzinfo is stripped, but not required for relative times.
"""
if not now:
now = utcnow()
return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
minute=now.minute, second=now.second,
microsecond=now.microsecond)
def unmarshall_time(tyme):
"""Unmarshall a datetime dict."""
return datetime.datetime(day=tyme['day'],
month=tyme['month'],
year=tyme['year'],
hour=tyme['hour'],
minute=tyme['minute'],
second=tyme['second'],
microsecond=tyme['microsecond'])
def delta_seconds(before, after):
"""Return the difference between two timing objects.
Compute the difference in seconds between two date, time, or
datetime objects (as a float, to microsecond resolution).
"""
delta = after - before
return total_seconds(delta)
def total_seconds(delta):
"""Return the total seconds of datetime.timedelta object.
Compute total seconds of datetime.timedelta, datetime.timedelta
doesn't have method total_seconds in Python2.6, calculate it manually.
"""
try:
return delta.total_seconds()
except AttributeError:
return ((delta.days * 24 * 3600) + delta.seconds +
float(delta.microseconds) / (10 ** 6))
def is_soon(dt, window):
"""Determines if time is going to happen in the next window seconds.
:param dt: the time
:param window: minimum seconds to remain to consider the time not soon
:return: True if expiration is within the given duration
"""
soon = (utcnow() + datetime.timedelta(seconds=window))
return normalize_time(dt) <= soon

View File

@ -1,48 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import fixtures
import testtools
_TRUE_VALUES = ('True', 'true', '1', 'yes')
class TestCase(testtools.TestCase):
"""Test case base class for all unit tests."""
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
test_timeout = int(test_timeout)
except ValueError:
# If timeout value is invalid do not set a timeout.
test_timeout = 0
if test_timeout > 0:
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
self.useFixture(fixtures.NestedTempfile())
self.useFixture(fixtures.TempHomeDir())
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
self.log_fixture = self.useFixture(fixtures.FakeLogger())

View File

@ -1,26 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_kiteclient
----------------------------------
Tests for `kiteclient` module.
"""
from kiteclient.tests import base
class TestKiteclient(base.TestCase):
def test_something(self):
pass

View File

@ -1,86 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_esek
----------------------------------
Tests for `esek` module.
"""
from kiteclient.openstack.common.crypto import utils as cryptoutils
from kiteclient.tests import base
from kiteclient.tests.v1 import utils
from kiteclient.v1 import esek
from kiteclient.v1 import key
import base64
import six
class TestEsek(base.TestCase):
def setUp(self):
super(base.TestCase, self).setUp()
key_ses = utils.DummyKeyResponse(gen=20)
skey_data = "gTqLlW7x2oyNi3k+9YXTpQ=="
self.srckey = key.Key('testkey', skey_data, session=key_ses)
dkey_data = "uoUUn/+ZL+hNUwJ0cxTScg=="
self.dstkey = key.Key('destkey', dkey_data, session=key_ses)
self.skey = "uZnhYaRtzA7QdnDN1hVSWw=="
self.ekey = "fAlG9eGL44ew6q8uTMMKJw=="
self.esek_data = (
"LZ6WWNvCot49sEhnwn0Is/xGWYGQF72rCw8emEKHGmZpDcSQ4K0c5Ld0+fmR"
"T8PjzozEzWK97gNJQHZWSAh1JhmvMO+bjkUNlEdepOjTXrIW6QxdNvMY+Bkd"
"dDwrkKga4wZnoGgeMgK+B7cdGsQ8yAPE3vDjbpmIOvHjHXniCUs=")
def _encrypt(self, data):
crypto = cryptoutils.SymmetricCrypto(enctype='AES',
hashtype='SHA256')
enc = crypto.encrypt(base64.b64decode(self.ekey),
six.b(data), b64encode=True)
sig = crypto.sign(base64.b64decode(self.skey),
six.b(data), b64encode=True)
return enc, sig
def test_integrity(self):
esek_obj = esek.Esek(self.srckey.key_name,
self.dstkey,
self.esek_data)
b64_sig_key = base64.b64encode(esek_obj.sig_key)
b64_enc_key = base64.b64encode(esek_obj.enc_key)
self.assertEqual(six.b(self.skey), b64_sig_key)
self.assertEqual(six.b(self.ekey), b64_enc_key)
def test_decryption(self):
esek_obj = esek.Esek(self.srckey.key_name,
self.dstkey,
self.esek_data)
message = "MESSAGE"
enc, sig = self._encrypt(message)
new_message = esek_obj.decrypt(enc, sig)
self.assertEqual(six.b(message), new_message)
def test_bad_signature_throws(self):
esek_obj = esek.Esek(self.srckey.key_name,
self.dstkey,
self.esek_data)
message = "MESSAGE"
enc, _ = self._encrypt(message)
sig = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
self.assertRaises(ValueError, esek_obj.decrypt, enc, sig)

View File

@ -1,81 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_group
----------------------------------
Tests for `group` module.
"""
from kiteclient.tests import base
from kiteclient.tests.v1 import utils
from kiteclient.v1 import group
from kiteclient.v1 import key
import base64
import datetime
import six
class TestTicket(base.TestCase):
def setUp(self):
super(base.TestCase, self).setUp()
self.nonce = 1062304930675487541
self.stamp = datetime.datetime(2014, 11, 2, 8, 19, 4, 333230)
key_ses = utils.DummyKeyResponse(gen=12)
skey_data = "mIu9E64f0FyJo/BfNCRVGw=="
self.srckey = key.Key('testkeyA', skey_data, session=key_ses)
self.gkey_signature = (
"MD2/BRMAlaabYugGkaLXeqrs6RDnKfoDhcJ/2Ty6xco=")
self.gkey_metadata = (
"eyJzb3VyY2UiOiAidGVzdGdyb3VwLnRlc3RrZXlBOjEyIiwgImRlc3Rpbm"
"F0aW9uIjogInRlc3Rncm91cDo0IiwgImV4cGlyYXRpb24iOiAiMjAxNC0w"
"OS0xMlQxMzowMjo1Mi44NTk2MTYiLCAiZW5jcnlwdGlvbiI6IHRydWV9")
self.group_key = (
"bar6L/o/otlfs+X36x6QW2UZVwAMx/fBOFZhdjB8PtkhZwVd2Z/t7GJ4Ki"
"yB2lgP")
self.dummy_session = utils.DummyGroupKeyResponse(self.gkey_signature,
self.gkey_metadata,
self.group_key)
def test_integrity(self):
gkey = group.GroupKey(self.srckey,
"testgroup",
timestamp=self.stamp,
nonce=self.nonce,
session=self.dummy_session)
# decrypted group key
group_key = six.b("Z6D3jvXWG2Ybg15EfamkEQ==")
self.assertEqual(group_key, gkey.b64_key)
self.assertEqual(gkey.b64_key, base64.b64encode(gkey.key))
def test_bad_signature_throws(self):
bad_signature = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
bad_session = utils.DummyGroupKeyResponse(bad_signature,
self.gkey_metadata,
self.group_key)
self.assertRaises(ValueError,
group.GroupKey,
self.srckey,
"testgroup",
session=bad_session)

View File

@ -1,86 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_key
----------------------------------
Tests for `key` module.
"""
from kiteclient.tests import base
from kiteclient.tests.v1 import utils
from kiteclient.v1 import key
import base64
import six
class TestKey(base.TestCase):
def setUp(self):
super(base.TestCase, self).setUp()
self.dummy_session = utils.DummyKeyResponse(gen=3)
self.test_key_name = "testkey"
self.test_key_b64key = "uoUUn/+ZL+hNUwJ0cxTScg=="
def test_createkey_with_session(self):
srckey = key.Key(self.test_key_name,
self.test_key_b64key,
session=self.dummy_session)
key_id = "%s:%s" % (self.test_key_name,
self.dummy_session.generation)
self.assertEqual(key_id, srckey.key_name)
self.assertEqual(six.b("uoUUn/+ZL+hNUwJ0cxTScg=="),
base64.b64encode(srckey.key))
def test_createkey_with_generation(self):
srckey = key.Key(self.test_key_name,
self.test_key_b64key,
generation=self.dummy_session.generation)
key_id = "%s:%s" % (self.test_key_name,
self.dummy_session.generation)
self.assertEqual(key_id, srckey.key_name)
self.assertEqual(six.b("uoUUn/+ZL+hNUwJ0cxTScg=="),
base64.b64encode(srckey.key))
def test_bad_key_generation(self):
self.assertRaises(RuntimeError,
key.Key,
self.test_key_name,
self.test_key_b64key,
generation=1,
session=self.dummy_session)
def test_encryption_roundtrip(self):
srckey = key.Key(self.test_key_name,
self.test_key_b64key,
session=self.dummy_session)
plain_text = six.b("MESSAGE")
crypt_text = srckey.encrypt(plain_text)
self.assertNotEqual(plain_text, crypt_text)
self.assertEqual(plain_text, srckey.decrypt(crypt_text))
def test_generate(self):
srckey = key.Key.generate(self.test_key_name,
self.dummy_session)
key_id = "%s:%s" % (self.test_key_name,
self.dummy_session.generation)
self.assertEqual(key_id, srckey.key_name)
self.assertNotEqual(None, srckey.key)

View File

@ -1,108 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_ticket
----------------------------------
Tests for `ticket` module.
"""
from kiteclient.tests import base
from kiteclient.tests.v1 import utils
from kiteclient.v1 import esek
from kiteclient.v1 import key
from kiteclient.v1 import ticket
import base64
import datetime
import six
class TestTicket(base.TestCase):
def setUp(self):
super(base.TestCase, self).setUp()
self.nonce = 1062304930675487541
self.stamp = datetime.datetime(2014, 7, 2, 8, 19, 4, 333230)
key_ses = utils.DummyKeyResponse(gen=20)
skey_data = "gTqLlW7x2oyNi3k+9YXTpQ=="
self.srckey = key.Key('testkey', skey_data, session=key_ses)
dkey_data = "uoUUn/+ZL+hNUwJ0cxTScg=="
self.dstkey = key.Key('destkey', dkey_data, session=key_ses)
self.tick_signature = (
"4cj/JRJmvO9L2UlHu1SBOXIofwu3FEFCILTsznwIkd8=")
self.tick_metadata = (
"eyJzb3VyY2UiOiAidGVzdGtleToyMCIsICJkZXN0aW5hdGlvbiI6ICJkZX"
"N0a2V5OjIwIiwgImV4cGlyYXRpb24iOiAiMjAxNC0wNy0wMlQwOTo1Njoz"
"NC4zNDI1NTYiLCAiZW5jcnlwdGlvbiI6IHRydWV9")
self.tick_ticket = (
"tuKo7NlhkDFyL7YO9mPgtBe4cbJF9TIJfDcPmkCkeo1tee0p8pF2h3wYwn"
"uC1wlt58lPc5don3ov9h16ncZh8PlIVT9JwSxg1o2tQLcByiTQ+PIqiMBp"
"7uM0E4RZsPlED7f89gV/fIdz03OGPjh9oiN+yxRFL2UdMN8VKJVjFdkuNm"
"zpmqbpmxKnkB4vueI3mokdfd3mr1xkYRrw/+L1xseayktgJX0ablgTMpvs"
"e4V/ssbvZFON/dZMXRrQ4xxWxEI2mVMVq9WkkEPzGaPySZ2TKtWUS/QcFi"
"OTNbP0hMwx2UuIjrUOX1V3cBUs8u/rJ8LCs8yRIT2xds6aLgPz8c9Z+bNO"
"6U0LGkDRfKzaV79z+yjZXMbU+m2WYLIY2Cat")
self.dummy_session = utils.DummyTicketResponse(self.tick_signature,
self.tick_metadata,
self.tick_ticket)
def test_integrity(self):
tick = ticket.Ticket(self.srckey,
self.dstkey.name,
timestamp=self.stamp,
nonce=self.nonce,
session=self.dummy_session)
good_skey = six.b("uZnhYaRtzA7QdnDN1hVSWw==")
good_ekey = six.b("fAlG9eGL44ew6q8uTMMKJw==")
self.assertEqual(good_ekey, tick.b64_ekey)
self.assertEqual(good_skey, tick.b64_skey)
self.assertEqual(tick.b64_skey, base64.b64encode(tick.skey))
self.assertEqual(tick.b64_ekey, base64.b64encode(tick.ekey))
def test_bad_signature_throws(self):
bad_signature = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
bad_session = utils.DummyTicketResponse(bad_signature,
self.tick_metadata,
self.tick_ticket)
self.assertRaises(ValueError,
ticket.Ticket,
self.srckey,
self.dstkey.name,
session=bad_session)
def test_encryption_roundtrip(self):
tick = ticket.Ticket(self.srckey,
self.dstkey.name,
timestamp=self.stamp,
nonce=self.nonce,
session=self.dummy_session)
message = six.b("MESSAGE")
enc, sig = tick.encrypt(message, b64encode=True)
tick_esek = esek.Esek(self.srckey.key_name,
self.dstkey,
tick.b64_esek)
new_message = tick_esek.decrypt(enc, sig, b64decode=True)
self.assertEqual(message,new_message)

View File

@ -1,69 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class DummyKeyResponse(object):
def __init__(self, gen=1):
self.generation = gen
self.name = ""
def request(self, path, method, **kwargs):
self.name = path.split('/')[-1]
return self
def json(self):
return {"generation": self.generation,
"name": self.name}
class DummyTicketResponse(object):
def __init__(self, signature, metadata, ticket):
self.signature = signature
self.metadata = metadata
self.ticket = ticket
def request(self, path, method, **kwargs):
return self
def json(self):
return {"signature": self.signature,
"metadata": self.metadata,
"ticket": self.ticket}
class DummyGroupResponse(object):
def __init__(self, name):
self.name = name
def request(self, path, method, **kwargs):
return self
def json(self):
return {"name": self.name}
class DummyGroupKeyResponse(object):
def __init__(self, signature, metadata, group_key):
self.signature = signature
self.metadata = metadata
self.group_key = group_key
def request(self, path, method, **kwargs):
return self
def json(self):
return {"signature": self.signature,
"metadata": self.metadata,
"group_key": self.group_key}

View File

@ -1,24 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from kiteclient.v1.esek import Esek
from kiteclient.v1.group import Group
from kiteclient.v1.key import Key
from kiteclient.v1.ticket import Ticket
__all__ = ['Esek',
'Key',
'Ticket',
'Group',
]

View File

@ -1,57 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import six
from kiteclient.openstack.common.crypto import utils as cryptoutils
from kiteclient.openstack.common import jsonutils
from kiteclient.openstack.common import timeutils
class Esek(object):
def __init__(self, source, destination, b64_data,
hashtype='SHA256', key_size=16):
data = jsonutils.loads(destination.decrypt(b64_data, b64decode=True))
base_key = base64.b64decode(data['key'])
key_info = '%s,%s,%s' % (source,
destination.key_name,
data['timestamp'])
crypto = cryptoutils.HKDF(hashtype=hashtype)
key_data = crypto.expand(base_key, six.b(key_info), key_size * 2)
self.sig_key = key_data[:key_size]
self.enc_key = key_data[key_size:]
# TODO(jamielennox): timestamp validate
self.timestamp = timeutils.parse_strtime(data['timestamp'])
def decrypt(self, encrypted, signature, b64decode=True,
enctype='AES', hashtype='SHA256'):
crypto = cryptoutils.SymmetricCrypto(enctype=enctype,
hashtype=hashtype)
data = crypto.decrypt(self.enc_key, encrypted, b64decode=b64decode)
# b64encode=b64decode looks funny but we only care about the comparison
# and not the actual data so it doesn't matter if we do it as base64 or
# bytes, just that they are both the same.
sig = crypto.sign(self.sig_key, data, b64encode=b64decode)
if sig != signature:
raise ValueError("Invalid signature")
return data

View File

@ -1,109 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import six
from kiteclient.common import meta_data
from kiteclient.common import resource
from kiteclient.openstack.common.crypto import utils as cryptoutils
from kiteclient.openstack.common import jsonutils
class GroupKey(resource.Resource):
base_path = 'groups'
def __init__(self, source, destination, timestamp=None, nonce=None,
session=None):
self._group_key = None
self.source = source
self._destination = destination
self._timestamp = timestamp
self._metadata = None
self._nonce = nonce
if session:
self.create(session)
def __repr__(self):
base = 'src: "{0}", dst: "{1}"'.format(self.source.name,
self._destination)
group_key = 'Not yet created'
if self._group_key is not None:
group_key = self.b64_key
return '<GroupKey {0} {1}>'.format(base, group_key)
def create(self, session):
b64_metadata = meta_data.Metadata(self.source.name,
self._destination,
self._timestamp,
self._nonce).encode()
b64_signature = self.source.sign(b64_metadata, b64encode=True)
json = {'metadata': b64_metadata,
'signature': b64_signature}
resp = self._http_post(session, json=json).json()
b64_metadata = resp['metadata']
b64_group_key = resp['group_key']
b64_signature = resp['signature']
sig = self.source.sign(six.b(b64_metadata + b64_group_key),
b64encode=True)
if sig != six.b(b64_signature):
raise ValueError("invalid signature on group key")
group_key = self.source.decrypt(b64_group_key, b64decode=True)
self._group_key = base64.b64encode(group_key)
self._metadata = jsonutils.loads(base64.b64decode(b64_metadata))
@property
def b64_key(self):
return self._group_key
@property
def key(self):
return base64.b64decode(self._group_key)
def encrypt(self, data, enctype='AES', hashtype='SHA256', b64encode=True):
crypto = cryptoutils.SymmetricCrypto(enctype=enctype,
hashtype=hashtype)
enc = crypto.encrypt(self.key, data, b64encode=b64encode)
# TODO(tim.kelsey): add a signing key.
# sig = crypto.sign(self.skey, data, b64encode=b64encode)
return enc # , sig
class Group(resource.Resource):
base_path = 'groups'
def __init__(self, name, session=None):
self.name = name
if session:
self.create(session)
def create(self, session):
resp = self._http_put(session, self.name).json()
if resp['name'] != self.name:
raise ValueError('Name was changed in response')
def getKey(self, source, session):
return GroupKey(source, self.name, session=session)

View File

@ -1,77 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
from Crypto import Random
from kiteclient.common import resource
from kiteclient.openstack.common.crypto import utils as cryptoutils
class Key(resource.Resource):
base_path = 'keys'
def __init__(self, name, b64_key, generation=None, session=None):
self.name = name
self.b64_key = b64_key
self.generation = generation
if session:
self.create(session)
@classmethod
def generate(cls, name, session=None):
b64_key = base64.b64encode(Random.new().read(16))
return cls(name, b64_key, session=session)
@property
def key(self):
return base64.b64decode(self.b64_key)
@key.setter
def key(self, val):
self.b64_key = base64.b64encode(val)
@property
def key_name(self):
return '%s:%d' % (self.name, self.generation)
def __repr__(self):
return '<Key %s - %s>' % (self.key_name, self.b64_key)
def sign(self, data, hashtype='SHA256', b64encode=True):
crypto = cryptoutils.SymmetricCrypto(hashtype=hashtype)
return crypto.sign(self.key, data, b64encode=b64encode)
def encrypt(self, data, enctype='AES', b64encode=True):
crypto = cryptoutils.SymmetricCrypto(enctype=enctype)
return crypto.encrypt(self.key, data, b64encode=b64encode)
def decrypt(self, data, enctype='AES', b64decode=True):
crypto = cryptoutils.SymmetricCrypto(enctype=enctype)
return crypto.decrypt(self.key, data, b64decode=b64decode)
def create(self, session):
if self.generation:
raise RuntimeError('This key has already been assigned a '
'generation meaning it has already been '
'created. Cannot create again')
resp = self._http_put(session, self.name,
json={'key': self.b64_key}).json()
if resp['name'] != self.name:
raise ValueError('Name was changed in response')
self.generation = resp['generation']

View File

@ -1,109 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import six
from kiteclient.common import meta_data
from kiteclient.common import resource
from kiteclient.openstack.common.crypto import utils as cryptoutils
from kiteclient.openstack.common import jsonutils
class Ticket(resource.Resource):
base_path = 'tickets'
def __init__(self, source, destination, timestamp=None, nonce=None,
session=None):
self.source = source
self._destination = destination
self._timestamp = timestamp
self._nonce = nonce
self._ticket = None
self._metadata = None
if session:
self.create(session)
def create(self, session):
b64_metadata = meta_data.Metadata(self.source.name,
self._destination,
self._timestamp,
self._nonce).encode()
b64_signature = self.source.sign(b64_metadata, b64encode=True)
json = {'metadata': b64_metadata,
'signature': b64_signature}
resp = self._http_post(session, json=json).json()
b64_metadata = resp['metadata']
b64_ticket = resp['ticket']
b64_signature = resp['signature']
sig = self.source.sign(six.b(b64_metadata + b64_ticket),
b64encode=True)
if sig != six.b(b64_signature):
raise ValueError("invalid signature on ticket")
data = self.source.decrypt(b64_ticket, b64decode=True)
self._ticket = jsonutils.loads(data)
self._ticket['skey'] = six.b(self._ticket['skey'])
self._ticket['ekey'] = six.b(self._ticket['ekey'])
self._ticket['esek'] = six.b(self._ticket['esek'])
self._metadata = jsonutils.loads(base64.b64decode(b64_metadata))
def __repr__(self):
base = 'src: "%s", dst: "%s"' % (self.source.name, self._destination)
if self._ticket:
ticket = 'skey: "%s", ekey: "%s"' % (self.b64_skey, self.b64_ekey)
else:
ticket = 'Not yet created'
return '<Ticket %s %s>' % (base, ticket)
@property
def b64_skey(self):
return self._ticket['skey']
@property
def skey(self):
return base64.b64decode(self.b64_skey)
@property
def b64_ekey(self):
return self._ticket['ekey']
@property
def ekey(self):
return base64.b64decode(self.b64_ekey)
@property
def b64_esek(self):
return self._ticket['esek']
@property
def esek(self):
return base64.b64decode(self.b64_esek)
def encrypt(self, data, enctype='AES', hashtype='SHA256', b64encode=True):
crypto = cryptoutils.SymmetricCrypto(enctype=enctype,
hashtype=hashtype)
enc = crypto.encrypt(self.ekey, data, b64encode=b64encode)
sig = crypto.sign(self.skey, data, b64encode=b64encode)
return enc, sig

View File

@ -1,8 +0,0 @@
[DEFAULT]
# The list of modules to copy from oslo-incubator.git
# The base module to hold the copy of openstack.common
base=kiteclient
module=crypto

View File

@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=0.6,!=0.7,<1.0
Babel>=1.3
pycrypto>=2.6
oslo.utils>=1.4.0 # Apache-2.0
iso8601>=0.1.9

View File

@ -1,43 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from keystoneclient.auth import token_endpoint
from keystoneclient import session
from kiteclient import v1
logging.basicConfig(level=logging.DEBUG)
message = 'MESSAGE'
auth = token_endpoint.Token('http://localhost:9109/v1', 'aToken')
s = session.Session(auth=auth)
srckey = v1.Key.generate('testkey', session=s)
print("source", srckey)
dstkey = v1.Key.generate('destkey', session=s)
print("dest", dstkey)
tick = v1.Ticket(srckey, dstkey.name, session=s)
print("ticket", tick)
enc, sig = tick.encrypt(message, b64encode=True)
esek = v1.Esek(srckey.key_name, dstkey, tick.b64_esek)
new_message = esek.decrypt(enc, sig, b64decode=True)
assert message == new_message
print(new_message)

View File

@ -1,84 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from kiteclient import v1
class KiteClient(object):
"""KiteClient class.
This acts as a client to the kite server and will register a key with
kite upon creation. A transport class must be provided, message data
will be read from the transport, decrypted and verified.
To send a message, data will be encrypted and signed using a kite
ticket then passed onto the transport. A message is formed thus:
{
"sender" : sender key name,
"body" : encrypted data, base64 encoded,
"signature" : message signature, base64 encoded,
"esec" : message esek data
}
"""
def __init__(self, name, transport, key=None, session=None,
verbose=False):
self._session = session
self._transport = transport
self.verbose = verbose
if key is None:
self._key = v1.Key.generate(name, self._session)
else:
self._key = v1.Key(name, key, self._session)
def _get_ticket(self, target):
ticket = v1.Ticket(self._key, target, session=self._session)
return ticket
def send(self, data, target):
"""Create and send a trusted message.
:param data: the message body
:param target: the recipient name
"""
ticket = self._get_ticket(target)
enc, sig = ticket.encrypt(data)
message = {
"sender": self._key.key_name,
"body": enc,
"signature": sig,
"esek": ticket.b64_esek
}
if self.verbose:
print("Sent: %s" % message)
resp = self._transport.send(message, target)
return resp
def recv(self):
"""Receive, verify and decrypt a secure message
:param data: the secure message data
"""
message = self._transport.recv()
if self.verbose:
print("Received: %s" % message)
esek = v1.Esek(message["sender"], self._key, message["esek"])
body = esek.decrypt(message["body"],
message["signature"],
b64decode=True)
return body

View File

@ -1,93 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import client
from keystoneclient.auth import token_endpoint
from keystoneclient import session
import transport
from kiteclient.openstack.common import gettextutils as u
import optparse
def main():
usage = "usage: %prog [options] name [-l | target message]"
parser = optparse.OptionParser(usage=usage)
parser.add_option("-v", "--verbose", dest="verbose",
help=u._("Be verbose."), action="store_true",
default=False)
parser.add_option('--kite_host',
help=u._('Kite server host address.'),
default="localhost"),
parser.add_option('--kite_port',
help=u._('Kite server port number.'),
default="9109"),
parser.add_option('--key', "-k",
help=u._('Optional base64 encoded key.')),
parser.add_option('--queue', "-q",
help=u._('Message queue url.'),
default="rabbit://localhost"),
parser.add_option('--listen', "-l",
help=u._('Listen for incoming messages.'),
action="store_true", default=False),
(options, args) = parser.parse_args()
if not args:
parser.print_usage()
return -1
# NOTE(tkelsey): using developer interface to remove the need to run
# a keystone server.
name = args[0]
host = options.kite_host
port = options.kite_port
kite = "http://%s:%s/v1" % (host, port)
auth = token_endpoint.Token(kite, 'aToken')
transport_obj = transport.Transport(target=name, url=options.queue)
session_obj = session.Session(auth=auth)
client_obj = client.KiteClient(name, transport_obj,
key=options.key,
session=session_obj,
verbose=options.verbose)
if options.listen:
try:
message = client_obj.recv()
print(message)
except KeyboardInterrupt:
pass
else:
if len(args) < 3:
print("target and message are required when not listening")
parser.print_usage()
return -1
else:
targ = args[1]
body = args[2]
resp = client_obj.send(body, targ)
print(resp)
if __name__ == "__main__":
main()

View File

@ -1,44 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from oslo import messaging
class Transport(object):
def __init__(self, target, url):
self._transport = messaging.get_transport(cfg.CONF, url)
self._target = messaging.Target(topic=target,
server='server1',
version='1.0')
self.timeout = 2
self.retry = True
def send(self, msg, target):
msg_ctxt = {}
targ = messaging.Target(topic=target,
server='server1',
version='1.0')
result = self._transport._send(targ, msg_ctxt, msg,
wait_for_reply=True,
timeout=self.timeout,
)
return result
def recv(self):
listener = self._transport._listen(self._target)
message = listener.poll()
message.reply(reply="OK")
message.acknowledge()
return message.message

View File

@ -1,38 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from keystoneclient.auth import token_endpoint
from keystoneclient import session
from kiteclient import v1
logging.basicConfig(level=logging.DEBUG)
auth = token_endpoint.Token('http://localhost:9109/v1', 'aToken')
s = session.Session(auth=auth)
group = v1.group.Group('testgroup', session=s)
keyA = v1.Key.generate('testgroup.testkeyA', session=s)
keyB = v1.Key.generate('testgroup.testkeyB', session=s)
tickA = v1.group.GroupKey(keyA, "testgroup", session=s)
tickB = group.getKey(keyB, session=s)
print("-----------------------------------------------------------------")
print(keyA)
print("gkey A", tickA)
print("-----------------------------------------------------------------")
print(keyB)
print("gkey B", tickB)
print("-----------------------------------------------------------------")

View File

@ -1,55 +0,0 @@
[metadata]
name = python-kiteclient
summary = Interacting with the kite server
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
[files]
packages =
kiteclient
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = kiteclient/locale
domain = python-kiteclient
[update_catalog]
domain = python-kiteclient
output_dir = kiteclient/locale
input_file = kiteclient/locale/python-kiteclient.pot
[entry_points]
openstack.cli.extension =
kds = kiteclient.cli.v1
openstack.kds.v1 =
key_set = kiteclient.cli.v1:KeySet
keygroup_create = kiteclient.cli.v1:GroupCreate
keygroup_delete = kiteclient.cli.v1:GroupDelete
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = kiteclient/locale/python-kiteclient.pot

View File

@ -1,30 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

View File

@ -1,14 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=0.9.1,<0.10
coverage>=3.6
discover
fixtures>=0.3.14
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
oslosphinx>=2.5.0 # Apache-2.0
testrepository>=0.0.18
testscenarios>=0.4
testtools>=0.9.36,!=1.2.0

32
tox.ini
View File

@ -1,32 +0,0 @@
[tox]
minversion = 1.6
envlist = py26,py27,py33,py34,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[flake8]
# H803 skipped on purpose per list discussion.
# E123, E125 skipped as they are invalid PEP-8.
# H302 relaxed for now
show-source = True
ignore = E123,E125,H803,H302
builtins = _
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build