From d07fedc45f91449787d939a5bf4cc00a0d100652 Mon Sep 17 00:00:00 2001
From: Matt Borland
Date: Thu, 8 Sep 2016 14:50:23 -0600
Subject: [PATCH] Use POST not GET for keypair generation
This patch fixes the Cross-Site Request Forgery (CSRF) attack against
the keypair generation pages:
- HORIZON_URL/project/key_pairs/PAIRNAME/generate/
- HORIZON_URL/project/key_pairs/PAIRNAME/download/
These pages exposed creating and/or overwriting a keypair with a given
name via a CSRF attack.
This patch closes these holes by using only POST-based keypair creation,
and exposing the keypair in the contents of a modal dialog instead of a
download, which ultimately requires a GET. It uses the same client-side
features for both the Launch Instance keypair creation and Compute / Key
Pairs panel.
Closes-Bug: 1575913
Change-Id: Ie5ca28ff2bd806eb1481eba6f419b797b68856b6
---
openstack_dashboard/api/rest/nova.py | 43 -----
.../dashboards/project/key_pairs/forms.py | 23 ---
.../dashboards/project/key_pairs/tables.py | 25 ++-
.../templates/key_pairs/_create.html | 7 -
.../key_pairs/templates/key_pairs/create.html | 8 -
.../dashboards/project/key_pairs/tests.py | 104 -----------
.../dashboards/project/key_pairs/urls.py | 7 -
.../dashboards/project/key_pairs/views.py | 53 ------
.../keypair/create-keypair.controller.js | 38 ++--
.../keypair/create-keypair.controller.spec.js | 24 +--
.../keypair/create-keypair.html | 25 ++-
.../keypair/keypair.controller.js | 1 +
.../launch-instance/keypair/keypair.html | 12 --
.../static/app/core/core.module.js | 1 +
.../app/core/keypairs/keypair.controller.js | 97 ++++++++++
.../core/keypairs/keypair.controller.spec.js | 80 +++++++++
.../app/core/keypairs/keypairs.module.js | 33 ++++
.../app/core/keypairs/keypairs.module.spec.js | 25 +++
.../keypair-download.service.js | 153 ----------------
.../keypair-download.service.spec.js | 166 ------------------
.../openstack-service-api/nova.service.js | 46 +----
.../nova.service.spec.js | 49 ------
.../test/api_tests/nova_rest_tests.py | 63 -------
23 files changed, 299 insertions(+), 784 deletions(-)
delete mode 100644 openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/_create.html
delete mode 100644 openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/create.html
create mode 100644 openstack_dashboard/static/app/core/keypairs/keypair.controller.js
create mode 100644 openstack_dashboard/static/app/core/keypairs/keypair.controller.spec.js
create mode 100644 openstack_dashboard/static/app/core/keypairs/keypairs.module.js
create mode 100644 openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js
delete mode 100644 openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.js
delete mode 100644 openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.spec.js
diff --git a/openstack_dashboard/api/rest/nova.py b/openstack_dashboard/api/rest/nova.py
index bf7ff4c97b..6eef062ffb 100644
--- a/openstack_dashboard/api/rest/nova.py
+++ b/openstack_dashboard/api/rest/nova.py
@@ -15,8 +15,6 @@
"""
from collections import OrderedDict
-from django.http import HttpResponse
-from django.template.defaultfilters import slugify
from django.utils import http as utils_http
from django.utils.translation import ugettext_lazy as _
from django.views import generic
@@ -87,47 +85,6 @@ class Keypairs(generic.View):
)
-@urls.register
-class Keypair(generic.View):
- url_regex = r'nova/keypairs/(?P.+)/$'
-
- def get(self, request, keypair_name):
- """Creates a new keypair and associates it to the current project.
-
- * Since the response for this endpoint creates a new keypair and
- is not idempotent, it normally would be represented by a POST HTTP
- request. However, this solution was adopted as it
- would support automatic file download across browsers.
-
- :param keypair_name: the name to associate the keypair to
- :param regenerate: (optional) if set to the string 'true',
- replaces the existing keypair with a new keypair
-
- This returns the new keypair object on success.
- """
- try:
- regenerate = request.GET.get('regenerate') == 'true'
- if regenerate:
- api.nova.keypair_delete(request, keypair_name)
-
- keypair = api.nova.keypair_create(request, keypair_name)
-
- except exceptions.Conflict:
- return HttpResponse(status=409)
-
- except Exception:
- return HttpResponse(status=500)
-
- else:
- response = HttpResponse(content_type='application/binary')
- response['Content-Disposition'] = ('attachment; filename=%s.pem'
- % slugify(keypair_name))
- response.write(keypair.private_key)
- response['Content-Length'] = str(len(response.content))
-
- return response
-
-
@urls.register
class Services(generic.View):
"""API for nova services.
diff --git a/openstack_dashboard/dashboards/project/key_pairs/forms.py b/openstack_dashboard/dashboards/project/key_pairs/forms.py
index add286d93f..f63ba0ef58 100644
--- a/openstack_dashboard/dashboards/project/key_pairs/forms.py
+++ b/openstack_dashboard/dashboards/project/key_pairs/forms.py
@@ -36,29 +36,6 @@ KEYPAIR_ERROR_MESSAGES = {
'and may not be white space.')}
-class CreateKeypair(forms.SelfHandlingForm):
- name = forms.RegexField(max_length=255,
- label=_("Key Pair Name"),
- regex=KEYPAIR_NAME_REGEX,
- error_messages=KEYPAIR_ERROR_MESSAGES)
-
- def handle(self, request, data):
- return True # We just redirect to the download view.
-
- def clean(self):
- cleaned_data = super(CreateKeypair, self).clean()
- name = cleaned_data.get('name')
- try:
- keypairs = api.nova.keypair_list(self.request)
- except Exception:
- exceptions.handle(self.request, ignore=True)
- keypairs = []
- if name in [keypair.name for keypair in keypairs]:
- error_msg = _("The name is already in use.")
- self._errors['name'] = self.error_class([error_msg])
- return cleaned_data
-
-
class ImportKeypair(forms.SelfHandlingForm):
name = forms.RegexField(max_length=255,
label=_("Key Pair Name"),
diff --git a/openstack_dashboard/dashboards/project/key_pairs/tables.py b/openstack_dashboard/dashboards/project/key_pairs/tables.py
index c6fe9366b2..249b122616 100644
--- a/openstack_dashboard/dashboards/project/key_pairs/tables.py
+++ b/openstack_dashboard/dashboards/project/key_pairs/tables.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from django.core import urlresolvers
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
@@ -78,16 +79,28 @@ class ImportKeyPair(QuotaKeypairMixin, tables.LinkAction):
return True
-class CreateKeyPair(QuotaKeypairMixin, tables.LinkAction):
- name = "create"
+class CreateLinkNG(QuotaKeypairMixin, tables.LinkAction):
+ name = "create-keypair-ng"
verbose_name = _("Create Key Pair")
- url = "horizon:project:key_pairs:create"
- classes = ("ajax-modal",)
+ url = "horizon:project:key_pairs:index"
+ classes = ("btn-launch",)
icon = "plus"
policy_rules = (("compute", "os_compute_api:os-keypairs:create"),)
+ def get_default_attrs(self):
+ url = urlresolvers.reverse(self.url)
+ ngclick = "modal.createKeyPair({ successUrl: '%s' })" % url
+ self.attrs.update({
+ 'ng-controller': 'KeypairController as modal',
+ 'ng-click': ngclick
+ })
+ return super(CreateLinkNG, self).get_default_attrs()
+
+ def get_link_url(self, datum=None):
+ return "javascript:void(0);"
+
def allowed(self, request, keypair=None):
- if super(CreateKeyPair, self).allowed(request, keypair):
+ if super(CreateLinkNG, self).allowed(request, keypair):
self.verbose_name = _("Create Key Pair")
return True
@@ -113,6 +126,6 @@ class KeyPairsTable(tables.DataTable):
class Meta(object):
name = "keypairs"
verbose_name = _("Key Pairs")
- table_actions = (CreateKeyPair, ImportKeyPair, DeleteKeyPairs,
+ table_actions = (CreateLinkNG, ImportKeyPair, DeleteKeyPairs,
KeypairsFilterAction,)
row_actions = (DeleteKeyPairs,)
diff --git a/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/_create.html b/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/_create.html
deleted file mode 100644
index 652689e168..0000000000
--- a/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/_create.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "horizon/common/_modal_form.html" %}
-{% load i18n %}
-
-{% block modal-body-right %}
- {% trans "Key pairs are SSH credentials which are injected into images when they are launched. Creating a new key pair registers the public key and downloads the private key (a .pem file)." %}
- {% trans "Protect and use the key as you would any normal SSH private key." %}
-{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/create.html b/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/create.html
deleted file mode 100644
index 26914557b6..0000000000
--- a/openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/create.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block title %}{{ page_title }}{% endblock %}
-
-{% block main %}
- {% include 'project/key_pairs/_create.html' %}
-{% endblock %}
-
diff --git a/openstack_dashboard/dashboards/project/key_pairs/tests.py b/openstack_dashboard/dashboards/project/key_pairs/tests.py
index c7f9027886..7fd5c9bdeb 100644
--- a/openstack_dashboard/dashboards/project/key_pairs/tests.py
+++ b/openstack_dashboard/dashboards/project/key_pairs/tests.py
@@ -22,8 +22,6 @@ from mox3.mox import IsA
import six
from openstack_dashboard import api
-from openstack_dashboard.dashboards.project.key_pairs.forms \
- import CreateKeypair
from openstack_dashboard.dashboards.project.key_pairs.forms \
import KEYPAIR_ERROR_MESSAGES
from openstack_dashboard.test import helpers as test
@@ -80,37 +78,6 @@ class KeyPairTests(test.TestCase):
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
- def test_create_keypair_get(self):
- res = self.client.get(
- reverse('horizon:project:key_pairs:create'))
- self.assertTemplateUsed(
- res, 'project/key_pairs/create.html')
-
- def test_download_keypair_get(self):
- keypair_name = "keypair"
- context = {'keypair_name': keypair_name}
- url = reverse('horizon:project:key_pairs:download',
- kwargs={'keypair_name': keypair_name})
- res = self.client.get(url, context)
- self.assertTemplateUsed(
- res, 'project/key_pairs/download.html')
-
- @test.create_stubs({api.nova: ('keypair_create',)})
- def test_generate_keypair_get(self):
- keypair = self.keypairs.first()
- keypair.private_key = "secret"
-
- api.nova.keypair_create(IsA(http.HttpRequest),
- keypair.name).AndReturn(keypair)
- self.mox.ReplayAll()
-
- context = {'keypair_name': keypair.name}
- url = reverse('horizon:project:key_pairs:generate',
- kwargs={'keypair_name': keypair.name})
- res = self.client.get(url, context)
-
- self.assertTrue(res.has_header('content-disposition'))
-
@test.create_stubs({api.nova: ('keypair_get',)})
def test_keypair_detail_get(self):
keypair = self.keypairs.first()
@@ -126,22 +93,6 @@ class KeyPairTests(test.TestCase):
res = self.client.get(url, context)
self.assertContains(res, "%s " % keypair.name, 1, 200)
- @test.create_stubs({api.nova: ("keypair_create", "keypair_delete",)})
- def test_regenerate_keypair_get(self):
- keypair = self.keypairs.first()
- keypair.private_key = "secret"
- optional_param = "regenerate"
- api.nova.keypair_delete(IsA(http.HttpRequest), keypair.name)
- api.nova.keypair_create(IsA(http.HttpRequest),
- keypair.name).AndReturn(keypair)
- self.mox.ReplayAll()
- url = reverse('horizon:project:key_pairs:generate',
- kwargs={'keypair_name': keypair.name,
- 'optional': optional_param})
- res = self.client.get(url)
-
- self.assertTrue(res.has_header('content-disposition'))
-
@test.create_stubs({api.nova: ("keypair_import",)})
def test_import_keypair(self):
key1_name = "new_key_pair"
@@ -203,22 +154,6 @@ class KeyPairTests(test.TestCase):
msg = six.text_type(KEYPAIR_ERROR_MESSAGES['invalid'])
self.assertFormErrors(res, count=1, message=msg)
- @test.create_stubs({api.nova: ("keypair_create",)})
- def test_generate_keypair_exception(self):
- keypair = self.keypairs.first()
-
- api.nova.keypair_create(IsA(http.HttpRequest), keypair.name) \
- .AndRaise(self.exceptions.nova)
- self.mox.ReplayAll()
-
- context = {'keypair_name': keypair.name}
- url = reverse('horizon:project:key_pairs:generate',
- kwargs={'keypair_name': keypair.name})
- res = self.client.get(url, context)
-
- self.assertRedirectsNoFollow(
- res, reverse('horizon:project:key_pairs:index'))
-
@test.create_stubs({api.nova: ("keypair_import",)})
def test_import_keypair_with_regex_defined_name(self):
key1_name = "new-key-pair with_regex"
@@ -235,42 +170,3 @@ class KeyPairTests(test.TestCase):
url = reverse('horizon:project:key_pairs:import')
res = self.client.post(url, formData)
self.assertMessageCount(res, success=1)
-
- @test.create_stubs({api.nova: ("keypair_create",)})
- def test_create_keypair_with_regex_name_get(self):
- keypair = self.keypairs.first()
- keypair.name = "key-space pair-regex_name-0123456789"
- keypair.private_key = "secret"
-
- api.nova.keypair_create(IsA(http.HttpRequest),
- keypair.name).AndReturn(keypair)
- self.mox.ReplayAll()
-
- context = {'keypair_name': keypair.name}
- url = reverse('horizon:project:key_pairs:generate',
- kwargs={'keypair_name': keypair.name})
- res = self.client.get(url, context)
-
- self.assertTrue(res.has_header('content-disposition'))
-
- def test_download_with_regex_name_get(self):
- keypair_name = "key pair-regex_name-0123456789"
- context = {'keypair_name': keypair_name}
- url = reverse('horizon:project:key_pairs:download',
- kwargs={'keypair_name': keypair_name})
- res = self.client.get(url, context)
- self.assertTemplateUsed(
- res, 'project/key_pairs/download.html')
-
- @test.create_stubs({api.nova: ('keypair_list',)})
- def test_create_duplicate_keypair(self):
- keypair_name = self.keypairs.first().name
-
- api.nova.keypair_list(IsA(http.HttpRequest)) \
- .AndReturn(self.keypairs.list())
- self.mox.ReplayAll()
-
- form = CreateKeypair(self.request, data={'name': keypair_name})
- self.assertFalse(form.is_valid())
- self.assertIn('The name is already in use.',
- form.errors['name'][0])
diff --git a/openstack_dashboard/dashboards/project/key_pairs/urls.py b/openstack_dashboard/dashboards/project/key_pairs/urls.py
index afe68a2977..9c5b2015f1 100644
--- a/openstack_dashboard/dashboards/project/key_pairs/urls.py
+++ b/openstack_dashboard/dashboards/project/key_pairs/urls.py
@@ -21,14 +21,7 @@ from openstack_dashboard.dashboards.project.key_pairs import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
- url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^import/$', views.ImportView.as_view(), name='import'),
- url(r'^(?P[^/]+)/download/$', views.DownloadView.as_view(),
- name='download'),
- url(r'^(?P[^/]+)/generate/$', views.GenerateView.as_view(),
- name='generate'),
- url(r'^(?P[^/]+)/(?P[^/]+)/generate/$',
- views.GenerateView.as_view(), name='generate'),
url(r'^(?P[^/]+)/$', views.DetailView.as_view(),
name='detail'),
]
diff --git a/openstack_dashboard/dashboards/project/key_pairs/views.py b/openstack_dashboard/dashboards/project/key_pairs/views.py
index 81bf525b2c..fb4a0598ae 100644
--- a/openstack_dashboard/dashboards/project/key_pairs/views.py
+++ b/openstack_dashboard/dashboards/project/key_pairs/views.py
@@ -14,12 +14,7 @@
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
-from django import http
-from django.template.defaultfilters import slugify
-from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _
-from django.views.decorators.cache import cache_control
-from django.views.decorators.cache import never_cache
from horizon import exceptions
from horizon import forms
@@ -55,20 +50,6 @@ class IndexView(tables.DataTableView):
return keypairs
-class CreateView(forms.ModalFormView):
- form_class = key_pairs_forms.CreateKeypair
- template_name = 'project/key_pairs/create.html'
- submit_url = reverse_lazy(
- "horizon:project:key_pairs:create")
- success_url = 'horizon:project:key_pairs:download'
- submit_label = page_title = _("Create Key Pair")
- cancel_url = reverse_lazy("horizon:project:key_pairs:index")
-
- def get_success_url(self):
- return reverse(self.success_url,
- kwargs={"keypair_name": self.request.POST['name']})
-
-
class ImportView(forms.ModalFormView):
form_class = key_pairs_forms.ImportKeypair
template_name = 'project/key_pairs/import.html'
@@ -103,37 +84,3 @@ class DetailView(views.HorizonTemplateView):
context = super(DetailView, self).get_context_data(**kwargs)
context['keypair'] = self._get_data()
return context
-
-
-class DownloadView(views.HorizonTemplateView):
- template_name = 'project/key_pairs/download.html'
- page_title = _("Download Key Pair")
-
- def get_context_data(self, keypair_name=None):
- return {'keypair_name': keypair_name}
-
-
-class GenerateView(views.HorizonTemplateView):
- # TODO(Itxaka): Remove cache_control in django >= 1.9
- # https://code.djangoproject.com/ticket/13008
- @method_decorator(cache_control(max_age=0, no_cache=True,
- no_store=True, must_revalidate=True))
- @method_decorator(never_cache)
- def get(self, request, keypair_name=None, optional=None):
- try:
- if optional == "regenerate":
- nova.keypair_delete(request, keypair_name)
-
- keypair = nova.keypair_create(request, keypair_name)
- except Exception:
- redirect = reverse('horizon:project:key_pairs:index')
- exceptions.handle(self.request,
- _('Unable to create key pair: %(exc)s'),
- redirect=redirect)
-
- response = http.HttpResponse(content_type='application/binary')
- response['Content-Disposition'] = ('attachment; filename=%s.pem'
- % slugify(keypair.name))
- response.write(keypair.private_key)
- response['Content-Length'] = str(len(response.content))
- return response
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js
index 5cffddafc1..b1a1bec8f5 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js
@@ -24,9 +24,7 @@
LaunchInstanceCreateKeyPairController.$inject = [
'$uibModalInstance',
'existingKeypairs',
- 'horizon.app.core.openstack-service-api.nova',
- 'horizon.framework.widgets.toast.service',
- 'horizon.app.core.openstack-service-api.keypair-download-service'
+ 'horizon.app.core.openstack-service-api.nova'
];
/**
@@ -35,22 +33,21 @@
* @param {Object} $uibModalInstance
* @param {Object} existingKeypairs
* @param {Object} nova
- * @param {Object} toastService
- * @param {Object} keypairDownloadService
* @description
* Provide a dialog for creation of a new key pair.
* @returns {undefined} Returns nothing
*/
- function LaunchInstanceCreateKeyPairController($uibModalInstance, existingKeypairs, nova,
- toastService, keypairDownloadService) {
+ function LaunchInstanceCreateKeyPairController($uibModalInstance, existingKeypairs, nova) {
var ctrl = this;
ctrl.submit = submit;
ctrl.cancel = cancel;
ctrl.doesKeypairExist = doesKeypairExist;
+ ctrl.generate = generate;
ctrl.keypair = '';
ctrl.keypairExistsError = gettext('Keypair already exists or name contains bad characters.');
+ ctrl.copyPrivateKey = copyPrivateKey;
/*
* @ngdoc function
@@ -62,6 +59,21 @@
return exists(ctrl.keypair);
}
+ function generate() {
+ nova.createKeypair({name: ctrl.keypair}).then(onKeypairCreated);
+
+ function onKeypairCreated(data) {
+ ctrl.createdKeypair = data.data;
+ ctrl.privateKey = ctrl.createdKeypair.private_key;
+ ctrl.publicKey = ctrl.createdKeypair.public_key;
+ }
+ }
+
+ function copyPrivateKey() {
+ angular.element('textarea').select();
+ document.execCommand('copy');
+ }
+
/*
* @ngdoc function
* @name exists
@@ -84,17 +96,7 @@
* notified of the problem and given the opportunity to try again.
*/
function submit() {
- keypairDownloadService.createAndDownloadKeypair(ctrl.keypair).then(
- function success(createdKeypair) {
- createdKeypair.regenerateUrl = nova.getRegenerateKeypairUrl(createdKeypair.name);
- $uibModalInstance.close(createdKeypair);
- },
- function error() {
- var errorMessage = interpolate(gettext('Unable to generate "%s". Please try again.'),
- [ctrl.keypair]);
- toastService.add('error', errorMessage);
- }
- );
+ $uibModalInstance.close(ctrl.createdKeypair);
}
/*
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.spec.js
index e30e14d229..57b81a05fd 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.spec.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.spec.js
@@ -77,36 +77,16 @@
});
it('should close the modal and return the created keypair', function() {
- mockCreationSuccess = true;
- mockKeypair = {
- name: "newKeypair"
- };
- spyOn(createKeypairServiceMock, 'getRegenerateKeypairUrl').and.returnValue(
- "a url"
- );
spyOn(modalInstanceMock, 'close');
+ ctrl.createdKeypair = {name: 'newKeypair'};
ctrl.submit();
expect(modalInstanceMock.close).toHaveBeenCalledWith({
- name: "newKeypair",
- regenerateUrl: "a url"
+ name: "newKeypair"
});
});
- it('should raise a toast error message when create is unsuccessful', function() {
- mockCreationSuccess = false;
- spyOn(toastServiceMock, 'add');
-
- ctrl.keypair = "aKeypair";
- ctrl.submit();
-
- expect(toastServiceMock.add).toHaveBeenCalledWith(
- 'error',
- 'Unable to generate "aKeypair". Please try again.'
- );
- });
-
it('defines a submit function', function() {
expect(ctrl.submit).toBeDefined();
});
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html
index 7b19be6d7a..148323f068 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html
@@ -29,17 +29,30 @@
{$ ctrl.keypairExistsError $}
+
+
+ Private Key
+
+
+
+
+
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.js
index 83809b3598..58ac7d1a1c 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.js
@@ -36,6 +36,7 @@
* @param {Object} launchInstanceModel
* @param {Object} $uibModal
* @param {Object} toastService
+ * @param {Object} settingsService
* @description
* Allows selection of key pairs.
* @returns {undefined} No return value
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.html
index 5cae897c49..73f4154bae 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.html
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.html
@@ -4,18 +4,6 @@
You may select an existing key pair, import a key pair, or generate a new key pair.
-
-
A key pair named '{$ctrl.createdKeypair.name$}' was successfully created. This key pair should automatically download.
-
If not, you can manually download this keypair at the following link:
-
-
- {$ctrl.createdKeypair.name$}
-
-
- Note: you will not be able to download this later.
-
-
-
diff --git a/openstack_dashboard/static/app/core/core.module.js b/openstack_dashboard/static/app/core/core.module.js
index 121c714ec5..901a090faa 100644
--- a/openstack_dashboard/static/app/core/core.module.js
+++ b/openstack_dashboard/static/app/core/core.module.js
@@ -36,6 +36,7 @@
'horizon.app.core.cloud-services',
'horizon.app.core.flavors',
'horizon.app.core.images',
+ 'horizon.app.core.keypairs',
'horizon.app.core.metadata',
'horizon.app.core.openstack-service-api',
'horizon.app.core.trunks',
diff --git a/openstack_dashboard/static/app/core/keypairs/keypair.controller.js b/openstack_dashboard/static/app/core/keypairs/keypair.controller.js
new file mode 100644
index 0000000000..5e9dfc0651
--- /dev/null
+++ b/openstack_dashboard/static/app/core/keypairs/keypair.controller.js
@@ -0,0 +1,97 @@
+/*
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+ 'use strict';
+
+ angular
+ .module('horizon.app.core.keypairs')
+ .controller('KeypairController', KeypairController);
+
+ KeypairController.$inject = [
+ 'horizon.dashboard.project.workflow.launch-instance.basePath',
+ 'horizon.app.core.openstack-service-api.nova',
+ 'horizon.framework.widgets.modal-wait-spinner.service',
+ '$window',
+ '$uibModal'
+ ];
+
+ /**
+ * @ngdoc controller
+ * @name KeypairController
+ * @param {string} basePath
+ * @param {Object} $uibModal
+ * @description
+ * Allows creation of key pairs.
+ * @returns {undefined} No return value
+ */
+ function KeypairController(
+ basePath,
+ nova,
+ spinnerService,
+ $window,
+ $uibModal
+ ) {
+ var ctrl = this;
+
+ ctrl.createKeyPair = createKeyPair;
+
+ //////////
+
+ function setKeyPairs(config) {
+ return function(response) {
+ var keyPairs = response.data.items.map(getName);
+
+ $uibModal.open({
+ templateUrl: basePath + 'keypair/create-keypair.html',
+ controller: 'LaunchInstanceCreateKeyPairController as ctrl',
+ windowClass: 'modal-dialog-wizard',
+ resolve: {
+ existingKeypairs: getKeypairs
+ }
+ }).result.then(go(config.successUrl));
+
+ function getName(item) {
+ return item.keypair.name;
+ }
+
+ function getKeypairs() {
+ return keyPairs;
+ }
+ };
+ }
+
+ /**
+ * @ngdoc function
+ * @name createKeyPair
+ * @description
+ * Launches the modal to create a key pair.
+ * @returns {undefined} No return value
+ */
+ function createKeyPair(config) {
+ nova.getKeypairs().then(setKeyPairs(config));
+ }
+
+ function go(url) {
+ return function changeUrl() {
+ spinnerService.showModalSpinner(gettext('Please Wait'));
+ $window.location.href = url;
+ };
+ }
+
+ }
+
+})();
diff --git a/openstack_dashboard/static/app/core/keypairs/keypair.controller.spec.js b/openstack_dashboard/static/app/core/keypairs/keypair.controller.spec.js
new file mode 100644
index 0000000000..29bd3b6d7b
--- /dev/null
+++ b/openstack_dashboard/static/app/core/keypairs/keypair.controller.spec.js
@@ -0,0 +1,80 @@
+/*
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ describe('KeypairController', function() {
+ var ctrl, keyPairCall, $timeout;
+ var nova = {
+ getKeypairs: function() {
+ var kps = {data: {items: [{keypair: {name: 'one'}},{keypair: {name: 'two'}} ]}};
+ keyPairCall.resolve(kps);
+ return keyPairCall.promise;
+ }
+ };
+ var spinner = {
+ showModalSpinner: angular.noop
+ };
+ var config = {successUrl: '/some/where'};
+ var $uibModal = {
+ open: angular.noop
+ };
+ var $window = {location: {}};
+
+ beforeEach(module('horizon.app.core.keypairs'));
+
+ beforeEach(
+ inject(
+ function($controller, $rootScope, $q, _$timeout_) {
+ $timeout = _$timeout_;
+ ctrl = $controller('KeypairController', {
+ 'horizon.dashboard.project.workflow.launch-instance.basePath': '/here/',
+ 'horizon.app.core.openstack-service-api.nova': nova,
+ 'horizon.framework.widgets.modal-wait-spinner.service': spinner,
+ '$uibModal': $uibModal,
+ '$window': $window
+ });
+
+ keyPairCall = $q.defer();
+ spyOn($uibModal, 'open').and.returnValue({result: $q.resolve({})});
+ }
+ )
+ );
+
+ describe('createKeyPair', function() {
+ it('opens the modal', function() {
+ ctrl.createKeyPair(config);
+ $timeout.flush();
+ expect($uibModal.open).toHaveBeenCalled();
+ });
+
+ it('provides a function to existingKeypairs that returns keypair names', function() {
+ ctrl.createKeyPair(config);
+ $timeout.flush();
+ var func = $uibModal.open.calls.argsFor(0)[0].resolve.existingKeypairs;
+ expect(func()).toEqual(['one','two']);
+ });
+
+ it('relocates to the config successUrl', function() {
+ ctrl.createKeyPair(config);
+ $timeout.flush();
+ expect($window.location.href).toBe('/some/where');
+ });
+ });
+ });
+
+})();
diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.module.js b/openstack_dashboard/static/app/core/keypairs/keypairs.module.js
new file mode 100644
index 0000000000..6e2285c001
--- /dev/null
+++ b/openstack_dashboard/static/app/core/keypairs/keypairs.module.js
@@ -0,0 +1,33 @@
+/**
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * @ngdoc overview
+ * @ngname horizon.app.core.keypairs
+ *
+ * @description
+ * Provides all of the services and widgets required
+ * to support and display keypairs related content.
+ */
+ angular
+ .module('horizon.app.core.keypairs', [
+ ])
+ ;
+
+})();
diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js b/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js
new file mode 100644
index 0000000000..15fb1144bd
--- /dev/null
+++ b/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 SUSE LLC
+ *
+ * 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.
+ */
+(function () {
+ 'use strict';
+
+ describe('horizon.app.core.keypairs', function () {
+ it('should be defined', function () {
+ expect(angular.module('horizon.app.core.keypairs')).toBeDefined();
+ });
+ });
+
+})();
diff --git a/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.js b/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.js
deleted file mode 100644
index 63a0e6ffe5..0000000000
--- a/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-(function () {
- 'use strict';
-
- /**
- * @ngdoc overview
- * @name horizon.dashboard.project.workflow.keypair.create-keypair-service
- *
- * @description
- * Service to handle creating keypairs and downloading their private keys.
- * Please note, the implementation has quirks due to what features are
- * available in which browsers. As a result, the implementation involves
- * using iframes to specify downloads. Since the API does not allow the
- * full retrieval of the key pair after creation, and the URL retrieved in
- * an iframe must be a GET method, and the fact that we shouldn't pass
- * data as part of the URL (because it is potentially logged and contains
- * private key data), we have to make the actual API call when performing
- * the GET. This also means that if the user misses the download for some
- * reason, we need to provide the ability to regenerate the key pair,
- * although that is not a feature of this service.
- */
-
- angular
- .module('horizon.app.core.openstack-service-api')
- .factory('horizon.app.core.openstack-service-api.keypair-download-service',
- keypairDownloadService);
-
- keypairDownloadService.$inject = [
- '$document',
- 'horizon.app.core.openstack-service-api.nova',
- '$q',
- '$timeout'
- ];
-
- function keypairDownloadService($document, novaAPI, $q, $timeout) {
-
- var service = {
- createAndDownloadKeypair: createAndDownloadKeypair
- };
-
- return service;
-
- /**
- * @ngdoc function
- * @name createAndDownloadKeypair
- *
- * @description
- * This function performs the actions necessary to begin a download
- * of a newly created key pair. The given name will be used as the
- * logical name of the key pair and will be used to make a file-system-
- * friendly filename.
- * In this implementation, for browser compatibility reasons, the
- * download is achieved by creating an iframe with the given path for
- * the create API call given, so the results are streamed directly to the
- * client. This is not ideal but is due to lack of support in IE for
- * features like the data: protocol. The iframes require that an element
- * with the class of 'download-iframes' is present.
- *
- * @param {string} name The desired name for the key pair
- * @returns {promise} A promise resolving if true, rejecting with error
- */
- function createAndDownloadKeypair(name) {
- addDOMResource(name);
- return verifyCreatedPromise(name);
- }
-
- /**
- * @ngdoc function
- * @name addDOMResource
- *
- * @description
- * This adds an iframe to the body of the current document, using
- * the appropriate URL for the API to create/download the new key pair.
- *
- * @param {string} keypairName The desired name for the key pair
- * @returns {undefined} Returns nothing
- */
- function addDOMResource(keypairName) {
- var url = novaAPI.getCreateKeypairUrl(keypairName);
- var iframe = angular.element("");
- iframe.attr('id', keypairName);
- iframe.attr('src', url);
- iframe.attr('style', 'display: none;');
- if ($document.find('.download-iframes').size() === 0) {
- var iframeContainer = angular.element('
');
- $document.find('body').append(iframeContainer);
- }
- $document.find('.download-iframes').append(iframe);
- }
-
- /**
- * @ngdoc function
- * @name verifyCreatedPromise
- *
- * @description
- * This function returns a promise that tries ten times to see if a
- * key pair of the given name exists in the key pair listing. These
- * tries are one second apart. Once it has been found, the promise
- * is resolved with the key pair data. If it is not found within the
- * period, the promise is rejected.
- *
- * @param {string} name The name for the key pair
- * @returns {promise} A promise resolving if true, rejecting with error
- */
- function verifyCreatedPromise(name) {
- return $q(function doesKeypairExistPromise(resolve, reject) {
-
- doesKeypairExist(10);
-
- function doesKeypairExist(timesToCheck) {
- $timeout(function doesKeypairExistTimeout() {
- novaAPI.getKeypairs().then(function isKeypairInResponse(response) {
-
- var foundKeypairs = response.data.items.filter(function sameName(item) {
- return item.keypair.name === name;
- });
-
- if (foundKeypairs.length === 1) {
- resolve(foundKeypairs[0].keypair);
- angular.element('.download-iframes #' + name).remove();
- } else if (timesToCheck > 1) {
- doesKeypairExist(timesToCheck - 1);
- } else {
- reject();
- angular.element('.download-iframes #' + name).remove();
- }
-
- });
- },
- 1000);
- }
-
- });
- }
-
- }
-
-})();
diff --git a/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.spec.js b/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.spec.js
deleted file mode 100644
index d4236fb063..0000000000
--- a/openstack_dashboard/static/app/core/openstack-service-api/keypair-download.service.spec.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-(function() {
- 'use strict';
-
- describe('Download Keypair Service', function() {
-
- var service, $scope, existingKeypairs, $timeout;
-
- var documentMock = {
- find: function() {
- return documentFindMock;
- }
- };
-
- var documentFindMock = {
- append : angular.noop,
- load: function (callback) {
- callback();
- },
- size: function() {
- return 1;
- }
- };
-
- var novaAPIMock = {
- getKeypairs: function() {
- return {
- then: function(callback) {
- callback(existingKeypairs);
- }
- };
- },
- getCreateKeypairUrl: function() {
- return "/some/given/path";
- }
- };
-
- beforeEach(module('horizon.app.core.openstack-service-api'));
-
- beforeEach(module(function ($provide) {
- $provide.value('horizon.app.core.openstack-service-api.nova', novaAPIMock);
- $provide.value('$document', documentMock);
- }));
-
- beforeEach(inject(function (_$injector_, _$rootScope_, _$timeout_) {
- service = _$injector_.get(
- 'horizon.app.core.openstack-service-api.keypair-download-service'
- );
- $scope = _$rootScope_.$new();
- $timeout = _$timeout_;
- }));
-
- it('adds the download key pair endpoint as a resource to the DOM', function() {
- spyOn(documentFindMock, 'append');
- spyOn(documentFindMock, 'load').and.returnValue({});
-
- service.createAndDownloadKeypair("newKeypair");
-
- var passedObj = documentFindMock.append.calls.argsFor(0)[0];
- expect(passedObj).toBeDefined();
- expect(passedObj.attr('id')).toBe("newKeypair");
- });
-
- it('encodes the URI component given slashes, etc.', function() {
- spyOn(documentFindMock, 'append');
- spyOn(documentFindMock, 'load').and.returnValue({});
-
- service.createAndDownloadKeypair("new/Keypair");
-
- var passedObj = documentFindMock.append.calls.argsFor(0)[0];
- expect(passedObj).toBeDefined();
- expect(passedObj.attr('id')).toBe("new/Keypair");
- });
-
- it('creates a div with download-iframes if not present', function() {
- spyOn(documentFindMock, 'append');
- spyOn(documentFindMock, 'load').and.returnValue({});
- spyOn(documentFindMock, 'size').and.returnValue(0);
-
- service.createAndDownloadKeypair("new/Keypair");
-
- expect(documentFindMock.append.calls.count()).toBe(2);
- expect(documentFindMock.append.calls.allArgs().some(function(x) {
- return x[0].attr('class') === 'download-iframes';
- })).toBe(true);
- });
-
- it('checks that the keypair was added and returns a success promise result', function() {
-
- existingKeypairs = {
- data: {
- items:[{
- keypair: {
- name: "newKeypair"
- }
- }]
- }
- };
-
- var promiseSuccessful, keypair;
-
- service.createAndDownloadKeypair("newKeypair").then(
- function success(createdKeypair) {
- promiseSuccessful = true;
- keypair = createdKeypair;
- }
- );
-
- $timeout.flush();
- $scope.$apply();
-
- expect(promiseSuccessful).toEqual(true);
- expect(keypair).toEqual({name: "newKeypair"});
- });
-
- it('checks that the keypair was not added and returns a error promise result', function() {
-
- existingKeypairs = {
- data: {
- items:[]
- }
- };
-
- var promiseErrored;
-
- service.createAndDownloadKeypair("newKeypair").then(
- function success() {},
- function error() {
- promiseErrored = true;
- }
- );
-
- // checks 10 times after one second
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
- $timeout.flush();
-
- $scope.$apply();
-
- expect(promiseErrored).toEqual(true);
- });
-
- });
-})();
diff --git a/openstack_dashboard/static/app/core/openstack-service-api/nova.service.js b/openstack_dashboard/static/app/core/openstack-service-api/nova.service.js
index ce05c16f91..0314873988 100644
--- a/openstack_dashboard/static/app/core/openstack-service-api/nova.service.js
+++ b/openstack_dashboard/static/app/core/openstack-service-api/nova.service.js
@@ -23,20 +23,18 @@
novaAPI.$inject = [
'horizon.framework.util.http.service',
- 'horizon.framework.widgets.toast.service',
- '$window'
+ 'horizon.framework.widgets.toast.service'
];
/**
* @ngdoc service
* @param {Object} apiService
* @param {Object} toastService
- * @param {Object} $window
* @name novaApi
* @description Provides access to Nova APIs.
* @returns {Object} The service
*/
- function novaAPI(apiService, toastService, $window) {
+ function novaAPI(apiService, toastService) {
var service = {
getActionList: getActionList,
@@ -71,8 +69,6 @@
getServices: getServices,
getInstanceMetadata: getInstanceMetadata,
editInstanceMetadata: editInstanceMetadata,
- getCreateKeypairUrl: getCreateKeypairUrl,
- getRegenerateKeypairUrl: getRegenerateKeypairUrl,
createFlavor: createFlavor,
updateFlavor: updateFlavor,
deleteFlavor: deleteFlavor,
@@ -747,44 +743,6 @@
});
}
- /**
- * @ngdoc function
- * @name getCreateKeypairUrl
- *
- * @description
- * Returns a URL, respecting WEBROOT, that if called as a REST call
- * would create and return a new key pair with the given name. This
- * function is provided because to perform a download of the key pair,
- * an iframe must be given a URL to use (which is further explained in
- * the key pair download service).
- *
- * @param {string} keyPairName
- * @returns {Object} The result of the API call
- */
- function getCreateKeypairUrl(keyPairName) {
- // NOTE: WEBROOT by definition must end with a slash (local_settings.py).
- return $window.WEBROOT + "api/nova/keypairs/" +
- encodeURIComponent(keyPairName) + "/";
- }
-
- /**
- * @ngdoc function
- * @name getRegenerateKeypairUrl
- *
- * @description
- * Returns a URL, respecting WEBROOT, that if called as a REST call
- * would regenereate an existing key pair with the given name and return
- * the new key pair data. This function is provided because to perform
- * a download of the key pair, an iframe must be given a URL to use
- * (which is further explained in the key pair download service).
- *
- * @param {string} keyPairName
- * @returns {Object} The result of the API call
- */
- function getRegenerateKeypairUrl(keyPairName) {
- return getCreateKeypairUrl(keyPairName) + "?regenerate=true";
- }
-
/**
* @name createServerSnapshot
* @param {Object} newSnapshot - The new server snapshot
diff --git a/openstack_dashboard/static/app/core/openstack-service-api/nova.service.spec.js b/openstack_dashboard/static/app/core/openstack-service-api/nova.service.spec.js
index bd92338e5a..5d07974e58 100644
--- a/openstack_dashboard/static/app/core/openstack-service-api/nova.service.spec.js
+++ b/openstack_dashboard/static/app/core/openstack-service-api/nova.service.spec.js
@@ -560,53 +560,4 @@
});
- //// This is separated due to differences in what is being tested.
- describe('Keypair functions', function() {
-
- var service, $window;
-
- beforeEach(module('horizon.app.core.openstack-service-api'));
-
- beforeEach(module(function ($provide) {
- $provide.value('horizon.framework.util.http.service', {});
- $provide.value('horizon.framework.widgets.toast.service', {});
- }));
-
- beforeEach(inject(function (_$injector_, _$rootScope_, _$timeout_, _$window_) {
- service = _$injector_.get(
- 'horizon.app.core.openstack-service-api.nova'
- );
- $window = _$window_;
- $window.WEBROOT = '/';
- }));
-
- afterEach(inject(function (_$window_) {
- $window = _$window_;
- $window.WEBROOT = '/';
- }));
-
- it('returns a link to download the private key for an existing keypair', function() {
- var link = service.getCreateKeypairUrl("keypairName");
- expect(link).toEqual('/api/nova/keypairs/keypairName/');
- });
-
- it('returns a WEBROOT link to download the private key for an existing keypair', function() {
- $window.WEBROOT = '/myroot/';
- var link = service.getCreateKeypairUrl("keypairName");
- expect(link).toEqual('/myroot/api/nova/keypairs/keypairName/');
- });
-
- it('returns a link to redownload the private key for an existing keypair', function() {
- var link = service.getRegenerateKeypairUrl("keypairName");
- expect(link).toEqual('/api/nova/keypairs/keypairName/?regenerate=true');
- });
-
- it('returns a WEBROOT link to redownload the private key for an existing keypair', function() {
- $window.WEBROOT = '/myroot/';
- var link = service.getRegenerateKeypairUrl("keypairName");
- expect(link).toEqual('/myroot/api/nova/keypairs/keypairName/?regenerate=true');
- });
-
- });
-
})();
diff --git a/openstack_dashboard/test/api_tests/nova_rest_tests.py b/openstack_dashboard/test/api_tests/nova_rest_tests.py
index c3d0cbfa8e..58e3d30dbf 100644
--- a/openstack_dashboard/test/api_tests/nova_rest_tests.py
+++ b/openstack_dashboard/test/api_tests/nova_rest_tests.py
@@ -22,8 +22,6 @@ from openstack_dashboard.api.rest import nova
from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
-from novaclient import exceptions
-
class NovaRestTestCase(test.TestCase):
#
@@ -216,67 +214,6 @@ class NovaRestTestCase(test.TestCase):
self.assertEqual('/api/nova/keypairs/Ni%21', response['location'])
nc.keypair_import.assert_called_once_with(request, 'Ni!', 'hi')
- def test_keypair_create_and_download(self):
- self._test_keypair_create_and_download(False)
-
- def test_keypair_recreate_and_download(self):
- self._test_keypair_create_and_download(True)
-
- @mock.patch.object(nova.api, 'nova')
- def _test_keypair_create_and_download(self, recreate_keypair, nc):
- params = {}
-
- if recreate_keypair:
- params = {'regenerate': 'true'}
-
- request = self.mock_rest_request(GET=params)
-
- keypair_create_response = mock.Mock()
- keypair_create_response.private_key = "private key content"
- nc.keypair_create.return_value = keypair_create_response
-
- with mock.patch.object(settings, 'DEBUG', True):
- response = nova.Keypair().get(request, "Ni!")
-
- if recreate_keypair:
- nc.keypair_delete.assert_called_once_with(request, 'Ni!')
- else:
- nc.keypair_delete.assert_not_called()
-
- nc.keypair_create.assert_called_once_with(request, 'Ni!')
- self.assertStatusCode(response, 200)
- self.assertEqual(
- 'attachment; filename=ni.pem',
- response['Content-Disposition'])
- self.assertEqual(
- "private key content",
- response.content.decode('utf-8'))
- self.assertEqual('19', response['Content-Length'])
-
- @mock.patch.object(nova.api, 'nova')
- def test_keypair_fail_to_create_because_already_exists(self, nc):
- request = self.mock_rest_request(GET={})
-
- conflict_exception = exceptions.Conflict(409, 'keypair exists!')
- nc.keypair_create.side_effect = conflict_exception
-
- with mock.patch.object(settings, 'DEBUG', True):
- response = nova.Keypair().get(request, "Ni!")
-
- self.assertEqual(409, response.status_code)
-
- @mock.patch.object(nova.api, 'nova')
- def test_keypair_fail_to_create(self, nc):
- request = self.mock_rest_request(GET={})
-
- surprise_exception = exceptions.ClientException(501, 'Boom!')
- nc.keypair_create.side_effect = surprise_exception
-
- with mock.patch.object(settings, 'DEBUG', True):
- response = nova.Keypair().get(request, "Ni!")
-
- self.assertEqual(500, response.status_code)
-
#
# Availability Zones
#