Retire castellan-ui
Change-Id: I30501c7c893e62194efa10965b2b3176a2ea0745
This commit is contained in:
parent
2f96f99338
commit
c6b3cd7bd2
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,22 +0,0 @@
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
build
|
||||
cover
|
||||
doc/source/contributor/api
|
||||
node_modules
|
||||
npm-debug.log
|
||||
releasenotes/build
|
||||
castellan_ui/test/.secret_key_store
|
||||
.coverage*
|
||||
.jshintrc
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
.tox
|
||||
.venv
|
||||
*.egg*
|
||||
*.lock
|
||||
*.mo
|
||||
*.py[cod]
|
||||
*.swp
|
||||
*nose_results.html
|
@ -1,6 +0,0 @@
|
||||
- project:
|
||||
templates:
|
||||
- openstack-python-jobs-horizon
|
||||
- openstack-python35-jobs-horizon
|
||||
- publish-openstack-sphinx-docs
|
||||
- check-requirements
|
@ -1,14 +0,0 @@
|
||||
If you would like to contribute to the development of OpenStack, you must
|
||||
follow the steps in this page:
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow
|
||||
section of this documentation to learn how changes to OpenStack should be
|
||||
submitted for review via the Gerrit tool:
|
||||
https://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/castellan-ui
|
@ -1,4 +0,0 @@
|
||||
castellan-ui Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
175
LICENSE
175
LICENSE
@ -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.
|
@ -1,9 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
include setup.py
|
||||
|
||||
recursive-include castellan_ui *.js *.html *.scss
|
||||
|
||||
global-exclude *.pyc
|
124
README.rst
124
README.rst
@ -1,118 +1,14 @@
|
||||
===============================
|
||||
============
|
||||
Castellan UI
|
||||
===============================
|
||||
============
|
||||
|
||||
Generic Key Manager UI Plugin for Horizon
|
||||
This project is no longer maintained.
|
||||
|
||||
* Free software: Apache license
|
||||
* Source: http://git.openstack.org/cgit/openstack/castellan-ui
|
||||
* Bugs: http://bugs.launchpad.net/castellan-ui
|
||||
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".
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| | Import from file | Import using direct input | Download | Delete | Generate [1] |
|
||||
====================+==================+===========================+===============+===========+==============+
|
||||
| X.509 Certificates | supported [2] | supported [2] | supported [2] | supported | N/A |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| Private Keys | supported [2] | supported [2] | supported [2] | supported | supported |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| Public Keys | supported [2] | supported [2] | supported [2] | supported | supported |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| Symmetric Keys | supported [3] | supported [4] | supported [3] | supported | supported |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| Opaque Data | supported [3] | supported [4] | supported [3] | supported | N/A |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
| Passphrases [5] | X | supported | X | supported | N/A |
|
||||
--------------------+------------------+---------------------------+---------------+-----------+--------------+
|
||||
|
||||
1. Key managers typically support generating keys only and do not generate
|
||||
other types of objects. Private and public keys will be generated as a key
|
||||
pair, and symmetric keys can be generated individually.
|
||||
2. Supports Privacy-enhanced Electronic Mail (PEM) formatted objects.
|
||||
3. Raw bytes represent the object.
|
||||
4. Object bytes are represented using hex characters.
|
||||
5. Because passphrases are typically not saved to files, passphrases are
|
||||
imported through a form on the web page and are not downloadable, only
|
||||
viewed through the web page.
|
||||
|
||||
Enabling in DevStack
|
||||
--------------------
|
||||
|
||||
Add this repo as an external repository into your ``local.conf`` file::
|
||||
|
||||
[[local|localrc]]
|
||||
enable_plugin castellan-ui https://github.com/openstack/castellan-ui
|
||||
|
||||
Manual Installation
|
||||
-------------------
|
||||
|
||||
Begin by cloning the Horizon and Castellan UI repositories::
|
||||
|
||||
git clone https://github.com/openstack/horizon
|
||||
git clone https://github.com/openstack/castellan-ui
|
||||
|
||||
Create a virtual environment and install Horizon dependencies::
|
||||
|
||||
cd horizon
|
||||
virtualenv horizon_dev
|
||||
. horizon_dev/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
Set up your ``local_settings.py`` file::
|
||||
|
||||
cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
|
||||
|
||||
Open up the copied ``local_settings.py`` file in your preferred text
|
||||
editor. You will want to customize several settings:
|
||||
|
||||
- ``OPENSTACK_HOST`` should be configured with the hostname of your
|
||||
OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and
|
||||
``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your
|
||||
environment. (They should be correct unless you modified your
|
||||
OpenStack server to change them.)
|
||||
|
||||
Install Castellan UI with all dependencies in your virtual environment::
|
||||
|
||||
. horizon_dev/bin/activate
|
||||
pip install -e ../castellan-ui/
|
||||
|
||||
And enable it in Horizon (use full paths instead of relative paths)::
|
||||
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_90_project_key_manager_panelgroup.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_91_project_key_manager_x509_certificates_panel.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_92_project_key_manager_private_key_panel.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_93_project_key_manager_public_key_panel.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_94_project_key_manager_symmetric_key_panel.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_95_project_key_manager_opaque_data_panel.py openstack_dashboard/local/enabled
|
||||
ln -s ../castellan-ui/castellan_ui/enabled/_96_project_key_manager_passphrase_panel.py openstack_dashboard/local/enabled
|
||||
|
||||
To run horizon with the newly enabled Castellan UI plugin run::
|
||||
|
||||
python manage.py runserver -- 0.0.0.0:8080
|
||||
|
||||
to have the application start on port 8080 and the horizon dashboard will be
|
||||
available in your browser at http://localhost:8080/
|
||||
|
||||
Troubleshooting Tips
|
||||
--------------------
|
||||
|
||||
If you are using Barbican plugin for Castellan, be sure to note that Barbican
|
||||
requires the 'admin' or 'creator' role be assigned to a user before the user
|
||||
can list or create key manager objects. The error message that appears if this
|
||||
is not the case is as follows::
|
||||
|
||||
Could not list objects: Key manager error: Forbidden: Secret(s) retrieval attempt not allowed - please review your user/project privileges
|
||||
|
||||
To add the appropriate role for a non-admin user, use the following command (as an admin) ::
|
||||
|
||||
openstack role add --user <username> --project <project name> creator
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
* Castellan: https://github.com/openstack/castellan
|
||||
* Barbican: https://github.com/openstack/barbican
|
||||
* Vault: https://github.com/hashicorp/vault
|
||||
* PyKMIP: https://github.com/OpenKMIP/PyKMIP
|
||||
For any further questions, please email
|
||||
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
@ -1,5 +0,0 @@
|
||||
[extractors]
|
||||
django = django_babel.extract:extract_django
|
||||
|
||||
[python: **.py]
|
||||
[django: templates/**.html]
|
@ -1,14 +0,0 @@
|
||||
[extractors]
|
||||
# We use a custom extractor to find translatable strings in AngularJS
|
||||
# templates. The extractor is included in horizon.utils for now.
|
||||
# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for
|
||||
# details on how this works.
|
||||
angular = horizon.utils.babel_extract_angular:extract_angular
|
||||
|
||||
[javascript: **.js]
|
||||
|
||||
# We need to look into all static folders for HTML files.
|
||||
# The **/static ensures that we also search within
|
||||
# /openstack_dashboard/dashboards/XYZ/static which will ensure
|
||||
# that plugins are also translated.
|
||||
[angular: **/static/**.html]
|
@ -1,140 +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 absolute_import
|
||||
|
||||
import base64
|
||||
import logging
|
||||
|
||||
from oslo_context import context
|
||||
|
||||
from castellan.common import exception as castellan_exception
|
||||
from castellan.common.objects import key as key_type
|
||||
from castellan.common.objects import opaque_data
|
||||
from castellan.common.objects import passphrase
|
||||
from castellan.common.objects import x_509
|
||||
from castellan import key_manager as key_manager_api
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon.utils.memoized import memoized_with_request
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
GENERATE_ATTRIBUTES = ['algorithm', 'length', 'name']
|
||||
IMPORT_KEY_ATTRIBUTES = ['algorithm', 'bit_length', 'name',
|
||||
'key']
|
||||
IMPORT_CERT_ATTRIBUTES = ['name', 'data']
|
||||
IMPORT_PASSPHRASE_ATTRIBUTES = ['name', 'passphrase']
|
||||
IMPORT_DATA_ATTRIBUTES = ['name', 'data']
|
||||
|
||||
|
||||
def key_manager():
|
||||
return key_manager_api.API()
|
||||
|
||||
|
||||
def get_auth_params_from_request(request):
|
||||
return(
|
||||
request.user.token.id,
|
||||
request.user.tenant_id,
|
||||
)
|
||||
|
||||
|
||||
@memoized_with_request(get_auth_params_from_request)
|
||||
def get_context(request_auth_params):
|
||||
token_id, tenant_id = request_auth_params
|
||||
|
||||
return context.RequestContext(auth_token=token_id,
|
||||
tenant=tenant_id)
|
||||
|
||||
|
||||
def import_object(request, **kwargs):
|
||||
args = {}
|
||||
try:
|
||||
object_type = kwargs.pop('object_type')
|
||||
except TypeError:
|
||||
raise exceptions.BadRequest("Object type must be included in kwargs")
|
||||
for (key, value) in kwargs.items():
|
||||
if key in ['data', 'key']:
|
||||
# the data was passed in b64 encoded because some of the bytes
|
||||
# were changed when the raw bytes were passed from the form
|
||||
value = base64.b64decode(value)
|
||||
|
||||
if (issubclass(object_type, key_type.Key) and
|
||||
key in IMPORT_KEY_ATTRIBUTES):
|
||||
args[str(key)] = value
|
||||
elif object_type == x_509.X509 and key in IMPORT_CERT_ATTRIBUTES:
|
||||
args[str(key)] = value
|
||||
elif (object_type == passphrase.Passphrase and
|
||||
key in IMPORT_PASSPHRASE_ATTRIBUTES):
|
||||
args[str(key)] = value
|
||||
elif (object_type == opaque_data.OpaqueData and
|
||||
key in IMPORT_DATA_ATTRIBUTES):
|
||||
args[str(key)] = value
|
||||
else:
|
||||
raise exceptions.BadRequest(
|
||||
"Attribute must be in %s" % ",".join(IMPORT_KEY_ATTRIBUTES))
|
||||
key = object_type(**args)
|
||||
created_uuid = key_manager().store(get_context(request), key)
|
||||
|
||||
return created_uuid
|
||||
|
||||
|
||||
def generate_symmetric_key(request, **kwargs):
|
||||
args = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in GENERATE_ATTRIBUTES:
|
||||
args[str(key)] = value
|
||||
else:
|
||||
raise exceptions.BadRequest(
|
||||
"Key must be in %s" % ",".join(GENERATE_ATTRIBUTES))
|
||||
created_uuid = key_manager().create_key(get_context(request),
|
||||
**args)
|
||||
|
||||
return created_uuid
|
||||
|
||||
|
||||
def generate_key_pair(request, **kwargs):
|
||||
args = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in GENERATE_ATTRIBUTES:
|
||||
args[str(key)] = value
|
||||
else:
|
||||
raise exceptions.BadRequest(
|
||||
"Key must be in %s" % ",".join(GENERATE_ATTRIBUTES))
|
||||
private_uuid, public_uuid = key_manager().create_key_pair(
|
||||
get_context(request),
|
||||
**args)
|
||||
|
||||
return "{priv} + {pub}".format(priv=private_uuid, pub=public_uuid)
|
||||
|
||||
|
||||
def delete(request, id):
|
||||
deleted = key_manager().delete(get_context(request), id)
|
||||
|
||||
return deleted
|
||||
|
||||
|
||||
def list(request, object_type=None):
|
||||
try:
|
||||
list = key_manager().list(
|
||||
get_context(request), object_type=object_type, metadata_only=True)
|
||||
except castellan_exception.KeyManagerError as e:
|
||||
raise exceptions.BadRequest("Could not list objects: %s" % e.message)
|
||||
|
||||
return list
|
||||
|
||||
|
||||
def get(request, id):
|
||||
show = key_manager().get(get_context(request), id)
|
||||
return show
|
@ -1,18 +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 datetime import datetime
|
||||
from horizon.utils import filters as horizon_filters
|
||||
|
||||
|
||||
def timestamp_to_iso(timestamp):
|
||||
date = datetime.utcfromtimestamp(timestamp)
|
||||
return horizon_filters.parse_isotime(date.isoformat())
|
@ -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.
|
||||
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import opaque_data
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content import shared_forms
|
||||
|
||||
|
||||
class ImportOpaqueData(forms.SelfHandlingForm):
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Data Name"),
|
||||
regex=shared_forms.NAME_REGEX,
|
||||
error_messages=shared_forms.ERROR_MESSAGES)
|
||||
source_type = forms.ChoiceField(
|
||||
label=_('Source'),
|
||||
required=False,
|
||||
choices=[('file', _('File')),
|
||||
('raw', _('Direct Input'))],
|
||||
widget=forms.ThemableSelectWidget(
|
||||
attrs={'class': 'switchable', 'data-slug': 'source'}))
|
||||
object_file = forms.FileField(
|
||||
label=_("Choose file"),
|
||||
help_text=_("A local file to upload."),
|
||||
widget=forms.FileInput(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-file': _('File')}),
|
||||
required=False)
|
||||
direct_input = forms.CharField(
|
||||
label=_('Object Bytes'),
|
||||
help_text=_('The bytes of the object, represented in hex.'),
|
||||
widget=forms.widgets.Textarea(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-raw': _('Bytes')}),
|
||||
required=False)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(ImportOpaqueData, self).__init__(request, *args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
data = super(ImportOpaqueData, self).clean()
|
||||
|
||||
# The data can be missing based on particular upload
|
||||
# conditions. Code defensively for it here...
|
||||
data_file = data.get('object_file', None)
|
||||
data_raw = data.get('direct_input', None)
|
||||
|
||||
if data_raw and data_file:
|
||||
raise forms.ValidationError(
|
||||
_("Cannot specify both file and direct input."))
|
||||
if not data_raw and not data_file:
|
||||
raise forms.ValidationError(
|
||||
_("No input was provided for the object value."))
|
||||
try:
|
||||
if data_file:
|
||||
data_bytes = self.files['object_file'].read()
|
||||
else:
|
||||
data_str = data['direct_input']
|
||||
data_bytes = binascii.unhexlify(data_str)
|
||||
data['object_bytes'] = base64.b64encode(data_bytes)
|
||||
except Exception as e:
|
||||
msg = _('There was a problem loading the object: %s. '
|
||||
'Is the object valid and in the correct format?') % e
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
return data
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
data_bytes = data.get('object_bytes')
|
||||
data_uuid = client.import_object(
|
||||
request,
|
||||
data=data_bytes,
|
||||
name=data['name'],
|
||||
object_type=opaque_data.OpaqueData)
|
||||
|
||||
if data['name']:
|
||||
data_identifier = data['name']
|
||||
else:
|
||||
data_identifier = data_uuid
|
||||
messages.success(request,
|
||||
_('Successfully imported object: %s')
|
||||
% data_identifier)
|
||||
return data_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to import object: %s')
|
||||
messages.error(msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to import object.'))
|
||||
return False
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class OpaqueData(horizon.Panel):
|
||||
name = _("Opaque Data")
|
||||
slug = "opaque_data"
|
@ -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 castellan_ui.content import filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class ImportOpaqueData(tables.LinkAction):
|
||||
name = "import_opaque_data"
|
||||
verbose_name = _("Import Opaque Data")
|
||||
url = "horizon:project:opaque_data:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DownloadOpaqueData(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Opaque Data")
|
||||
url = "horizon:project:opaque_data:download"
|
||||
classes = ("btn-download",)
|
||||
policy_rules = ()
|
||||
|
||||
def get_link_url(self, datum):
|
||||
return reverse(self.url,
|
||||
kwargs={'object_id': datum.id})
|
||||
|
||||
|
||||
class DeleteOpaqueData(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete an object unless you are "
|
||||
"certain it is not being used anywhere.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Opaque Data",
|
||||
u"Delete Opaque Data",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Opaque Data",
|
||||
u"Deleted Opaque Data",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class OpaqueDataTable(tables.DataTable):
|
||||
detail_link = "horizon:project:opaque_data:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("Object ID"), link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "opaque_data"
|
||||
table_actions = (ImportOpaqueData,
|
||||
DeleteOpaqueData,)
|
||||
row_actions = (DownloadOpaqueData, DeleteOpaqueData)
|
@ -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.
|
||||
|
||||
from castellan_ui.content.opaque_data import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^download/$', views.download_key, name='download'),
|
||||
url(r'^(?P<object_id>[^/]+)/download$',
|
||||
views.download_key,
|
||||
name='download'),
|
||||
]
|
@ -1,124 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import binascii
|
||||
from castellan.common.objects import opaque_data
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.opaque_data import forms as opaque_data_forms
|
||||
from castellan_ui.content.opaque_data import tables
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
def download_key(request, object_id):
|
||||
try:
|
||||
obj = client.get(request, object_id)
|
||||
data = obj.get_encoded()
|
||||
response = HttpResponse()
|
||||
response.write(data)
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename="%s.opaque"' % object_id)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:opaque_data:index')
|
||||
msg = _('Unable to download opaque_data "%s".')\
|
||||
% (object_id)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.OpaqueDataTable
|
||||
]
|
||||
template_name = 'opaque_data.html'
|
||||
|
||||
def get_opaque_data_data(self):
|
||||
try:
|
||||
return client.list(
|
||||
self.request, object_type=opaque_data.OpaqueData)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list objects: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = opaque_data_forms.ImportOpaqueData
|
||||
template_name = 'opaque_data_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:opaque_data:import")
|
||||
success_url = reverse_lazy('horizon:project:opaque_data:index')
|
||||
submit_label = page_title = _("Import Opaque Data")
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'opaque_data_detail.html'
|
||||
page_title = _("Opaque Data Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:opaque_data:index')
|
||||
msg = _('Unable to retrieve details for opaque_data "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:opaque_data:index')
|
||||
msg = _('Unable to retrieve details for opaque_data "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
try:
|
||||
data_bytes = binascii.hexlify(obj.get_encoded())
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:opaque_data:index')
|
||||
msg = _('Unable to retrieve details for opaque_data "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return data_bytes
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
return context
|
@ -1,61 +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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import passphrase
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content import shared_forms
|
||||
|
||||
|
||||
class ImportPassphrase(forms.SelfHandlingForm):
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Passphrase Name"),
|
||||
regex=shared_forms.NAME_REGEX,
|
||||
error_messages=shared_forms.ERROR_MESSAGES)
|
||||
direct_input = forms.CharField(
|
||||
label=_('Passphrase'),
|
||||
help_text=_('The text of the passphrase in plaintext'),
|
||||
widget=forms.widgets.Textarea(),
|
||||
required=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
# Remove any new lines in the passphrase
|
||||
direct_input = data.get('direct_input')
|
||||
direct_input = shared_forms.NEW_LINES.sub("", direct_input)
|
||||
object_uuid = client.import_object(
|
||||
request,
|
||||
passphrase=direct_input,
|
||||
name=data['name'],
|
||||
object_type=passphrase.Passphrase)
|
||||
|
||||
if data['name']:
|
||||
object_identifier = data['name']
|
||||
else:
|
||||
object_identifier = object_uuid
|
||||
messages.success(request,
|
||||
_('Successfully imported passphrase: %s')
|
||||
% object_identifier)
|
||||
return object_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to import passphrase: %s')
|
||||
messages.error(request, msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to import passphrase.'))
|
||||
return False
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class Passphrases(horizon.Panel):
|
||||
name = _("Passphrases")
|
||||
slug = "passphrases"
|
@ -1,72 +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 castellan_ui.content import filters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class ImportPassphrase(tables.LinkAction):
|
||||
name = "import_passphrase"
|
||||
verbose_name = _("Import Passphrase")
|
||||
url = "horizon:project:passphrases:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DeletePassphrase(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete a passphrase unless you are "
|
||||
"certain it is not being used anywhere.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Passphrase",
|
||||
u"Delete Passphrases",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Passphrase",
|
||||
u"Deleted Passphrases",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class PassphraseTable(tables.DataTable):
|
||||
detail_link = "horizon:project:passphrases:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("Passphrase ID"),
|
||||
link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "passphrase"
|
||||
table_actions = (ImportPassphrase,
|
||||
DeletePassphrase,)
|
||||
row_actions = (DeletePassphrase, )
|
@ -1,22 +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 castellan_ui.content.passphrases import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
]
|
@ -1,96 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import passphrase
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.passphrases import forms as passphrase_forms
|
||||
from castellan_ui.content.passphrases import tables
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.PassphraseTable
|
||||
]
|
||||
template_name = 'passphrases.html'
|
||||
|
||||
def get_passphrase_data(self):
|
||||
try:
|
||||
return client.list(
|
||||
self.request, object_type=passphrase.Passphrase)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list passphrases: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = passphrase_forms.ImportPassphrase
|
||||
template_name = 'passphrase_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:passphrases:import")
|
||||
success_url = reverse_lazy('horizon:project:passphrases:index')
|
||||
submit_label = page_title = _("Import Passphrase")
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'passphrase_detail.html'
|
||||
page_title = _("Passphrase Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:passphrases:index')
|
||||
msg = _('Unable to retrieve details for passphrase "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:passphrases:index')
|
||||
msg = _('Unable to retrieve details for passphrase "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
return obj.get_encoded()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
return context
|
@ -1,47 +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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import private_key
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
from castellan_ui.content import shared_forms
|
||||
|
||||
|
||||
class ImportPrivateKey(shared_forms.ImportKey):
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(ImportPrivateKey, self).__init__(
|
||||
request, *args, algorithms=shared_forms.KEY_PAIR_ALGORITHMS,
|
||||
**kwargs)
|
||||
self.fields['direct_input'].help_text = _(
|
||||
"PEM formatted private key.")
|
||||
self.fields['key_file'].help_text = _(
|
||||
"PEM formatted private key file.")
|
||||
|
||||
def clean_key_data(self, key_data):
|
||||
key_obj = load_pem_private_key(
|
||||
key_data.encode('utf-8'), password=None, backend=default_backend())
|
||||
key_der = key_obj.private_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
return base64.b64encode(key_der)
|
||||
|
||||
def handle(self, request, data):
|
||||
return super(ImportPrivateKey, self).handle(
|
||||
request, data, private_key.PrivateKey)
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class PrivateKeys(horizon.Panel):
|
||||
name = _("Private Keys")
|
||||
slug = "private_keys"
|
@ -1,98 +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 castellan_ui.content import filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class GeneratePrivateKey(tables.LinkAction):
|
||||
name = "generate_private_key"
|
||||
verbose_name = _("Generate Key Pair")
|
||||
url = "horizon:project:private_keys:generate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class ImportPrivateKey(tables.LinkAction):
|
||||
name = "import_private_key"
|
||||
verbose_name = _("Import Private Key")
|
||||
url = "horizon:project:private_keys:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DownloadKey(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Key")
|
||||
url = "horizon:project:private_keys:download"
|
||||
classes = ("btn-download",)
|
||||
policy_rules = ()
|
||||
|
||||
def get_link_url(self, datum):
|
||||
return reverse(self.url,
|
||||
kwargs={'object_id': datum.id})
|
||||
|
||||
|
||||
class DeletePrivateKey(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete a private key unless you are "
|
||||
"certain it is not being used anywhere. If there was a "
|
||||
"public key generated with this private key, it will not "
|
||||
"be deleted.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Private Key",
|
||||
u"Delete Private Keys",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Private Key",
|
||||
u"Deleted Private Keys",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class PrivateKeyTable(tables.DataTable):
|
||||
detail_link = "horizon:project:private_keys:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("Key ID"), link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
algorithm = tables.Column("algorithm", verbose_name=_("Algorithm"))
|
||||
bit_length = tables.Column("bit_length", verbose_name=_("Bit Length"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "private_key"
|
||||
table_actions = (GeneratePrivateKey,
|
||||
ImportPrivateKey,
|
||||
DeletePrivateKey,)
|
||||
row_actions = (DownloadKey, DeletePrivateKey)
|
@ -1,27 +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 castellan_ui.content.private_keys import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^generate/$', views.GenerateView.as_view(), name='generate'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^download/$', views.download_key, name='download'),
|
||||
url(r'^(?P<object_id>[^/]+)/download$',
|
||||
views.download_key,
|
||||
name='download'),
|
||||
]
|
@ -1,150 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import load_der_private_key
|
||||
|
||||
from castellan.common.objects import private_key
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.private_keys import forms as private_key_forms
|
||||
from castellan_ui.content.private_keys import tables
|
||||
from castellan_ui.content import shared_forms
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
def download_key(request, object_id):
|
||||
try:
|
||||
obj = client.get(request, object_id)
|
||||
data = obj.get_encoded()
|
||||
key_obj = load_der_private_key(
|
||||
data, password=None, backend=backends.default_backend())
|
||||
key_pem = key_obj.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
response = HttpResponse()
|
||||
response.write(key_pem)
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename="%s.key"' % object_id)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:private_keys:index')
|
||||
msg = _('Unable to download private_key "%s".')\
|
||||
% (object_id)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.PrivateKeyTable
|
||||
]
|
||||
template_name = 'private_keys.html'
|
||||
|
||||
def get_private_key_data(self):
|
||||
try:
|
||||
return client.list(
|
||||
self.request, object_type=private_key.PrivateKey)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list private keys: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class GenerateView(forms.ModalFormView):
|
||||
form_class = shared_forms.GenerateKeyPair
|
||||
template_name = 'private_key_generate.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:private_keys:generate")
|
||||
success_url = reverse_lazy('horizon:project:private_keys:index')
|
||||
submit_label = page_title = _("Generate Key Pair")
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = private_key_forms.ImportPrivateKey
|
||||
template_name = 'private_key_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:private_keys:import")
|
||||
success_url = reverse_lazy('horizon:project:private_keys:index')
|
||||
submit_label = page_title = _("Import Private Key")
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'private_key_detail.html'
|
||||
page_title = _("Private Key Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:private_keys:index')
|
||||
msg = _('Unable to retrieve details for private_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:private_keys:index')
|
||||
msg = _('Unable to retrieve details for private_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
try:
|
||||
key = serialization.load_der_private_key(
|
||||
obj.get_encoded(),
|
||||
backend=backends.default_backend(),
|
||||
password=None)
|
||||
data_bytes = key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:private_keys:index')
|
||||
msg = _('Unable to retrieve details for private_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return data_bytes
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
return context
|
@ -1,46 +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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import public_key
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||
|
||||
from castellan_ui.content import shared_forms
|
||||
|
||||
|
||||
class ImportPublicKey(shared_forms.ImportKey):
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(ImportPublicKey, self).__init__(
|
||||
request, *args, algorithms=shared_forms.KEY_PAIR_ALGORITHMS,
|
||||
**kwargs)
|
||||
self.fields['direct_input'].help_text = _(
|
||||
"PEM formatted public key.")
|
||||
self.fields['key_file'].help_text = _(
|
||||
"PEM formatted public key file.")
|
||||
|
||||
def clean_key_data(self, key_data):
|
||||
key_obj = load_pem_public_key(
|
||||
key_data.encode('utf-8'), backend=default_backend())
|
||||
key_der = key_obj.public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
return base64.b64encode(key_der)
|
||||
|
||||
def handle(self, request, data):
|
||||
return super(ImportPublicKey, self).handle(
|
||||
request, data, public_key.PublicKey)
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class PublicKeys(horizon.Panel):
|
||||
name = _("Public Keys")
|
||||
slug = "public_keys"
|
@ -1,98 +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 castellan_ui.content import filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class GeneratePublicKey(tables.LinkAction):
|
||||
name = "generate_public_key"
|
||||
verbose_name = _("Generate Key Pair")
|
||||
url = "horizon:project:public_keys:generate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class ImportPublicKey(tables.LinkAction):
|
||||
name = "import_public_key"
|
||||
verbose_name = _("Import Public Key")
|
||||
url = "horizon:project:public_keys:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DownloadKey(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Key")
|
||||
url = "horizon:project:public_keys:download"
|
||||
classes = ("btn-download",)
|
||||
policy_rules = ()
|
||||
|
||||
def get_link_url(self, datum):
|
||||
return reverse(self.url,
|
||||
kwargs={'object_id': datum.id})
|
||||
|
||||
|
||||
class DeletePublicKey(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete a public key unless you are "
|
||||
"certain it is not being used anywhere. If there was a "
|
||||
"private key generated with this public key, it will not "
|
||||
"be deleted.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Public Key",
|
||||
u"Delete Public Keys",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Public Key",
|
||||
u"Deleted Public Keys",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class PublicKeyTable(tables.DataTable):
|
||||
detail_link = "horizon:project:public_keys:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("Key ID"), link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
algorithm = tables.Column("algorithm", verbose_name=_("Algorithm"))
|
||||
bit_length = tables.Column("bit_length", verbose_name=_("Bit Length"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "public_key"
|
||||
table_actions = (GeneratePublicKey,
|
||||
ImportPublicKey,
|
||||
DeletePublicKey,)
|
||||
row_actions = (DownloadKey, DeletePublicKey)
|
@ -1,27 +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 castellan_ui.content.public_keys import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^generate/$', views.GenerateView.as_view(), name='generate'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^download/$', views.download_key, name='download'),
|
||||
url(r'^(?P<object_id>[^/]+)/download$',
|
||||
views.download_key,
|
||||
name='download'),
|
||||
]
|
@ -1,147 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from castellan.common.objects import public_key
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.public_keys import forms as public_key_forms
|
||||
from castellan_ui.content.public_keys import tables
|
||||
from castellan_ui.content import shared_forms
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
def download_key(request, object_id):
|
||||
try:
|
||||
obj = client.get(request, object_id)
|
||||
data = obj.get_encoded()
|
||||
key_obj = serialization.load_der_public_key(
|
||||
data, backend=backends.default_backend())
|
||||
key_pem = key_obj.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
|
||||
response = HttpResponse()
|
||||
response.write(key_pem)
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename="%s.key"' % object_id)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:public_keys:index')
|
||||
msg = _('Unable to download public_key "%s".')\
|
||||
% (object_id)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.PublicKeyTable
|
||||
]
|
||||
template_name = 'public_keys.html'
|
||||
|
||||
def get_public_key_data(self):
|
||||
try:
|
||||
return client.list(
|
||||
self.request, object_type=public_key.PublicKey)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list private keys: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class GenerateView(forms.ModalFormView):
|
||||
form_class = shared_forms.GenerateKeyPair
|
||||
template_name = 'public_key_generate.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:public_keys:generate")
|
||||
success_url = reverse_lazy('horizon:project:public_keys:index')
|
||||
submit_label = page_title = _("Generate Key Pair")
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = public_key_forms.ImportPublicKey
|
||||
template_name = 'public_key_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:public_keys:import")
|
||||
success_url = reverse_lazy('horizon:project:public_keys:index')
|
||||
submit_label = page_title = _("Import Public Key")
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'public_key_detail.html'
|
||||
page_title = _("Public Key Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:public_keys:index')
|
||||
msg = _('Unable to retrieve details for public_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:public_keys:index')
|
||||
msg = _('Unable to retrieve details for public_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
try:
|
||||
key = serialization.load_der_public_key(
|
||||
obj.get_encoded(),
|
||||
backend=backends.default_backend())
|
||||
data_bytes = key.public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:public_keys:index')
|
||||
msg = _('Unable to retrieve details for public_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return data_bytes
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
return context
|
@ -1,195 +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 abc
|
||||
import re
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from castellan_ui.api import client
|
||||
|
||||
|
||||
KEY_PAIR_ALGORITHMS = ('RSA', 'DSA')
|
||||
|
||||
NEW_LINES = re.compile(r"\r|\n")
|
||||
|
||||
NAME_REGEX = re.compile(r"^\w+(?:[- ]\w+)*$", re.UNICODE)
|
||||
ERROR_MESSAGES = {
|
||||
'invalid': _('Key name may only contain letters, '
|
||||
'numbers, underscores, spaces, and hyphens '
|
||||
'and may not be white space.')}
|
||||
|
||||
ALG_HELP_TEXT = _(
|
||||
"Check which algorithms your key manager supports. "
|
||||
"Some common algorithms are: %s") % ', '.join(KEY_PAIR_ALGORITHMS)
|
||||
LENGTH_HELP_TEXT = _(
|
||||
"Only certain bit lengths are valid for each algorithm. "
|
||||
"Some common bit lengths are: 1024, 2048")
|
||||
|
||||
|
||||
class ListTextWidget(forms.TextInput):
|
||||
def __init__(self, data_list, name, *args, **kwargs):
|
||||
super(ListTextWidget, self).__init__(*args, **kwargs)
|
||||
self._name = name
|
||||
self._list = data_list
|
||||
self.attrs.update({'list': 'list__%s' % self._name})
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
text_html = super(ListTextWidget, self).render(name,
|
||||
value,
|
||||
attrs=attrs)
|
||||
data_list = '<datalist id="list__%s">' % self._name
|
||||
for item in self._list:
|
||||
data_list += '<option value="%s">' % item
|
||||
data_list += '</datalist>'
|
||||
|
||||
return (text_html + data_list)
|
||||
|
||||
|
||||
class ImportKey(forms.SelfHandlingForm):
|
||||
algorithm = forms.CharField(label=_("Algorithm"), help_text=ALG_HELP_TEXT)
|
||||
bit_length = forms.IntegerField(
|
||||
label=_("Bit Length"), min_value=0, help_text=LENGTH_HELP_TEXT)
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Key Name"),
|
||||
regex=NAME_REGEX,
|
||||
error_messages=ERROR_MESSAGES)
|
||||
source_type = forms.ChoiceField(
|
||||
label=_('Source'),
|
||||
required=False,
|
||||
choices=[('file', _('Key File')),
|
||||
('raw', _('Direct Input'))],
|
||||
widget=forms.ThemableSelectWidget(
|
||||
attrs={'class': 'switchable', 'data-slug': 'source'}))
|
||||
key_file = forms.FileField(
|
||||
label=_("Choose file"),
|
||||
widget=forms.FileInput(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-file': _('Key File')}),
|
||||
required=False)
|
||||
direct_input = forms.CharField(
|
||||
label=_('Key Value'),
|
||||
widget=forms.widgets.Textarea(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-raw': _('Key Value')}),
|
||||
required=False)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
algorithms = kwargs.pop('algorithms', None)
|
||||
super(ImportKey, self).__init__(request, *args, **kwargs)
|
||||
self.fields['algorithm'].widget = ListTextWidget(data_list=algorithms,
|
||||
name='algorithms')
|
||||
|
||||
@abc.abstractmethod
|
||||
def clean_key_data(self, key_pem):
|
||||
"""This should be implemented for the specific key import form"""
|
||||
return
|
||||
|
||||
def clean(self):
|
||||
data = super(ImportKey, self).clean()
|
||||
|
||||
# The key can be missing based on particular upload
|
||||
# conditions. Code defensively for it here...
|
||||
key_file = data.get('key_file', None)
|
||||
key_raw = data.get('direct_input', None)
|
||||
|
||||
if key_raw and key_file:
|
||||
raise forms.ValidationError(
|
||||
_("Cannot specify both file and direct input."))
|
||||
if not key_raw and not key_file:
|
||||
raise forms.ValidationError(
|
||||
_("No input was provided for the key value."))
|
||||
try:
|
||||
if key_file:
|
||||
key_pem = self.files['key_file'].read()
|
||||
else:
|
||||
key_pem = data['direct_input']
|
||||
|
||||
data['key_data'] = self.clean_key_data(key_pem)
|
||||
|
||||
except Exception as e:
|
||||
msg = _('There was a problem loading the key: %s. '
|
||||
'Is the key valid and in the correct format?') % e
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
return data
|
||||
|
||||
def handle(self, request, data, key_type):
|
||||
try:
|
||||
key_uuid = client.import_object(
|
||||
request,
|
||||
algorithm=data['algorithm'],
|
||||
bit_length=data['bit_length'],
|
||||
key=data['key_data'],
|
||||
name=data['name'],
|
||||
object_type=key_type)
|
||||
|
||||
if data['name']:
|
||||
key_identifier = data['name']
|
||||
else:
|
||||
key_identifier = key_uuid
|
||||
messages.success(request,
|
||||
_('Successfully imported key: %s')
|
||||
% key_identifier)
|
||||
return key_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to import key: %s')
|
||||
messages.error(request, msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to import key.'))
|
||||
return False
|
||||
|
||||
|
||||
class GenerateKeyPair(forms.SelfHandlingForm):
|
||||
algorithm = forms.CharField(
|
||||
label=_("Algorithm"),
|
||||
help_text=ALG_HELP_TEXT,
|
||||
widget=ListTextWidget(
|
||||
data_list=KEY_PAIR_ALGORITHMS, name='algorithm-list'))
|
||||
length = forms.IntegerField(
|
||||
label=_("Bit Length"),
|
||||
min_value=0,
|
||||
help_text=LENGTH_HELP_TEXT)
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Key Name"),
|
||||
regex=NAME_REGEX,
|
||||
error_messages=ERROR_MESSAGES)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
key_uuid = client.generate_key_pair(
|
||||
request,
|
||||
algorithm=data['algorithm'],
|
||||
length=data['length'],
|
||||
name=data['name'])
|
||||
|
||||
if data['name']:
|
||||
key_identifier = data['name']
|
||||
else:
|
||||
key_identifier = key_uuid
|
||||
messages.success(request,
|
||||
_('Successfully generated key pair %s')
|
||||
% key_identifier)
|
||||
return key_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to generate key pair: %s')
|
||||
messages.error(request, msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to generate key pair.'))
|
||||
return False
|
@ -1,96 +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 binascii
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from castellan.common.objects import symmetric_key
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content import shared_forms
|
||||
|
||||
ALGORITHMS = ('AES', 'DES', 'DESEDE')
|
||||
|
||||
ALG_HELP_TEXT = _(
|
||||
"Check which algorithms your key manager supports. "
|
||||
"Some common algorithms are: %s") % ', '.join(ALGORITHMS)
|
||||
LENGTH_HELP_TEXT = _(
|
||||
"Only certain bit lengths are valid for each algorithm. "
|
||||
"Some common bit lengths are: 128, 256")
|
||||
|
||||
|
||||
class ImportSymmetricKey(shared_forms.ImportKey):
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(ImportSymmetricKey, self).__init__(
|
||||
request, *args, algorithms=ALGORITHMS, **kwargs)
|
||||
self.fields['direct_input'].help_text = _(
|
||||
"Key bytes represented as hex characters. Acceptable values are "
|
||||
"0-9, a-f, A-F")
|
||||
self.fields['key_file'].help_text = _(
|
||||
"The file should contain the raw bytes of the key.")
|
||||
|
||||
def clean_key_data(self, key_data):
|
||||
if self.files.get('key_file'):
|
||||
key_bytes = key_data
|
||||
else:
|
||||
key_bytes = binascii.unhexlify(key_data)
|
||||
b64_key_data = base64.b64encode(key_bytes)
|
||||
|
||||
return b64_key_data
|
||||
|
||||
def handle(self, request, data):
|
||||
return super(ImportSymmetricKey, self).handle(
|
||||
request, data, symmetric_key.SymmetricKey)
|
||||
|
||||
|
||||
class GenerateSymmetricKey(forms.SelfHandlingForm):
|
||||
algorithm = forms.CharField(label=_("Algorithm"),
|
||||
widget=shared_forms.ListTextWidget(
|
||||
data_list=ALGORITHMS,
|
||||
name='algorithm-list'),
|
||||
help_text=ALG_HELP_TEXT)
|
||||
length = forms.IntegerField(label=_("Bit Length"), min_value=0,
|
||||
help_text=LENGTH_HELP_TEXT)
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Key Name"),
|
||||
regex=shared_forms.NAME_REGEX,
|
||||
error_messages=shared_forms.ERROR_MESSAGES)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
key_uuid = client.generate_symmetric_key(
|
||||
request,
|
||||
algorithm=data['algorithm'],
|
||||
length=data['length'],
|
||||
name=data['name'])
|
||||
|
||||
if data['name']:
|
||||
key_identifier = data['name']
|
||||
else:
|
||||
key_identifier = key_uuid
|
||||
messages.success(request,
|
||||
_('Successfully generated symmetric key: %s')
|
||||
% key_identifier)
|
||||
return key_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to generate symmetric key: %s')
|
||||
messages.error(request, msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to generate symmetric key.'))
|
||||
return False
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class SymmetricKeys(horizon.Panel):
|
||||
name = _("Symmetric Keys")
|
||||
slug = "symmetric_keys"
|
@ -1,96 +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 castellan_ui.content import filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class GenerateSymmetricKey(tables.LinkAction):
|
||||
name = "generate_symmetric_key"
|
||||
verbose_name = _("Generate Symmetric Key")
|
||||
url = "horizon:project:symmetric_keys:generate"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class ImportSymmetricKey(tables.LinkAction):
|
||||
name = "import_symmetric_key"
|
||||
verbose_name = _("Import Symmetric Key")
|
||||
url = "horizon:project:symmetric_keys:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DownloadKey(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Key")
|
||||
url = "horizon:project:symmetric_keys:download"
|
||||
classes = ("btn-download",)
|
||||
policy_rules = ()
|
||||
|
||||
def get_link_url(self, datum):
|
||||
return reverse(self.url,
|
||||
kwargs={'object_id': datum.id})
|
||||
|
||||
|
||||
class DeleteSymmetricKey(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete a symmetric key unless you are "
|
||||
"certain it is not being used anywhere.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Symmetric Key",
|
||||
u"Delete Symmetric Keys",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Symmetric Key",
|
||||
u"Deleted Symmetric Keys",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class SymmetricKeyTable(tables.DataTable):
|
||||
detail_link = "horizon:project:symmetric_keys:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("Key ID"), link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
algorithm = tables.Column("algorithm", verbose_name=_("Algorithm"))
|
||||
bit_length = tables.Column("bit_length", verbose_name=_("Bit Length"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "symmetric_key"
|
||||
table_actions = (GenerateSymmetricKey,
|
||||
ImportSymmetricKey,
|
||||
DeleteSymmetricKey,)
|
||||
row_actions = (DownloadKey, DeleteSymmetricKey)
|
@ -1,27 +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 castellan_ui.content.symmetric_keys import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^generate/$', views.GenerateView.as_view(), name='generate'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^download/$', views.download_key, name='download'),
|
||||
url(r'^(?P<object_id>[^/]+)/download$',
|
||||
views.download_key,
|
||||
name='download'),
|
||||
]
|
@ -1,133 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import binascii
|
||||
from castellan.common.objects import symmetric_key
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.symmetric_keys import forms as symmetric_key_forms
|
||||
from castellan_ui.content.symmetric_keys import tables
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
def download_key(request, object_id):
|
||||
try:
|
||||
obj = client.get(request, object_id)
|
||||
data = obj.get_encoded()
|
||||
response = HttpResponse()
|
||||
response.write(data)
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename="%s.key"' % object_id)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:symmetric_keys:index')
|
||||
msg = _('Unable to download symmetric_key "%s".')\
|
||||
% (object_id)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.SymmetricKeyTable
|
||||
]
|
||||
template_name = 'symmetric_keys.html'
|
||||
|
||||
def get_symmetric_key_data(self):
|
||||
try:
|
||||
return client.list(self.request,
|
||||
object_type=symmetric_key.SymmetricKey)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list symmetric keys: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class GenerateView(forms.ModalFormView):
|
||||
form_class = symmetric_key_forms.GenerateSymmetricKey
|
||||
template_name = 'symmetric_key_generate.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:symmetric_keys:generate")
|
||||
success_url = reverse_lazy('horizon:project:symmetric_keys:index')
|
||||
submit_label = page_title = _("Generate Symmetric Key")
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = symmetric_key_forms.ImportSymmetricKey
|
||||
template_name = 'symmetric_key_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:symmetric_keys:import")
|
||||
success_url = reverse_lazy('horizon:project:symmetric_keys:index')
|
||||
submit_label = page_title = _("Import Symmetric Key")
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'symmetric_key_detail.html'
|
||||
page_title = _("Symmetric Key Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:symmetric_keys:index')
|
||||
msg = _('Unable to retrieve details for symmetric_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:symmetric_keys:index')
|
||||
msg = _('Unable to retrieve details for symmetric_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
try:
|
||||
data_bytes = binascii.hexlify(obj.get_encoded())
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:symmetric_keys:index')
|
||||
msg = _('Unable to retrieve details for symmetric_key "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return data_bytes
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
return context
|
@ -1,114 +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 django.utils.translation import ugettext_lazy as _
|
||||
import re
|
||||
|
||||
from castellan.common.objects import x_509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.x509 import load_pem_x509_certificate
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from castellan_ui.api import client
|
||||
|
||||
NAME_REGEX = re.compile(r"^\w+(?:[- ]\w+)*$", re.UNICODE)
|
||||
ERROR_MESSAGES = {
|
||||
'invalid': _('Name may only contain letters, '
|
||||
'numbers, underscores, spaces, and hyphens '
|
||||
'and may not be white space.')}
|
||||
|
||||
|
||||
class ImportX509Certificate(forms.SelfHandlingForm):
|
||||
name = forms.RegexField(required=False,
|
||||
max_length=255,
|
||||
label=_("Certificate Name"),
|
||||
regex=NAME_REGEX,
|
||||
error_messages=ERROR_MESSAGES)
|
||||
source_type = forms.ChoiceField(
|
||||
label=_('Source'),
|
||||
required=False,
|
||||
choices=[('file', _('Import File')),
|
||||
('raw', _('Direct Input'))],
|
||||
widget=forms.ThemableSelectWidget(
|
||||
attrs={'class': 'switchable', 'data-slug': 'source'}))
|
||||
cert_file = forms.FileField(
|
||||
label=_("Choose file"),
|
||||
widget=forms.FileInput(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-file': _('PEM Certificate File')}),
|
||||
required=False)
|
||||
direct_input = forms.CharField(
|
||||
label=_('PEM Certificate'),
|
||||
widget=forms.widgets.Textarea(
|
||||
attrs={'class': 'switched', 'data-switch-on': 'source',
|
||||
'data-source-raw': _('PEM Certificate')}),
|
||||
required=False)
|
||||
|
||||
def clean(self):
|
||||
data = super(ImportX509Certificate, self).clean()
|
||||
|
||||
# The cert can be missing based on particular upload
|
||||
# conditions. Code defensively for it here...
|
||||
cert_file = data.get('cert_file', None)
|
||||
cert_raw = data.get('direct_input', None)
|
||||
|
||||
if cert_raw and cert_file:
|
||||
raise forms.ValidationError(
|
||||
_("Cannot specify both file and direct input."))
|
||||
if not cert_raw and not cert_file:
|
||||
raise forms.ValidationError(
|
||||
_("No input was provided for the certificate value."))
|
||||
try:
|
||||
if cert_file:
|
||||
cert_pem = self.files['cert_file'].read()
|
||||
else:
|
||||
cert_pem = str(data['direct_input'])
|
||||
cert_obj = load_pem_x509_certificate(
|
||||
cert_pem.encode('utf-8'), default_backend())
|
||||
cert_der = cert_obj.public_bytes(Encoding.DER)
|
||||
except Exception as e:
|
||||
msg = _('There was a problem loading the certificate: %s. '
|
||||
'Is the certificate valid and in PEM format?') % e
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
data['cert_data'] = base64.b64encode(cert_der).decode('utf-8')
|
||||
|
||||
return data
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
cert_pem = data.get('cert_data')
|
||||
cert_uuid = client.import_object(
|
||||
request,
|
||||
data=cert_pem,
|
||||
name=data['name'],
|
||||
object_type=x_509.X509)
|
||||
|
||||
if data['name']:
|
||||
identifier = data['name']
|
||||
else:
|
||||
identifier = cert_uuid
|
||||
messages.success(request,
|
||||
_('Successfully imported certificate: %s')
|
||||
% identifier)
|
||||
return cert_uuid
|
||||
except Exception as e:
|
||||
msg = _('Unable to import certificate: %s')
|
||||
messages.error(request, msg % e)
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_('Unable to import certificate.'))
|
||||
return False
|
@ -1,23 +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 django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
# This panel will be loaded from horizon, because specified in enabled file.
|
||||
# To register REST api, import below here.
|
||||
from castellan_ui.api import client # noqa: F401
|
||||
|
||||
|
||||
class X509Certificates(horizon.Panel):
|
||||
name = _("X.509 Certificates")
|
||||
slug = "x509_certificates"
|
@ -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 castellan_ui.content import filters
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from castellan_ui.api import client
|
||||
from horizon import tables
|
||||
|
||||
|
||||
class ImportX509Certificate(tables.LinkAction):
|
||||
name = "import_x509_certificate"
|
||||
verbose_name = _("Import Certificate")
|
||||
url = "horizon:project:x509_certificates:import"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "upload"
|
||||
policy_rules = ()
|
||||
|
||||
|
||||
class DownloadX509Certificate(tables.LinkAction):
|
||||
name = "download"
|
||||
verbose_name = _("Download Certificate")
|
||||
url = "horizon:project:x509_certificates:download"
|
||||
classes = ("btn-download",)
|
||||
policy_rules = ()
|
||||
|
||||
def get_link_url(self, datum):
|
||||
return reverse(self.url,
|
||||
kwargs={'object_id': datum.id})
|
||||
|
||||
|
||||
class DeleteX509Certificate(tables.DeleteAction):
|
||||
policy_rules = ()
|
||||
help_text = _("You should not delete a certificate unless you are "
|
||||
"certain it is not being used anywhere.")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete X.509 Certificate",
|
||||
u"Delete X.509 Certificates",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted X.509 Certificate",
|
||||
u"Deleted X.509 Certificates",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
client.delete(request, obj_id)
|
||||
|
||||
|
||||
class X509CertificateTable(tables.DataTable):
|
||||
detail_link = "horizon:project:x509_certificates:detail"
|
||||
uuid = tables.Column("id", verbose_name=_("ID"), link=detail_link)
|
||||
name = tables.Column("name", verbose_name=_("Name"))
|
||||
created_date = tables.Column("created",
|
||||
verbose_name=_("Created Date"),
|
||||
filters=(filters.timestamp_to_iso,))
|
||||
|
||||
def get_object_display(self, datum):
|
||||
return datum.name if datum.name else datum.id
|
||||
|
||||
class Meta(object):
|
||||
name = "x509_certificate"
|
||||
table_actions = (ImportX509Certificate,
|
||||
DeleteX509Certificate,)
|
||||
row_actions = (DownloadX509Certificate, DeleteX509Certificate)
|
@ -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.
|
||||
|
||||
from castellan_ui.content.x509_certificates import views
|
||||
from django.conf.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^import/$', views.ImportView.as_view(), name='import'),
|
||||
url(r'^(?P<object_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^download/$', views.download_cert, name='download'),
|
||||
url(r'^(?P<object_id>[^/]+)/download$',
|
||||
views.download_cert,
|
||||
name='download'),
|
||||
]
|
@ -1,186 +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 django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import binascii
|
||||
from castellan.common.objects import x_509
|
||||
from castellan_ui.api import client
|
||||
from castellan_ui.content.x509_certificates import forms as x509_forms
|
||||
from castellan_ui.content.x509_certificates import tables
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.x509 import load_der_x509_certificate
|
||||
|
||||
from datetime import datetime
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon.tables import views as tables_views
|
||||
from horizon.utils import memoized
|
||||
from horizon import views
|
||||
|
||||
|
||||
def download_cert(request, object_id):
|
||||
try:
|
||||
obj = client.get(request, object_id)
|
||||
der_data = obj.get_encoded()
|
||||
cert_obj = load_der_x509_certificate(der_data, default_backend())
|
||||
data = cert_obj.public_bytes(Encoding.PEM)
|
||||
response = HttpResponse()
|
||||
response.write(data)
|
||||
response['Content-Disposition'] = ('attachment; '
|
||||
'filename="%s.pem"' % object_id)
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
return response
|
||||
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:x509_certificates:index')
|
||||
msg = _('Unable to download x509_certificate "%s".')\
|
||||
% (object_id)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class IndexView(tables_views.MultiTableView):
|
||||
table_classes = [
|
||||
tables.X509CertificateTable
|
||||
]
|
||||
template_name = 'x509_certificates.html'
|
||||
|
||||
def get_x509_certificate_data(self):
|
||||
try:
|
||||
return client.list(self.request, object_type=x_509.X509)
|
||||
except Exception as e:
|
||||
msg = _('Unable to list certificates: "%s".') % (e.message)
|
||||
exceptions.handle(self.request, msg)
|
||||
return []
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = x509_forms.ImportX509Certificate
|
||||
template_name = 'x509_certificate_import.html'
|
||||
submit_url = reverse_lazy(
|
||||
"horizon:project:x509_certificates:import")
|
||||
success_url = reverse_lazy('horizon:project:x509_certificates:index')
|
||||
submit_label = page_title = _("Import X.509 Certificate")
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
if form_class is None:
|
||||
form_class = self.get_form_class()
|
||||
return form_class(self.request, **self.get_form_kwargs())
|
||||
|
||||
def get_object_id(self, key_uuid):
|
||||
return key_uuid
|
||||
|
||||
|
||||
class DetailView(views.HorizonTemplateView):
|
||||
template_name = 'x509_certificate_detail.html'
|
||||
page_title = _("X.509 Certificate Details")
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data(self):
|
||||
try:
|
||||
obj = client.get(self.request, self.kwargs['object_id'])
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:x509_certificates:index')
|
||||
msg = _('Unable to retrieve details for x509_certificate "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return obj
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_created_date(self, obj):
|
||||
try:
|
||||
created_date = datetime.utcfromtimestamp(obj.created).isoformat()
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:x509_certificates:index')
|
||||
msg = _('Unable to retrieve details for x509_certificate "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return created_date
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_crypto_obj(self, obj):
|
||||
der_data = obj.get_encoded()
|
||||
return load_der_x509_certificate(der_data, default_backend())
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_certificate_version(self, obj):
|
||||
return self._get_crypto_obj(obj).version
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_certificate_fingerprint(self, obj):
|
||||
return binascii.hexlify(
|
||||
self._get_crypto_obj(obj).fingerprint(hashes.SHA256()))
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_serial_number(self, obj):
|
||||
return self._get_crypto_obj(obj).serial_number
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_validity_start(self, obj):
|
||||
return self._get_crypto_obj(obj).not_valid_before
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_validity_end(self, obj):
|
||||
return self._get_crypto_obj(obj).not_valid_after
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_issuer(self, obj):
|
||||
result = ""
|
||||
issuer = self._get_crypto_obj(obj).issuer
|
||||
for attribute in issuer:
|
||||
result = (result + str(attribute.oid._name) + "=" +
|
||||
str(attribute.value) + ",")
|
||||
return result[:-1]
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_subject(self, obj):
|
||||
result = ""
|
||||
issuer = self._get_crypto_obj(obj).subject
|
||||
for attribute in issuer:
|
||||
result = (result + str(attribute.oid._name) + "=" +
|
||||
str(attribute.value) + ",")
|
||||
return result[:-1]
|
||||
|
||||
@memoized.memoized_method
|
||||
def _get_data_bytes(self, obj):
|
||||
try:
|
||||
data = self._get_crypto_obj(obj).public_bytes(Encoding.PEM)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:x509_certificates:index')
|
||||
msg = _('Unable to retrieve details for x509_certificate "%s".')\
|
||||
% (self.kwargs['object_id'])
|
||||
exceptions.handle(self.request, msg,
|
||||
redirect=redirect)
|
||||
return data
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Gets the context data for key."""
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
obj = self._get_data()
|
||||
context['object'] = obj
|
||||
context['object_created_date'] = self._get_data_created_date(obj)
|
||||
context['object_bytes'] = self._get_data_bytes(obj)
|
||||
context['cert_version'] = self._get_certificate_version(obj)
|
||||
context['cert_fingerprint'] = self._get_certificate_fingerprint(obj)
|
||||
context['cert_serial_number'] = self._get_serial_number(obj)
|
||||
context['cert_validity_start'] = self._get_validity_start(obj)
|
||||
context['cert_validity_end'] = self._get_validity_end(obj)
|
||||
context['cert_issuer'] = self._get_issuer(obj)
|
||||
context['cert_subject'] = self._get_subject(obj)
|
||||
return context
|
@ -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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# The slug of the panel group to be added to HORIZON_CONFIG. Required.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The display name of the PANEL_GROUP. Required.
|
||||
PANEL_GROUP_NAME = _('Key Manager')
|
||||
# The slug of the dashboard the PANEL_GROUP associated with. Required.
|
||||
PANEL_GROUP_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APPS = ['castellan_ui']
|
||||
|
||||
AUTO_DISCOVER_STATIC_FILES = True
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'x509_certificates'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.x509_certificates.panel.X509Certificates'
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'private_keys'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.private_keys.panel.PrivateKeys'
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'public_keys'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.public_keys.panel.PublicKeys'
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'symmetric_keys'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.symmetric_keys.panel.SymmetricKeys'
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'opaque_data'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.opaque_data.panel.OpaqueData'
|
@ -1,23 +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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'passphrases'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'key_manager'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
|
||||
ADD_INSTALLED_APP = ['castellan_ui', ]
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'castellan_ui.content.passphrases.panel.Passphrases'
|
@ -1,151 +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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function (config) {
|
||||
// This tox venv is setup in the post-install npm step
|
||||
var toxPath = '../.tox/py27/lib/python2.7/site-packages/';
|
||||
|
||||
config.set({
|
||||
preprocessors: {
|
||||
// Used to collect templates for preprocessing.
|
||||
// NOTE: the templates must also be listed in the files section below.
|
||||
'./static/**/*.html': ['ng-html2js'],
|
||||
// Used to indicate files requiring coverage reports.
|
||||
'./static/**/!(*.spec).js': ['coverage'],
|
||||
},
|
||||
|
||||
// Sets up module to process templates.
|
||||
ngHtml2JsPreprocessor: {
|
||||
prependPrefix: '/',
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
basePath: './',
|
||||
|
||||
// Contains both source and test files.
|
||||
files: [
|
||||
/*
|
||||
* shim, partly stolen from /i18n/js/horizon/
|
||||
* Contains expected items not provided elsewhere (dynamically by
|
||||
* Django or via jasmine template.
|
||||
*/
|
||||
'../test-shim.js',
|
||||
|
||||
// from jasmine.html
|
||||
toxPath + 'xstatic/pkg/jquery/data/jquery.js',
|
||||
toxPath + 'xstatic/pkg/angular/data/angular.js',
|
||||
toxPath + 'xstatic/pkg/angular/data/angular-route.js',
|
||||
toxPath + 'xstatic/pkg/angular/data/angular-mocks.js',
|
||||
toxPath + 'xstatic/pkg/angular/data/angular-cookies.js',
|
||||
toxPath + 'xstatic/pkg/angular_bootstrap/data/angular-bootstrap.js',
|
||||
toxPath + 'xstatic/pkg/angular_gettext/data/angular-gettext.js',
|
||||
toxPath + 'xstatic/pkg/angular/data/angular-sanitize.js',
|
||||
toxPath + 'xstatic/pkg/d3/data/d3.js',
|
||||
toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
|
||||
toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
|
||||
toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
|
||||
toxPath + 'xstatic/pkg/spin/data/spin.js',
|
||||
toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
|
||||
toxPath + 'xstatic/pkg/tv4/data/tv4.js',
|
||||
toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js',
|
||||
toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js',
|
||||
toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload.js',
|
||||
|
||||
|
||||
// TODO: These should be mocked.
|
||||
toxPath + '/horizon/static/horizon/js/horizon.js',
|
||||
|
||||
/**
|
||||
* Include framework source code from horizon that we need.
|
||||
* Otherwise, karma will not be able to find them when testing.
|
||||
* These files should be mocked in the foreseeable future.
|
||||
*/
|
||||
toxPath + 'horizon/static/framework/**/*.module.js',
|
||||
toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js',
|
||||
toxPath + 'openstack_dashboard/static/**/*.module.js',
|
||||
toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js',
|
||||
toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js',
|
||||
toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js',
|
||||
|
||||
/**
|
||||
* First, list all the files that defines application's angular modules.
|
||||
* Those files have extension of `.module.js`. The order among them is
|
||||
* not significant.
|
||||
*/
|
||||
'./static/**/*.module.js',
|
||||
|
||||
/**
|
||||
* Followed by other JavaScript files that defines angular providers
|
||||
* on the modules defined in files listed above. And they are not mock
|
||||
* files or spec files defined below. The order among them is not
|
||||
* significant.
|
||||
*/
|
||||
'./static/**/!(*.spec|*.mock).js',
|
||||
|
||||
/**
|
||||
* Then, list files for mocks with `mock.js` extension. The order
|
||||
* among them should not be significant.
|
||||
*/
|
||||
toxPath + 'openstack_dashboard/static/**/*.mock.js',
|
||||
|
||||
/**
|
||||
* Finally, list files for spec with `spec.js` extension. The order
|
||||
* among them should not be significant.
|
||||
*/
|
||||
'./static/**/*.spec.js',
|
||||
|
||||
/**
|
||||
* Angular external templates
|
||||
*/
|
||||
'./static/**/*.html'
|
||||
],
|
||||
|
||||
autoWatch: true,
|
||||
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
browsers: ['Chrome'],
|
||||
|
||||
browserNoActivityTimeout: 60000,
|
||||
|
||||
reporters: ['progress', 'coverage', 'threshold'],
|
||||
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-ng-html2js-preprocessor',
|
||||
'karma-coverage',
|
||||
'karma-threshold-reporter'
|
||||
],
|
||||
|
||||
// Places coverage report in HTML format in the subdirectory below.
|
||||
coverageReporter: {
|
||||
type: 'html',
|
||||
dir: '../cover/karma/'
|
||||
},
|
||||
|
||||
// Coverage threshold values.
|
||||
thresholdReporter: {
|
||||
statements: 10, // target 100
|
||||
branches: 0, // target 100
|
||||
functions: 10, // target 100
|
||||
lines: 10 // target 100
|
||||
}
|
||||
});
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
{% extends "horizon/common/_modal.html" %}
|
||||
{% block content %}
|
||||
{% if table %}
|
||||
<div class="modal-body">
|
||||
{{ table.render }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form id="{% block form_id %}{{ form_id }}{% endblock %}"
|
||||
ng-controller="{% block ng_controller %}DummyController{% endblock %}"
|
||||
name="{% block form_name %}{% endblock %}"
|
||||
autocomplete="{% block autocomplete %}{% if form.no_autocomplete %}off{% endif %}{% endblock %}"
|
||||
class="{% block form_class %}{% endblock %}"
|
||||
action="{% block form_action %}{{ submit_url }}{% endblock %}"
|
||||
method="{% block form-method %}POST{% endblock %}"
|
||||
{% block form_validation %}{% endblock %}
|
||||
{% if add_to_field %}data-add-to-field="{{ add_to_field }}"{% endif %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %}>{% csrf_token %}
|
||||
<div class="modal-body clearfix">
|
||||
{% comment %}
|
||||
These fake fields are required to prevent Chrome v34+ from autofilling form.
|
||||
{% endcomment %}
|
||||
{% if form.no_autocomplete %}
|
||||
<div class="fake_credentials" style="display: none">
|
||||
<input type="text" name="fake_email" value="" />
|
||||
<input type="password" name="fake_password" value="" />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block modal-body %}
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
{% block modal-body-right %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{% block modal-footer %}
|
||||
{% if cancel_url %}
|
||||
<a href="{% block cancel_url %}{{ cancel_url }}{% endblock %}"
|
||||
class="btn btn-default cancel">
|
||||
{{ cancel_label }}
|
||||
</a>
|
||||
{% endif %}
|
||||
<input class="btn btn-primary" type="submit" value="{{ submit_label }}">
|
||||
{% endblock %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% load i18n %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "When importing your object as a file, the raw bytes of the file will be the raw bytes of the object. If you open the file using a text editor, it may not be human-readable because the bytes may not map to ASCII characters." %}</p>
|
||||
<p>{% trans "To import your object using direct input, use the hex dump of the value of the object. For example, it may look like this:" %}</p>
|
||||
<p><pre>00112233445566778899aabbccddeeff</pre></p>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Enter the passphrase as you would type it on on the command line or into a form." %}</p>
|
||||
{% endblock %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Check your key manager to see which algorithms and bit lengths are supported." %}</p>
|
||||
<p>{% trans "A key pair consists of a private key and a public key. When you generate a private key, the public key will also be generated, and vice versa." %}</p>
|
||||
<p>{% trans "You can find the corresponding public key on the " %}<a href="{% url 'horizon:project:public_keys:index' %}">Public Keys</a> {% trans "page." %}</p>
|
||||
{% endblock %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Private keys can be imported if they are in Privacy Enhanced Mail (PEM) format." %}</p>
|
||||
<p>{% trans "Your PEM formatted key will look something like this:" %}</p>
|
||||
<p><pre>-----BEGIN PRIVATE KEY-----<br><base64-encoded data><br>-----END PRIVATE KEY-----</pre></p>
|
||||
{% endblock %}
|
||||
|
@ -1,10 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Check your key manager to see which algorithms and bit lengths are supported." %}</p>
|
||||
<p>{% trans "A key pair consists of a private key and a public key. When you generate a public key, the private key will also be generated, and vice versa." %}</p>
|
||||
<p>{% trans "You can find the corresponding private key on the " %}<a href="{% url 'horizon:project:private_keys:index' %}">Private Keys</a> {% trans "page." %}</p>
|
||||
|
||||
{% endblock %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Public keys can be imported if they are in Privacy Enhanced Mail (PEM) format." %}</p>
|
||||
<p>{% trans "Your PEM formatted key will look something like this:" %}</p>
|
||||
<p><pre>-----BEGIN PUBLIC KEY-----<br><base64-encoded data><br>-----END PUBLIC KEY-----</pre></p>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "Check your key manager to see which algorithms and bit lengths are supported." %}</p>
|
||||
{% endblock %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "When importing your key as a file, the raw bytes of the file will be the raw bytes of the key. If you open the key file using a text editor, it will not be human-readable because the bytes may not map to ASCII characters." %}</p>
|
||||
<p>{% trans "To import your key using direct input, use the hex dump of the value of the key. For example, a 128 bit key may look like this:" %}</p>
|
||||
<p><pre>00112233445566778899aabbccddeeff</pre></p>
|
||||
{% endblock %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{% extends '_object_import.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<p>{% trans "X.509 certificates can be imported if they are in Privacy Enhanced Mail (PEM) format." %}</p>
|
||||
<p>{% trans "Your PEM formatted certificate will look like this:" %}</p>
|
||||
<p><pre>-----BEGIN CERTIFICATE-----<br><base64-encoded data><br>-----END CERTIFICATE-----</pre></p>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/key_pairs/_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Opaque Data" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "Opaque Data" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Opaque Data" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ opaque_data_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,24 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Object Value (in hex)" %}</dt>
|
||||
<dd>
|
||||
<div class="key-text word-wrap">{{ object_bytes|default:_("None") }}</div>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_opaque_data_import.html' %}
|
||||
{% endblock %}
|
@ -1,49 +0,0 @@
|
||||
<style>
|
||||
.hidden{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.visible{
|
||||
display:block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
function unhide(clickedButton, divID) {
|
||||
var item = document.getElementById(divID);
|
||||
if (item) {
|
||||
if(item.className=='hidden'){
|
||||
item.className = 'visible' ;
|
||||
clickedButton.textContent = 'hide'
|
||||
}else{
|
||||
item.className = 'hidden';
|
||||
clickedButton.textContent = 'show'
|
||||
}
|
||||
}}
|
||||
|
||||
</script>
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Passphrase" %}</dt>
|
||||
<dd>
|
||||
<div id="passphrase" class="hidden">{{ object_bytes }}</div>
|
||||
<button class="btn" onclick="unhide(this, 'passphrase') ">show</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_passphrase_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Passphrases" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "Passphrases" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Passphrases" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ passphrase_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,27 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Algorithm" %}</dt>
|
||||
<dd>{{ object.algorithm|default:_("None") }}</dd>
|
||||
<dt>{% trans "Bit Length" %}</dt>
|
||||
<dd>{{ object.bit_length|default:_("None") }}</dd>
|
||||
<dt>{% trans "Key" %}</dt>
|
||||
<dd>
|
||||
<div style="white-space: pre-wrap; font-family: monospace;">{{ object_bytes|default:_("None") }}</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_private_key_generate.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_private_key_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Private Keys" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "Private Keys" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Private Keys" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ private_key_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,27 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Algorithm" %}</dt>
|
||||
<dd>{{ object.algorithm|default:_("None") }}</dd>
|
||||
<dt>{% trans "Bit Length" %}</dt>
|
||||
<dd>{{ object.bit_length|default:_("None") }}</dd>
|
||||
<dt>{% trans "Key" %}</dt>
|
||||
<dd>
|
||||
<div style="white-space: pre-wrap; font-family: monospace;">{{ object_bytes|default:_("None") }}</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_public_key_generate.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_public_key_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Public Keys" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "Public Keys" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Public Keys" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ public_key_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,27 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Algorithm" %}</dt>
|
||||
<dd>{{ object.algorithm|default:_("None") }}</dd>
|
||||
<dt>{% trans "Bit Length" %}</dt>
|
||||
<dd>{{ object.bit_length|default:_("None") }}</dd>
|
||||
<dt>{% trans "Key Value (in hex)" %}</dt>
|
||||
<dd>
|
||||
<div class="key-text word-wrap">{{ object_bytes|default:_("None") }}</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_symmetric_key_generate.html' %}
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_symmetric_key_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Symmetric Keys" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "Symmetric Keys" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Symmetric Keys" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ symmetric_key_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,37 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n parse_date %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ object.name|default:_("None") }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
<dd>{{ object_created_date|parse_date}}</dd>
|
||||
<dt>{% trans "Certificate Version" %}</dt>
|
||||
<dd>{{ cert_version}}</dd>
|
||||
<dt>{% trans "SHA-256 Fingerprint" %}</dt>
|
||||
<dd>{{ cert_fingerprint}}</dd>
|
||||
<dt>{% trans "Serial Number" %}</dt>
|
||||
<dd>{{ cert_serial_number}}</dd>
|
||||
<dt>{% trans "Valid From" %}</dt>
|
||||
<dd>{{ cert_validity_start }}</dd>
|
||||
<dt>{% trans "Valid To" %}</dt>
|
||||
<dd>{{ cert_validity_end }}</dd>
|
||||
<dt>{% trans "Issuer" %}</dt>
|
||||
<dd>{{ cert_issuer}}</dd>
|
||||
<dt>{% trans "Subject" %}</dt>
|
||||
<dd>{{ cert_subject}}</dd>
|
||||
<dt>{% trans "Raw Certificate Value" %}</dt>
|
||||
<dd>
|
||||
<div style="white-space: pre-wrap; font-family: monospace">{{ object_bytes|default:_("None") }}</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include '_x509_certificate_import.html' %}
|
||||
{% endblock %}
|
@ -1,23 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "X.509 Certificates" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class = "breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Key Manager" %}</li>
|
||||
<li class="active">{% trans "X.509 Certificates" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "X.509 Certificates" %}"></hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ x509_certificate_table.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,188 +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 mock
|
||||
import uuid
|
||||
|
||||
from castellan.common.objects import symmetric_key
|
||||
from castellan_ui.api import client as api
|
||||
from castellan_ui.test import helpers as base
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
|
||||
class ClientApiTests(base.APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(self.__class__, self).setUp()
|
||||
self.ctxt = api.get_context(self.request)
|
||||
|
||||
def test_get_auth_params_from_request(self):
|
||||
token, tenant = api.get_auth_params_from_request(self.request)
|
||||
self.assertEqual(self.token.id, token)
|
||||
self.assertEqual(self.tenant.id, tenant)
|
||||
|
||||
def test_get_context(self):
|
||||
ctxt = api.get_context(self.request)
|
||||
self.assertEqual(ctxt.auth_token, self.token.id)
|
||||
self.assertEqual(ctxt.tenant, self.tenant.id)
|
||||
self.assertEqual(ctxt, self.ctxt)
|
||||
|
||||
def test_import_object(self):
|
||||
algorithm = "AES"
|
||||
bit_length = 48
|
||||
name = None
|
||||
key = b'deadbeef'
|
||||
key_b64 = base64.b64encode(key)
|
||||
actual_uuid = api.import_object(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
bit_length=bit_length,
|
||||
name=name,
|
||||
key=key_b64,
|
||||
object_type=symmetric_key.SymmetricKey)
|
||||
self.key_manager.store.assert_called_once()
|
||||
args, kwargs = self.key_manager.store.call_args
|
||||
actual_ctxt, actual_key = args
|
||||
self.assertEqual(actual_uuid, self.mock_uuid1)
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_key.algorithm, algorithm)
|
||||
self.assertEqual(actual_key.bit_length, bit_length)
|
||||
self.assertEqual(actual_key.name, name)
|
||||
self.assertEqual(actual_key.get_encoded(), key)
|
||||
|
||||
def test_import_object_includes_invalid_param(self):
|
||||
algorithm = "AES"
|
||||
bit_length = 256
|
||||
name = None
|
||||
key = b'deadbeef'
|
||||
other_value = "other_value"
|
||||
with self.assertRaises(exceptions.BadRequest):
|
||||
api.import_object(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
bit_length=bit_length,
|
||||
name=name,
|
||||
key=key,
|
||||
object_type=symmetric_key.SymmetricKey,
|
||||
other_value=other_value)
|
||||
|
||||
def test_generate_symmetric_key(self):
|
||||
algorithm = "AES"
|
||||
length = 256
|
||||
name = None
|
||||
actual_uuid = api.generate_symmetric_key(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
length=length,
|
||||
name=name)
|
||||
self.key_manager.create_key.assert_called_once()
|
||||
args, kwargs = self.key_manager.create_key.call_args
|
||||
(actual_ctxt,) = args
|
||||
actual_algorithm = kwargs.get("algorithm")
|
||||
actual_length = kwargs.get("length")
|
||||
actual_name = kwargs.get("name")
|
||||
self.assertEqual(actual_uuid, self.mock_uuid1)
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_algorithm, algorithm)
|
||||
self.assertEqual(actual_length, length)
|
||||
self.assertEqual(actual_name, name)
|
||||
|
||||
def test_generate_symmetric_key_includes_invalid_param(self):
|
||||
algorithm = "AES"
|
||||
length = 256
|
||||
name = None
|
||||
other_value = "other_value"
|
||||
with self.assertRaises(exceptions.BadRequest):
|
||||
api.generate_symmetric_key(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
length=length,
|
||||
name=name,
|
||||
other_value=other_value)
|
||||
|
||||
def test_generate_key_pair(self):
|
||||
algorithm = "RSA"
|
||||
length = 2048
|
||||
name = None
|
||||
actual_result = api.generate_key_pair(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
length=length,
|
||||
name=name)
|
||||
self.key_manager.create_key_pair.assert_called_once()
|
||||
args, kwargs = self.key_manager.create_key_pair.call_args
|
||||
(actual_ctxt,) = args
|
||||
actual_algorithm = kwargs.get("algorithm")
|
||||
actual_length = kwargs.get("length")
|
||||
actual_name = kwargs.get("name")
|
||||
self.assertTrue(self.mock_uuid1 in actual_result)
|
||||
self.assertTrue(self.mock_uuid2 in actual_result)
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_algorithm, algorithm)
|
||||
self.assertEqual(actual_length, length)
|
||||
self.assertEqual(actual_name, name)
|
||||
|
||||
def test_generate_key_pair_invalid_param(self):
|
||||
algorithm = "RSA"
|
||||
length = 2048
|
||||
name = None
|
||||
other_value = "other_value"
|
||||
with self.assertRaises(exceptions.BadRequest):
|
||||
api.generate_key_pair(
|
||||
self.request,
|
||||
algorithm=algorithm,
|
||||
length=length,
|
||||
name=name,
|
||||
other_value=other_value)
|
||||
|
||||
def test_delete(self):
|
||||
object_id = str(uuid.uuid4())
|
||||
api.delete(self.request, object_id)
|
||||
self.key_manager.delete.assert_called_once()
|
||||
args, kwargs = self.key_manager.delete.call_args
|
||||
actual_ctxt, actual_id = args
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_id, object_id)
|
||||
|
||||
def test_list(self):
|
||||
api.list(self.request)
|
||||
self.key_manager.list.assert_called_once()
|
||||
args, kwargs = self.key_manager.list.call_args
|
||||
(actual_ctxt,) = args
|
||||
actual_object_type = kwargs.get("object_type")
|
||||
actual_metadata_only = kwargs.get("metadata_only")
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertIsNone(actual_object_type)
|
||||
self.assertTrue(actual_metadata_only)
|
||||
|
||||
def test_list_with_type(self):
|
||||
object_type = mock.Mock()
|
||||
api.list(self.request, object_type=object_type)
|
||||
self.key_manager.list.assert_called_once()
|
||||
args, kwargs = self.key_manager.list.call_args
|
||||
(actual_ctxt,) = args
|
||||
actual_object_type = kwargs.get("object_type")
|
||||
actual_metadata_only = kwargs.get("metadata_only")
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_object_type, object_type)
|
||||
self.assertTrue(actual_metadata_only)
|
||||
|
||||
def test_get(self):
|
||||
object_id = str(uuid.uuid4())
|
||||
api.get(self.request, object_id)
|
||||
self.key_manager.get.assert_called_once()
|
||||
args, kwargs = self.key_manager.get.call_args
|
||||
actual_ctxt, actual_id = args
|
||||
self.assertEqual(actual_ctxt, self.ctxt)
|
||||
self.assertEqual(actual_id, object_id)
|
@ -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 binascii
|
||||
from django.core.handlers import wsgi
|
||||
from django.core.urlresolvers import reverse
|
||||
from horizon import messages as horizon_messages
|
||||
import mock
|
||||
|
||||
from castellan.common.objects import opaque_data
|
||||
from castellan_ui.api import client as api_castellan
|
||||
from castellan_ui.test import helpers as tests
|
||||
from castellan_ui.test import test_data
|
||||
|
||||
INDEX_URL = reverse('horizon:project:opaque_data:index')
|
||||
|
||||
|
||||
class OpaqueDataViewTest(tests.APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(OpaqueDataViewTest, self).setUp()
|
||||
self.data = test_data.opaque_data
|
||||
self.data_b64_bytes = base64.b64encode(self.data.get_encoded())
|
||||
self.mock_object(
|
||||
api_castellan, "get", mock.Mock(return_value=self.data))
|
||||
self.mock_object(api_castellan, "list", mock.Mock(return_value=[]))
|
||||
self.mock_object(horizon_messages, "success")
|
||||
FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'}
|
||||
self.request = wsgi.WSGIRequest(FAKE_ENVIRON)
|
||||
|
||||
def test_index(self):
|
||||
data_list = [test_data.opaque_data, test_data.nameless_opaque_data]
|
||||
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=data_list))
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'opaque_data.html')
|
||||
api_castellan.list.assert_called_with(
|
||||
mock.ANY, object_type=opaque_data.OpaqueData)
|
||||
|
||||
def test_detail_view(self):
|
||||
url = reverse('horizon:project:opaque_data:detail',
|
||||
args=[self.data.id])
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.data]))
|
||||
self.mock_object(
|
||||
api_castellan, "get", mock.Mock(return_value=self.data))
|
||||
|
||||
res = self.client.get(url)
|
||||
self.assertContains(
|
||||
res, "<dt>Name</dt>\n <dd>%s</dd>" % self.data.name, 1, 200)
|
||||
api_castellan.get.assert_called_once_with(mock.ANY, self.data.id)
|
||||
|
||||
def test_import_data(self):
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.data]))
|
||||
url = reverse('horizon:project:opaque_data:import')
|
||||
self.mock_object(
|
||||
api_castellan, "import_object", mock.Mock(return_value=self.data))
|
||||
|
||||
data_input = (
|
||||
binascii.hexlify(self.data.get_encoded()).decode('utf-8')
|
||||
)
|
||||
|
||||
data_form_data = {
|
||||
'source_type': 'raw',
|
||||
'name': self.data.name,
|
||||
'direct_input': data_input,
|
||||
}
|
||||
|
||||
self.client.post(url, data_form_data)
|
||||
|
||||
api_castellan.import_object.assert_called_once_with(
|
||||
mock.ANY,
|
||||
object_type=opaque_data.OpaqueData,
|
||||
data=self.data_b64_bytes,
|
||||
name=self.data.name,
|
||||
)
|
||||
|
||||
def test_delete_data(self):
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.data]))
|
||||
self.mock_object(api_castellan, "delete")
|
||||
|
||||
data_form_data = {
|
||||
'action': 'opaque_data__delete__%s' % self.data.id
|
||||
}
|
||||
|
||||
res = self.client.post(INDEX_URL, data_form_data)
|
||||
|
||||
api_castellan.list.assert_called_with(
|
||||
mock.ANY, object_type=opaque_data.OpaqueData)
|
||||
api_castellan.delete.assert_called_once_with(
|
||||
mock.ANY,
|
||||
self.data.id,
|
||||
)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
@ -1,112 +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 django.core.handlers import wsgi
|
||||
from django.core.urlresolvers import reverse
|
||||
from horizon import messages as horizon_messages
|
||||
import mock
|
||||
|
||||
from castellan.common.objects import passphrase
|
||||
from castellan_ui.api import client as api_castellan
|
||||
from castellan_ui.test import helpers as tests
|
||||
from castellan_ui.test import test_data
|
||||
|
||||
INDEX_URL = reverse('horizon:project:passphrases:index')
|
||||
|
||||
|
||||
class PassphrasesViewTest(tests.APITestCase):
|
||||
|
||||
class FakeCert(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
super(PassphrasesViewTest, self).setUp()
|
||||
self.passphrase = test_data.passphrase
|
||||
self.mock_object(
|
||||
api_castellan, "get", mock.Mock(return_value=self.passphrase))
|
||||
self.mock_object(api_castellan, "list", mock.Mock(return_value=[]))
|
||||
self.mock_object(horizon_messages, "success")
|
||||
FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'}
|
||||
self.request = wsgi.WSGIRequest(FAKE_ENVIRON)
|
||||
|
||||
def test_index(self):
|
||||
passphrase_list = [test_data.passphrase, test_data.nameless_passphrase]
|
||||
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=passphrase_list))
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'passphrases.html')
|
||||
api_castellan.list.assert_called_with(
|
||||
mock.ANY, object_type=passphrase.Passphrase)
|
||||
|
||||
def test_detail_view(self):
|
||||
url = reverse('horizon:project:passphrases:detail',
|
||||
args=[self.passphrase.id])
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.passphrase]))
|
||||
self.mock_object(
|
||||
api_castellan, "get", mock.Mock(return_value=self.passphrase))
|
||||
|
||||
res = self.client.get(url)
|
||||
self.assertContains(
|
||||
res, "<dt>Name</dt>\n <dd>%s</dd>" % self.passphrase.name,
|
||||
1, 200)
|
||||
api_castellan.get.assert_called_once_with(mock.ANY, self.passphrase.id)
|
||||
|
||||
def test_import_cert(self):
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.passphrase]))
|
||||
url = reverse('horizon:project:passphrases:import')
|
||||
self.mock_object(
|
||||
api_castellan, "import_object", mock.Mock(
|
||||
return_value=self.passphrase))
|
||||
|
||||
passphrase_input = (
|
||||
self.passphrase.get_encoded()
|
||||
)
|
||||
|
||||
passphrase_form_data = {
|
||||
'source_type': 'raw',
|
||||
'name': self.passphrase.name,
|
||||
'direct_input': passphrase_input
|
||||
}
|
||||
|
||||
self.client.post(url, passphrase_form_data)
|
||||
|
||||
api_castellan.import_object.assert_called_once_with(
|
||||
mock.ANY,
|
||||
object_type=passphrase.Passphrase,
|
||||
passphrase=self.passphrase.get_encoded(),
|
||||
name=self.passphrase.name
|
||||
)
|
||||
|
||||
def test_delete_cert(self):
|
||||
self.mock_object(
|
||||
api_castellan, "list", mock.Mock(return_value=[self.passphrase]))
|
||||
self.mock_object(api_castellan, "delete")
|
||||
|
||||
passphrase_form_data = {
|
||||
'action': 'passphrase__delete__%s' % self.passphrase.id
|
||||
}
|
||||
|
||||
res = self.client.post(INDEX_URL, passphrase_form_data)
|
||||
|
||||
api_castellan.list.assert_called_with(
|
||||
mock.ANY, object_type=passphrase.Passphrase)
|
||||
api_castellan.delete.assert_called_once_with(
|
||||
mock.ANY,
|
||||
self.passphrase.id,
|
||||
)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user