summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaitlin Farr <kaitlin.farr@jhuapl.edu>2017-08-22 16:31:54 -0400
committerBrianna Poulos <Brianna.Poulos@jhuapl.edu>2018-02-27 21:27:11 +0000
commit299eb23fa4a7917d1e8818c5516df9e0cb8e21b5 (patch)
treec799c41549c937dc1d9acc1ac7dd9cb5e7f00b50
parent32c35f6f20bc76fcaa4c77ccc9c7dc31bfd57eca (diff)
Add Symmetric Keys Panel
Notes
Notes (review): Code-Review+2: Brianna Poulos <Brianna.Poulos@jhuapl.edu> Workflow+1: Brianna Poulos <Brianna.Poulos@jhuapl.edu> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 27 Feb 2018 21:43:15 +0000 Reviewed-on: https://review.openstack.org/500940 Project: openstack/castellan-ui Branch: refs/heads/master
-rw-r--r--README.rst1
-rw-r--r--castellan_ui/content/symmetric_keys/__init__.py0
-rw-r--r--castellan_ui/content/symmetric_keys/forms.py96
-rw-r--r--castellan_ui/content/symmetric_keys/panel.py23
-rw-r--r--castellan_ui/content/symmetric_keys/tables.py96
-rw-r--r--castellan_ui/content/symmetric_keys/urls.py27
-rw-r--r--castellan_ui/content/symmetric_keys/views.py133
-rw-r--r--castellan_ui/enabled/_94_project_key_manager_symmetric_keys_panel.py23
-rw-r--r--castellan_ui/templates/_symmetric_key_generate.html7
-rw-r--r--castellan_ui/templates/_symmetric_key_import.html9
-rw-r--r--castellan_ui/templates/import_symmetric_key.html7
-rw-r--r--castellan_ui/templates/symmetric_key_detail.html27
-rw-r--r--castellan_ui/templates/symmetric_key_generate.html7
-rw-r--r--castellan_ui/templates/symmetric_key_import.html7
-rw-r--r--castellan_ui/templates/symmetric_keys.html23
-rw-r--r--castellan_ui/test/content/symmetric_keys/__init__.py0
-rw-r--r--castellan_ui/test/content/symmetric_keys/tests.py136
-rw-r--r--castellan_ui/test/test_data.py16
-rw-r--r--tox.ini2
19 files changed, 639 insertions, 1 deletions
diff --git a/README.rst b/README.rst
index 060bd19..e747721 100644
--- a/README.rst
+++ b/README.rst
@@ -57,6 +57,7 @@ And enable it in Horizon::
57 ln -s ../castellan-ui/castellan_ui/enabled/_91_project_key_manager_x509_certificates_panel.py openstack_dashboard/local/enabled 57 ln -s ../castellan-ui/castellan_ui/enabled/_91_project_key_manager_x509_certificates_panel.py openstack_dashboard/local/enabled
58 ln -s ../castellan-ui/castellan_ui/enabled/_92_project_key_manager_private_key_panel.py openstack_dashboard/local/enabled 58 ln -s ../castellan-ui/castellan_ui/enabled/_92_project_key_manager_private_key_panel.py openstack_dashboard/local/enabled
59 ln -s ../castellan-ui/castellan_ui/enabled/_93_project_key_manager_public_key_panel.py openstack_dashboard/local/enabled 59 ln -s ../castellan-ui/castellan_ui/enabled/_93_project_key_manager_public_key_panel.py openstack_dashboard/local/enabled
60 ln -s ../castellan-ui/castellan_ui/enabled/_93_project_key_manager_symmetric_key_panel.py openstack_dashboard/local/enabled
60 61
61To run horizon with the newly enabled Castellan UI plugin run:: 62To run horizon with the newly enabled Castellan UI plugin run::
62 63
diff --git a/castellan_ui/content/symmetric_keys/__init__.py b/castellan_ui/content/symmetric_keys/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/__init__.py
diff --git a/castellan_ui/content/symmetric_keys/forms.py b/castellan_ui/content/symmetric_keys/forms.py
new file mode 100644
index 0000000..ede157f
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/forms.py
@@ -0,0 +1,96 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13
14import base64
15import binascii
16from django.utils.translation import ugettext_lazy as _
17
18from castellan.common.objects import symmetric_key
19from horizon import exceptions
20from horizon import forms
21from horizon import messages
22
23from castellan_ui.api import client
24from castellan_ui.content import shared_forms
25
26ALGORITHMS = ('AES', 'DES', 'DESEDE')
27
28ALG_HELP_TEXT = _(
29 "Check which algorithms your key manager supports. "
30 "Some common algorithms are: %s") % ', '.join(ALGORITHMS)
31LENGTH_HELP_TEXT = _(
32 "Only certain bit lengths are valid for each algorithm. "
33 "Some common bit lengths are: 128, 256")
34
35
36class ImportSymmetricKey(shared_forms.ImportKey):
37
38 def __init__(self, request, *args, **kwargs):
39 super(ImportSymmetricKey, self).__init__(
40 request, *args, algorithms=ALGORITHMS, **kwargs)
41 self.fields['direct_input'].help_text = _(
42 "Key bytes represented as hex characters. Acceptable values are "
43 "0-9, a-f, A-F")
44 self.fields['key_file'].help_text = _(
45 "The file should contain the raw bytes of the key.")
46
47 def clean_key_data(self, key_data):
48 if self.files.get('key_file'):
49 key_bytes = key_data
50 else:
51 key_bytes = binascii.unhexlify(key_data)
52 b64_key_data = base64.b64encode(key_bytes)
53
54 return b64_key_data
55
56 def handle(self, request, data):
57 return super(ImportSymmetricKey, self).handle(
58 request, data, symmetric_key.SymmetricKey)
59
60
61class GenerateSymmetricKey(forms.SelfHandlingForm):
62 algorithm = forms.CharField(label=_("Algorithm"),
63 widget=shared_forms.ListTextWidget(
64 data_list=ALGORITHMS,
65 name='algorithm-list'),
66 help_text=ALG_HELP_TEXT)
67 length = forms.IntegerField(label=_("Bit Length"), min_value=0,
68 help_text=LENGTH_HELP_TEXT)
69 name = forms.RegexField(required=False,
70 max_length=255,
71 label=_("Key Name"),
72 regex=shared_forms.NAME_REGEX,
73 error_messages=shared_forms.ERROR_MESSAGES)
74
75 def handle(self, request, data):
76 try:
77 key_uuid = client.generate_symmetric_key(
78 request,
79 algorithm=data['algorithm'],
80 length=data['length'],
81 name=data['name'])
82
83 if data['name']:
84 key_identifier = data['name']
85 else:
86 key_identifier = key_uuid
87 messages.success(request,
88 _('Successfully generated symmetric key: %s')
89 % key_identifier)
90 return key_uuid
91 except Exception as e:
92 msg = _('Unable to generate symmetric key: %s')
93 messages.error(request, msg % e)
94 exceptions.handle(request, ignore=True)
95 self.api_error(_('Unable to generate symmetric key.'))
96 return False
diff --git a/castellan_ui/content/symmetric_keys/panel.py b/castellan_ui/content/symmetric_keys/panel.py
new file mode 100644
index 0000000..6b879e3
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/panel.py
@@ -0,0 +1,23 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from django.utils.translation import ugettext_lazy as _
14import horizon
15
16# This panel will be loaded from horizon, because specified in enabled file.
17# To register REST api, import below here.
18from castellan_ui.api import client # noqa: F401
19
20
21class SymmetricKeys(horizon.Panel):
22 name = _("Symmetric Keys")
23 slug = "symmetric_keys"
diff --git a/castellan_ui/content/symmetric_keys/tables.py b/castellan_ui/content/symmetric_keys/tables.py
new file mode 100644
index 0000000..6e4eda0
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/tables.py
@@ -0,0 +1,96 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13
14from castellan_ui.content import filters
15from django.core.urlresolvers import reverse
16from django.utils.translation import ugettext_lazy as _
17from django.utils.translation import ungettext_lazy
18
19from castellan_ui.api import client
20from horizon import tables
21
22
23class GenerateSymmetricKey(tables.LinkAction):
24 name = "generate_symmetric_key"
25 verbose_name = _("Generate Symmetric Key")
26 url = "horizon:project:symmetric_keys:generate"
27 classes = ("ajax-modal",)
28 icon = "plus"
29 policy_rules = ()
30
31
32class ImportSymmetricKey(tables.LinkAction):
33 name = "import_symmetric_key"
34 verbose_name = _("Import Symmetric Key")
35 url = "horizon:project:symmetric_keys:import"
36 classes = ("ajax-modal",)
37 icon = "upload"
38 policy_rules = ()
39
40
41class DownloadKey(tables.LinkAction):
42 name = "download"
43 verbose_name = _("Download Key")
44 url = "horizon:project:symmetric_keys:download"
45 classes = ("btn-download",)
46 policy_rules = ()
47
48 def get_link_url(self, datum):
49 return reverse(self.url,
50 kwargs={'object_id': datum.id})
51
52
53class DeleteSymmetricKey(tables.DeleteAction):
54 policy_rules = ()
55 help_text = _("You should not delete a symmetric key unless you are "
56 "certain it is not being used anywhere.")
57
58 @staticmethod
59 def action_present(count):
60 return ungettext_lazy(
61 u"Delete Symmetric Key",
62 u"Delete Symmetric Keys",
63 count
64 )
65
66 @staticmethod
67 def action_past(count):
68 return ungettext_lazy(
69 u"Deleted Symmetric Key",
70 u"Deleted Symmetric Keys",
71 count
72 )
73
74 def delete(self, request, obj_id):
75 client.delete(request, obj_id)
76
77
78class SymmetricKeyTable(tables.DataTable):
79 detail_link = "horizon:project:symmetric_keys:detail"
80 uuid = tables.Column("id", verbose_name=_("Key ID"), link=detail_link)
81 name = tables.Column("name", verbose_name=_("Name"))
82 algorithm = tables.Column("algorithm", verbose_name=_("Algorithm"))
83 bit_length = tables.Column("bit_length", verbose_name=_("Bit Length"))
84 created_date = tables.Column("created",
85 verbose_name=_("Created Date"),
86 filters=(filters.timestamp_to_iso,))
87
88 def get_object_display(self, datum):
89 return datum.name if datum.name else datum.id
90
91 class Meta(object):
92 name = "symmetric_key"
93 table_actions = (GenerateSymmetricKey,
94 ImportSymmetricKey,
95 DeleteSymmetricKey,)
96 row_actions = (DownloadKey, DeleteSymmetricKey)
diff --git a/castellan_ui/content/symmetric_keys/urls.py b/castellan_ui/content/symmetric_keys/urls.py
new file mode 100644
index 0000000..96cd488
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/urls.py
@@ -0,0 +1,27 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from castellan_ui.content.symmetric_keys import views
14from django.conf.urls import url
15
16urlpatterns = [
17 url(r'^$', views.IndexView.as_view(), name='index'),
18 url(r'^import/$', views.ImportView.as_view(), name='import'),
19 url(r'^generate/$', views.GenerateView.as_view(), name='generate'),
20 url(r'^(?P<object_id>[^/]+)/$',
21 views.DetailView.as_view(),
22 name='detail'),
23 url(r'^download/$', views.download_key, name='download'),
24 url(r'^(?P<object_id>[^/]+)/download$',
25 views.download_key,
26 name='download'),
27]
diff --git a/castellan_ui/content/symmetric_keys/views.py b/castellan_ui/content/symmetric_keys/views.py
new file mode 100644
index 0000000..4d95a58
--- /dev/null
+++ b/castellan_ui/content/symmetric_keys/views.py
@@ -0,0 +1,133 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from django.core.urlresolvers import reverse
14from django.core.urlresolvers import reverse_lazy
15from django.http import HttpResponse
16from django.utils.translation import ugettext_lazy as _
17
18import binascii
19from castellan.common.objects import symmetric_key
20from castellan_ui.api import client
21from castellan_ui.content.symmetric_keys import forms as symmetric_key_forms
22from castellan_ui.content.symmetric_keys import tables
23from datetime import datetime
24from horizon import exceptions
25from horizon import forms
26from horizon.tables import views as tables_views
27from horizon.utils import memoized
28from horizon import views
29
30
31def download_key(request, object_id):
32 try:
33 obj = client.get(request, object_id)
34 data = obj.get_encoded()
35 response = HttpResponse()
36 response.write(data)
37 response['Content-Disposition'] = ('attachment; '
38 'filename="%s.key"' % object_id)
39 response['Content-Length'] = str(len(response.content))
40 return response
41
42 except Exception:
43 redirect = reverse('horizon:project:symmetric_keys:index')
44 msg = _('Unable to download symmetric_key "%s".')\
45 % (object_id)
46 exceptions.handle(request, msg, redirect=redirect)
47
48
49class IndexView(tables_views.MultiTableView):
50 table_classes = [
51 tables.SymmetricKeyTable
52 ]
53 template_name = 'symmetric_keys.html'
54
55 def get_symmetric_key_data(self):
56 try:
57 return client.list(self.request,
58 object_type=symmetric_key.SymmetricKey)
59 except Exception as e:
60 msg = _('Unable to list symmetric keys: "%s".') % (e.message)
61 exceptions.handle(self.request, msg)
62 return []
63
64
65class GenerateView(forms.ModalFormView):
66 form_class = symmetric_key_forms.GenerateSymmetricKey
67 template_name = 'symmetric_key_generate.html'
68 submit_url = reverse_lazy(
69 "horizon:project:symmetric_keys:generate")
70 success_url = reverse_lazy('horizon:project:symmetric_keys:index')
71 submit_label = page_title = _("Generate Symmetric Key")
72
73
74class ImportView(forms.ModalFormView):
75 form_class = symmetric_key_forms.ImportSymmetricKey
76 template_name = 'symmetric_key_import.html'
77 submit_url = reverse_lazy(
78 "horizon:project:symmetric_keys:import")
79 success_url = reverse_lazy('horizon:project:symmetric_keys:index')
80 submit_label = page_title = _("Import Symmetric Key")
81
82 def get_object_id(self, key_uuid):
83 return key_uuid
84
85
86class DetailView(views.HorizonTemplateView):
87 template_name = 'symmetric_key_detail.html'
88 page_title = _("Symmetric Key Details")
89
90 @memoized.memoized_method
91 def _get_data(self):
92 try:
93 obj = client.get(self.request, self.kwargs['object_id'])
94 except Exception:
95 redirect = reverse('horizon:project:symmetric_keys:index')
96 msg = _('Unable to retrieve details for symmetric_key "%s".')\
97 % (self.kwargs['object_id'])
98 exceptions.handle(self.request, msg,
99 redirect=redirect)
100 return obj
101
102 @memoized.memoized_method
103 def _get_data_created_date(self, obj):
104 try:
105 created_date = datetime.utcfromtimestamp(obj.created).isoformat()
106 except Exception:
107 redirect = reverse('horizon:project:symmetric_keys:index')
108 msg = _('Unable to retrieve details for symmetric_key "%s".')\
109 % (self.kwargs['object_id'])
110 exceptions.handle(self.request, msg,
111 redirect=redirect)
112 return created_date
113
114 @memoized.memoized_method
115 def _get_data_bytes(self, obj):
116 try:
117 data_bytes = binascii.hexlify(obj.get_encoded())
118 except Exception:
119 redirect = reverse('horizon:project:symmetric_keys:index')
120 msg = _('Unable to retrieve details for symmetric_key "%s".')\
121 % (self.kwargs['object_id'])
122 exceptions.handle(self.request, msg,
123 redirect=redirect)
124 return data_bytes
125
126 def get_context_data(self, **kwargs):
127 """Gets the context data for key."""
128 context = super(DetailView, self).get_context_data(**kwargs)
129 obj = self._get_data()
130 context['object'] = obj
131 context['object_created_date'] = self._get_data_created_date(obj)
132 context['object_bytes'] = self._get_data_bytes(obj)
133 return context
diff --git a/castellan_ui/enabled/_94_project_key_manager_symmetric_keys_panel.py b/castellan_ui/enabled/_94_project_key_manager_symmetric_keys_panel.py
new file mode 100644
index 0000000..8f75ebd
--- /dev/null
+++ b/castellan_ui/enabled/_94_project_key_manager_symmetric_keys_panel.py
@@ -0,0 +1,23 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13# The slug of the panel to be added to HORIZON_CONFIG. Required.
14PANEL = 'symmetric_keys'
15# The slug of the panel group the PANEL is associated with.
16PANEL_GROUP = 'key_manager'
17# The slug of the dashboard the PANEL associated with. Required.
18PANEL_DASHBOARD = 'project'
19
20ADD_INSTALLED_APP = ['castellan_ui', ]
21
22# Python panel class of the PANEL to be added.
23ADD_PANEL = 'castellan_ui.content.symmetric_keys.panel.SymmetricKeys'
diff --git a/castellan_ui/templates/_symmetric_key_generate.html b/castellan_ui/templates/_symmetric_key_generate.html
new file mode 100644
index 0000000..7a318f3
--- /dev/null
+++ b/castellan_ui/templates/_symmetric_key_generate.html
@@ -0,0 +1,7 @@
1{% extends "horizon/common/_modal_form.html" %}
2{% load i18n %}
3
4{% block modal-body-right %}
5 <p>{% trans "Check your key manager to see which algorithms and bit lengths are supported." %}</p>
6{% endblock %}
7
diff --git a/castellan_ui/templates/_symmetric_key_import.html b/castellan_ui/templates/_symmetric_key_import.html
new file mode 100644
index 0000000..f417456
--- /dev/null
+++ b/castellan_ui/templates/_symmetric_key_import.html
@@ -0,0 +1,9 @@
1{% extends '_object_import.html' %}
2{% load i18n %}
3
4{% block modal-body-right %}
5 <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>
6 <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>
7 <p><pre>00112233445566778899aabbccddeeff</pre></p>
8{% endblock %}
9
diff --git a/castellan_ui/templates/import_symmetric_key.html b/castellan_ui/templates/import_symmetric_key.html
new file mode 100644
index 0000000..d062316
--- /dev/null
+++ b/castellan_ui/templates/import_symmetric_key.html
@@ -0,0 +1,7 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{{ page_title }}{% endblock %}
4
5{% block main %}
6 {% include 'project/key_pairs/_import.html' %}
7{% endblock %}
diff --git a/castellan_ui/templates/symmetric_key_detail.html b/castellan_ui/templates/symmetric_key_detail.html
new file mode 100644
index 0000000..02a4da1
--- /dev/null
+++ b/castellan_ui/templates/symmetric_key_detail.html
@@ -0,0 +1,27 @@
1{% extends 'base.html' %}
2{% load i18n parse_date %}
3
4{% block title %}{{ page_title }}{% endblock %}
5
6{% block page_header %}
7 {% include "horizon/common/_detail_header.html" %}
8{% endblock %}
9
10{% block main %}
11<div class="detail">
12 <dl class="dl-horizontal">
13 <dt>{% trans "Name" %}</dt>
14 <dd>{{ object.name|default:_("None") }}</dd>
15 <dt>{% trans "Created" %}</dt>
16 <dd>{{ object_created_date|parse_date}}</dd>
17 <dt>{% trans "Algorithm" %}</dt>
18 <dd>{{ object.algorithm|default:_("None") }}</dd>
19 <dt>{% trans "Bit Length" %}</dt>
20 <dd>{{ object.bit_length|default:_("None") }}</dd>
21 <dt>{% trans "Key Value (in hex)" %}</dt>
22 <dd>
23 <div class="key-text word-wrap">{{ object_bytes|default:_("None") }}</div>
24 </dd>
25 </dl>
26</div>
27{% endblock %}
diff --git a/castellan_ui/templates/symmetric_key_generate.html b/castellan_ui/templates/symmetric_key_generate.html
new file mode 100644
index 0000000..3ec75e8
--- /dev/null
+++ b/castellan_ui/templates/symmetric_key_generate.html
@@ -0,0 +1,7 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{{ page_title }}{% endblock %}
4
5{% block main %}
6 {% include '_symmetric_key_generate.html' %}
7{% endblock %}
diff --git a/castellan_ui/templates/symmetric_key_import.html b/castellan_ui/templates/symmetric_key_import.html
new file mode 100644
index 0000000..eaa3e62
--- /dev/null
+++ b/castellan_ui/templates/symmetric_key_import.html
@@ -0,0 +1,7 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{{ page_title }}{% endblock %}
4
5{% block main %}
6 {% include '_symmetric_key_import.html' %}
7{% endblock %}
diff --git a/castellan_ui/templates/symmetric_keys.html b/castellan_ui/templates/symmetric_keys.html
new file mode 100644
index 0000000..d1d810a
--- /dev/null
+++ b/castellan_ui/templates/symmetric_keys.html
@@ -0,0 +1,23 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Symmetric Keys" %}{% endblock %}
4
5{% block breadcrumb_nav %}
6 <ol class = "breadcrumb">
7 <li>{% trans "Project" %}</li>
8 <li>{% trans "Key Manager" %}</li>
9 <li class="active">{% trans "Symmetric Keys" %}</li>
10 </ol>
11{% endblock %}
12
13{% block page_header %}
14 <hz-page-header header="{% trans "Symmetric Keys" %}"></hz-page-header>
15{% endblock page_header %}
16
17{% block main %}
18<div class="row">
19 <div class="col-sm-12">
20 {{ symmetric_key_table.render }}
21 </div>
22</div>
23{% endblock %}
diff --git a/castellan_ui/test/content/symmetric_keys/__init__.py b/castellan_ui/test/content/symmetric_keys/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/castellan_ui/test/content/symmetric_keys/__init__.py
diff --git a/castellan_ui/test/content/symmetric_keys/tests.py b/castellan_ui/test/content/symmetric_keys/tests.py
new file mode 100644
index 0000000..a593ab7
--- /dev/null
+++ b/castellan_ui/test/content/symmetric_keys/tests.py
@@ -0,0 +1,136 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import base64
14import binascii
15from django.core.handlers import wsgi
16from django.core.urlresolvers import reverse
17from horizon import messages as horizon_messages
18import mock
19
20from castellan.common.objects import symmetric_key
21from castellan_ui.api import client as api_castellan
22from castellan_ui.test import helpers as tests
23from castellan_ui.test import test_data
24
25INDEX_URL = reverse('horizon:project:symmetric_keys:index')
26
27
28class SymmetricKeysViewTest(tests.APITestCase):
29
30 def setUp(self):
31 super(SymmetricKeysViewTest, self).setUp()
32 self.key = test_data.symmetric_key
33 self.key_b64_bytes = base64.b64encode(self.key.get_encoded())
34 self.mock_object(
35 api_castellan, "get", mock.Mock(return_value=self.key))
36 self.mock_object(api_castellan, "list", mock.Mock(return_value=[]))
37 self.mock_object(horizon_messages, "success")
38 FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'}
39 self.request = wsgi.WSGIRequest(FAKE_ENVIRON)
40
41 def test_index(self):
42 key_list = [test_data.symmetric_key, test_data.nameless_symmetric_key]
43
44 self.mock_object(
45 api_castellan, "list", mock.Mock(return_value=key_list))
46
47 res = self.client.get(INDEX_URL)
48 self.assertEqual(res.status_code, 200)
49 self.assertTemplateUsed(res, 'symmetric_keys.html')
50 api_castellan.list.assert_called_with(
51 mock.ANY, object_type=symmetric_key.SymmetricKey)
52
53 def test_detail_view(self):
54 url = reverse('horizon:project:symmetric_keys:detail',
55 args=[self.key.id])
56 self.mock_object(
57 api_castellan, "list", mock.Mock(return_value=[self.key]))
58 self.mock_object(
59 api_castellan, "get", mock.Mock(return_value=self.key))
60
61 res = self.client.get(url)
62 self.assertContains(
63 res, "<dt>Name</dt>\n <dd>%s</dd>" % self.key.name, 1, 200)
64 api_castellan.get.assert_called_once_with(mock.ANY, self.key.id)
65
66 def test_import_key(self):
67 self.mock_object(
68 api_castellan, "list", mock.Mock(return_value=[self.key]))
69 url = reverse('horizon:project:symmetric_keys:import')
70 self.mock_object(
71 api_castellan, "import_object", mock.Mock(return_value=self.key))
72
73 key_input = (
74 binascii.hexlify(self.key.get_encoded()).decode('utf-8')
75 )
76
77 key_form_data = {
78 'source_type': 'raw',
79 'name': self.key.name,
80 'direct_input': key_input,
81 'bit_length': 128,
82 'algorithm': 'AES'
83 }
84
85 self.client.post(url, key_form_data)
86
87 api_castellan.import_object.assert_called_once_with(
88 mock.ANY,
89 object_type=symmetric_key.SymmetricKey,
90 key=self.key_b64_bytes,
91 name=self.key.name,
92 algorithm=u'AES',
93 bit_length=128
94 )
95
96 def test_generate_key(self):
97 self.mock_object(
98 api_castellan, "list", mock.Mock(return_value=[self.key]))
99 url = reverse('horizon:project:symmetric_keys:generate')
100 self.mock_object(
101 api_castellan, "generate_symmetric_key",
102 mock.Mock(return_value=self.key))
103
104 key_form_data = {
105 'name': self.key.name,
106 'length': 256,
107 'algorithm': 'AES'
108 }
109
110 self.client.post(url, key_form_data)
111
112 api_castellan.generate_symmetric_key.assert_called_once_with(
113 mock.ANY,
114 name=self.key.name,
115 algorithm=u'AES',
116 length=256
117 )
118
119 def test_delete_key(self):
120 self.mock_object(
121 api_castellan, "list", mock.Mock(return_value=[self.key]))
122 self.mock_object(api_castellan, "delete")
123
124 key_form_data = {
125 'action': 'symmetric_key__delete__%s' % self.key.id
126 }
127
128 res = self.client.post(INDEX_URL, key_form_data)
129
130 api_castellan.list.assert_called_with(
131 mock.ANY, object_type=symmetric_key.SymmetricKey)
132 api_castellan.delete.assert_called_once_with(
133 mock.ANY,
134 self.key.id,
135 )
136 self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/castellan_ui/test/test_data.py b/castellan_ui/test/test_data.py
index cd57734..3261c55 100644
--- a/castellan_ui/test/test_data.py
+++ b/castellan_ui/test/test_data.py
@@ -56,3 +56,19 @@ nameless_public_key = objects.public_key.PublicKey(
56 name=None, 56 name=None,
57 created=1448088699, 57 created=1448088699,
58 id=u'11111111-1111-1111-1111-111111111111') 58 id=u'11111111-1111-1111-1111-111111111111')
59
60symmetric_key = objects.symmetric_key.SymmetricKey(
61 key=castellan_utils.get_symmetric_key(),
62 algorithm="AES",
63 bit_length=128,
64 name=u'test symmetric key',
65 created=1448088699,
66 id=u'00000000-0000-0000-0000-000000000000')
67
68nameless_symmetric_key = objects.symmetric_key.SymmetricKey(
69 key=castellan_utils.get_symmetric_key(),
70 algorithm="AES",
71 bit_length=128,
72 name=None,
73 created=1448088699,
74 id=u'11111111-1111-1111-1111-111111111111')
diff --git a/tox.ini b/tox.ini
index a01f5c3..04a17d4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
1[tox] 1[tox]
2envlist = py34,py27,py27dj18,pep8 2envlist = py35,py27,py27dj18,pep8
3minversion = 2.0 3minversion = 2.0
4skipsdist = True 4skipsdist = True
5 5