summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaitlin Farr <kaitlin.farr@jhuapl.edu>2017-11-14 12:01:29 -0500
committerBrianna Poulos <Brianna.Poulos@jhuapl.edu>2018-02-27 21:27:27 +0000
commit21504848d687e998b68c52b5824c940a87d0cfb7 (patch)
tree5848dadd0a50f6fa5be79412c5b3a76c2e997fc3
parent299eb23fa4a7917d1e8818c5516df9e0cb8e21b5 (diff)
Add Opaque Data 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:41 +0000 Reviewed-on: https://review.openstack.org/519703 Project: openstack/castellan-ui Branch: refs/heads/master
-rw-r--r--README.rst3
-rw-r--r--castellan_ui/content/opaque_data/__init__.py0
-rw-r--r--castellan_ui/content/opaque_data/forms.py108
-rw-r--r--castellan_ui/content/opaque_data/panel.py23
-rw-r--r--castellan_ui/content/opaque_data/tables.py84
-rw-r--r--castellan_ui/content/opaque_data/urls.py26
-rw-r--r--castellan_ui/content/opaque_data/views.py124
-rw-r--r--castellan_ui/enabled/_95_project_key_manager_opaque_data_panel.py23
-rw-r--r--castellan_ui/templates/_opaque_data_import.html9
-rw-r--r--castellan_ui/templates/opaque_data.html23
-rw-r--r--castellan_ui/templates/opaque_data_detail.html24
-rw-r--r--castellan_ui/templates/opaque_data_import.html7
-rw-r--r--castellan_ui/test/content/opaque_data/__init__.py0
-rw-r--r--castellan_ui/test/content/opaque_data/tests.py109
-rw-r--r--castellan_ui/test/test_data.py12
15 files changed, 574 insertions, 1 deletions
diff --git a/README.rst b/README.rst
index e747721..bf5bc16 100644
--- a/README.rst
+++ b/README.rst
@@ -57,7 +57,8 @@ 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 ln -s ../castellan-ui/castellan_ui/enabled/_94_project_key_manager_symmetric_key_panel.py openstack_dashboard/local/enabled
61 ln -s ../castellan-ui/castellan_ui/enabled/_95_project_key_manager_opaque_data_panel.py openstack_dashboard/local/enabled
61 62
62To run horizon with the newly enabled Castellan UI plugin run:: 63To run horizon with the newly enabled Castellan UI plugin run::
63 64
diff --git a/castellan_ui/content/opaque_data/__init__.py b/castellan_ui/content/opaque_data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/castellan_ui/content/opaque_data/__init__.py
diff --git a/castellan_ui/content/opaque_data/forms.py b/castellan_ui/content/opaque_data/forms.py
new file mode 100644
index 0000000..a323c7b
--- /dev/null
+++ b/castellan_ui/content/opaque_data/forms.py
@@ -0,0 +1,108 @@
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 opaque_data
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
26
27class ImportOpaqueData(forms.SelfHandlingForm):
28 name = forms.RegexField(required=False,
29 max_length=255,
30 label=_("Data Name"),
31 regex=shared_forms.NAME_REGEX,
32 error_messages=shared_forms.ERROR_MESSAGES)
33 source_type = forms.ChoiceField(
34 label=_('Source'),
35 required=False,
36 choices=[('file', _('File')),
37 ('raw', _('Direct Input'))],
38 widget=forms.ThemableSelectWidget(
39 attrs={'class': 'switchable', 'data-slug': 'source'}))
40 object_file = forms.FileField(
41 label=_("Choose file"),
42 help_text=_("A local file to upload."),
43 widget=forms.FileInput(
44 attrs={'class': 'switched', 'data-switch-on': 'source',
45 'data-source-file': _('File')}),
46 required=False)
47 direct_input = forms.CharField(
48 label=_('Object Bytes'),
49 help_text=_('The bytes of the object, represented in hex.'),
50 widget=forms.widgets.Textarea(
51 attrs={'class': 'switched', 'data-switch-on': 'source',
52 'data-source-raw': _('Bytes')}),
53 required=False)
54
55 def __init__(self, request, *args, **kwargs):
56 super(ImportOpaqueData, self).__init__(request, *args, **kwargs)
57
58 def clean(self):
59 data = super(ImportOpaqueData, self).clean()
60
61 # The data can be missing based on particular upload
62 # conditions. Code defensively for it here...
63 data_file = data.get('object_file', None)
64 data_raw = data.get('direct_input', None)
65
66 if data_raw and data_file:
67 raise forms.ValidationError(
68 _("Cannot specify both file and direct input."))
69 if not data_raw and not data_file:
70 raise forms.ValidationError(
71 _("No input was provided for the object value."))
72 try:
73 if data_file:
74 data_bytes = self.files['object_file'].read()
75 else:
76 data_str = data['direct_input']
77 data_bytes = binascii.unhexlify(data_str)
78 data['object_bytes'] = base64.b64encode(data_bytes)
79 except Exception as e:
80 msg = _('There was a problem loading the object: %s. '
81 'Is the object valid and in the correct format?') % e
82 raise forms.ValidationError(msg)
83
84 return data
85
86 def handle(self, request, data):
87 try:
88 data_bytes = data.get('object_bytes')
89 data_uuid = client.import_object(
90 request,
91 data=data_bytes,
92 name=data['name'],
93 object_type=opaque_data.OpaqueData)
94
95 if data['name']:
96 data_identifier = data['name']
97 else:
98 data_identifier = data_uuid
99 messages.success(request,
100 _('Successfully imported object: %s')
101 % data_identifier)
102 return data_uuid
103 except Exception as e:
104 msg = _('Unable to import object: %s')
105 messages.error(msg % e)
106 exceptions.handle(request, ignore=True)
107 self.api_error(_('Unable to import object.'))
108 return False
diff --git a/castellan_ui/content/opaque_data/panel.py b/castellan_ui/content/opaque_data/panel.py
new file mode 100644
index 0000000..5725252
--- /dev/null
+++ b/castellan_ui/content/opaque_data/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 OpaqueData(horizon.Panel):
22 name = _("Opaque Data")
23 slug = "opaque_data"
diff --git a/castellan_ui/content/opaque_data/tables.py b/castellan_ui/content/opaque_data/tables.py
new file mode 100644
index 0000000..4129431
--- /dev/null
+++ b/castellan_ui/content/opaque_data/tables.py
@@ -0,0 +1,84 @@
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 ImportOpaqueData(tables.LinkAction):
24 name = "import_opaque_data"
25 verbose_name = _("Import Opaque Data")
26 url = "horizon:project:opaque_data:import"
27 classes = ("ajax-modal",)
28 icon = "upload"
29 policy_rules = ()
30
31
32class DownloadOpaqueData(tables.LinkAction):
33 name = "download"
34 verbose_name = _("Download Opaque Data")
35 url = "horizon:project:opaque_data:download"
36 classes = ("btn-download",)
37 policy_rules = ()
38
39 def get_link_url(self, datum):
40 return reverse(self.url,
41 kwargs={'object_id': datum.id})
42
43
44class DeleteOpaqueData(tables.DeleteAction):
45 policy_rules = ()
46 help_text = _("You should not delete an object unless you are "
47 "certain it is not being used anywhere.")
48
49 @staticmethod
50 def action_present(count):
51 return ungettext_lazy(
52 u"Delete Opaque Data",
53 u"Delete Opaque Data",
54 count
55 )
56
57 @staticmethod
58 def action_past(count):
59 return ungettext_lazy(
60 u"Deleted Opaque Data",
61 u"Deleted Opaque Data",
62 count
63 )
64
65 def delete(self, request, obj_id):
66 client.delete(request, obj_id)
67
68
69class OpaqueDataTable(tables.DataTable):
70 detail_link = "horizon:project:opaque_data:detail"
71 uuid = tables.Column("id", verbose_name=_("Object ID"), link=detail_link)
72 name = tables.Column("name", verbose_name=_("Name"))
73 created_date = tables.Column("created",
74 verbose_name=_("Created Date"),
75 filters=(filters.timestamp_to_iso,))
76
77 def get_object_display(self, datum):
78 return datum.name if datum.name else datum.id
79
80 class Meta(object):
81 name = "opaque_data"
82 table_actions = (ImportOpaqueData,
83 DeleteOpaqueData,)
84 row_actions = (DownloadOpaqueData, DeleteOpaqueData)
diff --git a/castellan_ui/content/opaque_data/urls.py b/castellan_ui/content/opaque_data/urls.py
new file mode 100644
index 0000000..5e77c40
--- /dev/null
+++ b/castellan_ui/content/opaque_data/urls.py
@@ -0,0 +1,26 @@
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.opaque_data 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'^(?P<object_id>[^/]+)/$',
20 views.DetailView.as_view(),
21 name='detail'),
22 url(r'^download/$', views.download_key, name='download'),
23 url(r'^(?P<object_id>[^/]+)/download$',
24 views.download_key,
25 name='download'),
26]
diff --git a/castellan_ui/content/opaque_data/views.py b/castellan_ui/content/opaque_data/views.py
new file mode 100644
index 0000000..1e1dae0
--- /dev/null
+++ b/castellan_ui/content/opaque_data/views.py
@@ -0,0 +1,124 @@
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 opaque_data
20from castellan_ui.api import client
21from castellan_ui.content.opaque_data import forms as opaque_data_forms
22from castellan_ui.content.opaque_data 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.opaque"' % object_id)
39 response['Content-Length'] = str(len(response.content))
40 return response
41
42 except Exception:
43 redirect = reverse('horizon:project:opaque_data:index')
44 msg = _('Unable to download opaque_data "%s".')\
45 % (object_id)
46 exceptions.handle(request, msg, redirect=redirect)
47
48
49class IndexView(tables_views.MultiTableView):
50 table_classes = [
51 tables.OpaqueDataTable
52 ]
53 template_name = 'opaque_data.html'
54
55 def get_opaque_data_data(self):
56 try:
57 return client.list(
58 self.request, object_type=opaque_data.OpaqueData)
59 except Exception as e:
60 msg = _('Unable to list objects: "%s".') % (e.message)
61 exceptions.handle(self.request, msg)
62 return []
63
64
65class ImportView(forms.ModalFormView):
66 form_class = opaque_data_forms.ImportOpaqueData
67 template_name = 'opaque_data_import.html'
68 submit_url = reverse_lazy(
69 "horizon:project:opaque_data:import")
70 success_url = reverse_lazy('horizon:project:opaque_data:index')
71 submit_label = page_title = _("Import Opaque Data")
72
73 def get_object_id(self, key_uuid):
74 return key_uuid
75
76
77class DetailView(views.HorizonTemplateView):
78 template_name = 'opaque_data_detail.html'
79 page_title = _("Opaque Data Details")
80
81 @memoized.memoized_method
82 def _get_data(self):
83 try:
84 obj = client.get(self.request, self.kwargs['object_id'])
85 except Exception:
86 redirect = reverse('horizon:project:opaque_data:index')
87 msg = _('Unable to retrieve details for opaque_data "%s".')\
88 % (self.kwargs['object_id'])
89 exceptions.handle(self.request, msg,
90 redirect=redirect)
91 return obj
92
93 @memoized.memoized_method
94 def _get_data_created_date(self, obj):
95 try:
96 created_date = datetime.utcfromtimestamp(obj.created).isoformat()
97 except Exception:
98 redirect = reverse('horizon:project:opaque_data:index')
99 msg = _('Unable to retrieve details for opaque_data "%s".')\
100 % (self.kwargs['object_id'])
101 exceptions.handle(self.request, msg,
102 redirect=redirect)
103 return created_date
104
105 @memoized.memoized_method
106 def _get_data_bytes(self, obj):
107 try:
108 data_bytes = binascii.hexlify(obj.get_encoded())
109 except Exception:
110 redirect = reverse('horizon:project:opaque_data:index')
111 msg = _('Unable to retrieve details for opaque_data "%s".')\
112 % (self.kwargs['object_id'])
113 exceptions.handle(self.request, msg,
114 redirect=redirect)
115 return data_bytes
116
117 def get_context_data(self, **kwargs):
118 """Gets the context data for key."""
119 context = super(DetailView, self).get_context_data(**kwargs)
120 obj = self._get_data()
121 context['object'] = obj
122 context['object_created_date'] = self._get_data_created_date(obj)
123 context['object_bytes'] = self._get_data_bytes(obj)
124 return context
diff --git a/castellan_ui/enabled/_95_project_key_manager_opaque_data_panel.py b/castellan_ui/enabled/_95_project_key_manager_opaque_data_panel.py
new file mode 100644
index 0000000..5641277
--- /dev/null
+++ b/castellan_ui/enabled/_95_project_key_manager_opaque_data_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 = 'opaque_data'
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.opaque_data.panel.OpaqueData'
diff --git a/castellan_ui/templates/_opaque_data_import.html b/castellan_ui/templates/_opaque_data_import.html
new file mode 100644
index 0000000..c3d4074
--- /dev/null
+++ b/castellan_ui/templates/_opaque_data_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 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>
6 <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>
7 <p><pre>00112233445566778899aabbccddeeff</pre></p>
8{% endblock %}
9
diff --git a/castellan_ui/templates/opaque_data.html b/castellan_ui/templates/opaque_data.html
new file mode 100644
index 0000000..fa740be
--- /dev/null
+++ b/castellan_ui/templates/opaque_data.html
@@ -0,0 +1,23 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Opaque Data" %}{% 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 "Opaque Data" %}</li>
10 </ol>
11{% endblock %}
12
13{% block page_header %}
14 <hz-page-header header="{% trans "Opaque Data" %}"></hz-page-header>
15{% endblock page_header %}
16
17{% block main %}
18<div class="row">
19 <div class="col-sm-12">
20 {{ opaque_data_table.render }}
21 </div>
22</div>
23{% endblock %}
diff --git a/castellan_ui/templates/opaque_data_detail.html b/castellan_ui/templates/opaque_data_detail.html
new file mode 100644
index 0000000..6e28ecf
--- /dev/null
+++ b/castellan_ui/templates/opaque_data_detail.html
@@ -0,0 +1,24 @@
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 "Object Value (in hex)" %}</dt>
18 <dd>
19 <div class="key-text word-wrap">{{ object_bytes|default:_("None") }}</div>
20 </dd>
21
22 </dl>
23</div>
24{% endblock %}
diff --git a/castellan_ui/templates/opaque_data_import.html b/castellan_ui/templates/opaque_data_import.html
new file mode 100644
index 0000000..8c41592
--- /dev/null
+++ b/castellan_ui/templates/opaque_data_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 '_opaque_data_import.html' %}
7{% endblock %}
diff --git a/castellan_ui/test/content/opaque_data/__init__.py b/castellan_ui/test/content/opaque_data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/castellan_ui/test/content/opaque_data/__init__.py
diff --git a/castellan_ui/test/content/opaque_data/tests.py b/castellan_ui/test/content/opaque_data/tests.py
new file mode 100644
index 0000000..c108964
--- /dev/null
+++ b/castellan_ui/test/content/opaque_data/tests.py
@@ -0,0 +1,109 @@
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 opaque_data
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:opaque_data:index')
26
27
28class OpaqueDataViewTest(tests.APITestCase):
29
30 def setUp(self):
31 super(OpaqueDataViewTest, self).setUp()
32 self.data = test_data.opaque_data
33 self.data_b64_bytes = base64.b64encode(self.data.get_encoded())
34 self.mock_object(
35 api_castellan, "get", mock.Mock(return_value=self.data))
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 data_list = [test_data.opaque_data, test_data.nameless_opaque_data]
43
44 self.mock_object(
45 api_castellan, "list", mock.Mock(return_value=data_list))
46
47 res = self.client.get(INDEX_URL)
48 self.assertEqual(res.status_code, 200)
49 self.assertTemplateUsed(res, 'opaque_data.html')
50 api_castellan.list.assert_called_with(
51 mock.ANY, object_type=opaque_data.OpaqueData)
52
53 def test_detail_view(self):
54 url = reverse('horizon:project:opaque_data:detail',
55 args=[self.data.id])
56 self.mock_object(
57 api_castellan, "list", mock.Mock(return_value=[self.data]))
58 self.mock_object(
59 api_castellan, "get", mock.Mock(return_value=self.data))
60
61 res = self.client.get(url)
62 self.assertContains(
63 res, "<dt>Name</dt>\n <dd>%s</dd>" % self.data.name, 1, 200)
64 api_castellan.get.assert_called_once_with(mock.ANY, self.data.id)
65
66 def test_import_data(self):
67 self.mock_object(
68 api_castellan, "list", mock.Mock(return_value=[self.data]))
69 url = reverse('horizon:project:opaque_data:import')
70 self.mock_object(
71 api_castellan, "import_object", mock.Mock(return_value=self.data))
72
73 data_input = (
74 binascii.hexlify(self.data.get_encoded()).decode('utf-8')
75 )
76
77 data_form_data = {
78 'source_type': 'raw',
79 'name': self.data.name,
80 'direct_input': data_input,
81 }
82
83 self.client.post(url, data_form_data)
84
85 api_castellan.import_object.assert_called_once_with(
86 mock.ANY,
87 object_type=opaque_data.OpaqueData,
88 data=self.data_b64_bytes,
89 name=self.data.name,
90 )
91
92 def test_delete_data(self):
93 self.mock_object(
94 api_castellan, "list", mock.Mock(return_value=[self.data]))
95 self.mock_object(api_castellan, "delete")
96
97 data_form_data = {
98 'action': 'opaque_data__delete__%s' % self.data.id
99 }
100
101 res = self.client.post(INDEX_URL, data_form_data)
102
103 api_castellan.list.assert_called_with(
104 mock.ANY, object_type=opaque_data.OpaqueData)
105 api_castellan.delete.assert_called_once_with(
106 mock.ANY,
107 self.data.id,
108 )
109 self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/castellan_ui/test/test_data.py b/castellan_ui/test/test_data.py
index 3261c55..675d5a4 100644
--- a/castellan_ui/test/test_data.py
+++ b/castellan_ui/test/test_data.py
@@ -72,3 +72,15 @@ nameless_symmetric_key = objects.symmetric_key.SymmetricKey(
72 name=None, 72 name=None,
73 created=1448088699, 73 created=1448088699,
74 id=u'11111111-1111-1111-1111-111111111111') 74 id=u'11111111-1111-1111-1111-111111111111')
75
76opaque_data = objects.opaque_data.OpaqueData(
77 data=b'\xde\xad\xbe\xef',
78 name=u'test opaque data',
79 created=1448088699,
80 id=u'00000000-0000-0000-0000-000000000000')
81
82nameless_opaque_data = objects.opaque_data.OpaqueData(
83 data=b'\xde\xad\xbe\xef',
84 name=None,
85 created=1448088699,
86 id=u'11111111-1111-1111-1111-111111111111')