Browse Source

Add Symmetric Keys Panel

Change-Id: Id423dbfd30990b8c4240b8008b6c659da38f91fd
Kaitlin Farr 1 year ago
parent
commit
299eb23fa4

+ 1
- 0
README.rst View File

@@ -57,6 +57,7 @@ And enable it in Horizon::
57 57
     ln -s ../castellan-ui/castellan_ui/enabled/_91_project_key_manager_x509_certificates_panel.py openstack_dashboard/local/enabled
58 58
     ln -s ../castellan-ui/castellan_ui/enabled/_92_project_key_manager_private_key_panel.py openstack_dashboard/local/enabled
59 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
 
61 62
 To run horizon with the newly enabled Castellan UI plugin run::
62 63
 

+ 0
- 0
castellan_ui/content/symmetric_keys/__init__.py View File


+ 96
- 0
castellan_ui/content/symmetric_keys/forms.py View File

@@ -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
+
14
+import base64
15
+import binascii
16
+from django.utils.translation import ugettext_lazy as _
17
+
18
+from castellan.common.objects import symmetric_key
19
+from horizon import exceptions
20
+from horizon import forms
21
+from horizon import messages
22
+
23
+from castellan_ui.api import client
24
+from castellan_ui.content import shared_forms
25
+
26
+ALGORITHMS = ('AES', 'DES', 'DESEDE')
27
+
28
+ALG_HELP_TEXT = _(
29
+    "Check which algorithms your key manager supports. "
30
+    "Some common algorithms are: %s") % ', '.join(ALGORITHMS)
31
+LENGTH_HELP_TEXT = _(
32
+    "Only certain bit lengths are valid for each algorithm. "
33
+    "Some common bit lengths are: 128, 256")
34
+
35
+
36
+class 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
+
61
+class 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

+ 23
- 0
castellan_ui/content/symmetric_keys/panel.py View File

@@ -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
+from django.utils.translation import ugettext_lazy as _
14
+import horizon
15
+
16
+# This panel will be loaded from horizon, because specified in enabled file.
17
+# To register REST api, import below here.
18
+from castellan_ui.api import client  # noqa: F401
19
+
20
+
21
+class SymmetricKeys(horizon.Panel):
22
+    name = _("Symmetric Keys")
23
+    slug = "symmetric_keys"

+ 96
- 0
castellan_ui/content/symmetric_keys/tables.py View File

@@ -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
+
14
+from castellan_ui.content import filters
15
+from django.core.urlresolvers import reverse
16
+from django.utils.translation import ugettext_lazy as _
17
+from django.utils.translation import ungettext_lazy
18
+
19
+from castellan_ui.api import client
20
+from horizon import tables
21
+
22
+
23
+class 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
+
32
+class 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
+
41
+class 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
+
53
+class 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
+
78
+class 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)

+ 27
- 0
castellan_ui/content/symmetric_keys/urls.py View File

@@ -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
+
13
+from castellan_ui.content.symmetric_keys import views
14
+from django.conf.urls import url
15
+
16
+urlpatterns = [
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
+]

+ 133
- 0
castellan_ui/content/symmetric_keys/views.py View File

@@ -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
+
13
+from django.core.urlresolvers import reverse
14
+from django.core.urlresolvers import reverse_lazy
15
+from django.http import HttpResponse
16
+from django.utils.translation import ugettext_lazy as _
17
+
18
+import binascii
19
+from castellan.common.objects import symmetric_key
20
+from castellan_ui.api import client
21
+from castellan_ui.content.symmetric_keys import forms as symmetric_key_forms
22
+from castellan_ui.content.symmetric_keys import tables
23
+from datetime import datetime
24
+from horizon import exceptions
25
+from horizon import forms
26
+from horizon.tables import views as tables_views
27
+from horizon.utils import memoized
28
+from horizon import views
29
+
30
+
31
+def 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
+
49
+class 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
+
65
+class 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
+
74
+class 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
+
86
+class 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

+ 23
- 0
castellan_ui/enabled/_94_project_key_manager_symmetric_keys_panel.py View File

@@ -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.
14
+PANEL = 'symmetric_keys'
15
+# The slug of the panel group the PANEL is associated with.
16
+PANEL_GROUP = 'key_manager'
17
+# The slug of the dashboard the PANEL associated with. Required.
18
+PANEL_DASHBOARD = 'project'
19
+
20
+ADD_INSTALLED_APP = ['castellan_ui', ]
21
+
22
+# Python panel class of the PANEL to be added.
23
+ADD_PANEL = 'castellan_ui.content.symmetric_keys.panel.SymmetricKeys'

+ 7
- 0
castellan_ui/templates/_symmetric_key_generate.html View File

@@ -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
+

+ 9
- 0
castellan_ui/templates/_symmetric_key_import.html View File

@@ -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
+

+ 7
- 0
castellan_ui/templates/import_symmetric_key.html View File

@@ -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 %}

+ 27
- 0
castellan_ui/templates/symmetric_key_detail.html View File

@@ -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 %}

+ 7
- 0
castellan_ui/templates/symmetric_key_generate.html View File

@@ -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 %}

+ 7
- 0
castellan_ui/templates/symmetric_key_import.html View File

@@ -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 %}

+ 23
- 0
castellan_ui/templates/symmetric_keys.html View File

@@ -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 %}

+ 0
- 0
castellan_ui/test/content/symmetric_keys/__init__.py View File


+ 136
- 0
castellan_ui/test/content/symmetric_keys/tests.py View File

@@ -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
+
13
+import base64
14
+import binascii
15
+from django.core.handlers import wsgi
16
+from django.core.urlresolvers import reverse
17
+from horizon import messages as horizon_messages
18
+import mock
19
+
20
+from castellan.common.objects import symmetric_key
21
+from castellan_ui.api import client as api_castellan
22
+from castellan_ui.test import helpers as tests
23
+from castellan_ui.test import test_data
24
+
25
+INDEX_URL = reverse('horizon:project:symmetric_keys:index')
26
+
27
+
28
+class 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)

+ 16
- 0
castellan_ui/test/test_data.py View File

@@ -56,3 +56,19 @@ nameless_public_key = objects.public_key.PublicKey(
56 56
     name=None,
57 57
     created=1448088699,
58 58
     id=u'11111111-1111-1111-1111-111111111111')
59
+
60
+symmetric_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
+
68
+nameless_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')

+ 1
- 1
tox.ini View File

@@ -1,5 +1,5 @@
1 1
 [tox]
2
-envlist = py34,py27,py27dj18,pep8
2
+envlist = py35,py27,py27dj18,pep8
3 3
 minversion = 2.0
4 4
 skipsdist = True
5 5
 

Loading…
Cancel
Save