From c4d208e457abc7c214f286d52064375df7e64e40 Mon Sep 17 00:00:00 2001 From: Pino de Candia <32303022+pinodeca@users.noreply.github.com> Date: Tue, 23 Jan 2018 14:51:15 -0600 Subject: [PATCH] Add modals for User Cert create and revoke. --- tatudashboard/rest_api/passthrough.py | 16 ++ .../resources/os-tatu-user/actions.module.js | 66 ++++++ .../resources/os-tatu-user/api.service.js | 37 ++++ .../resources/os-tatu-user/create.service.js | 141 ++++++++++++ .../resources/os-tatu-user/revoke.service.js | 203 ++++++++++++++++++ tatudashboard/static/tatudashboard/user.html | 2 +- 6 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 tatudashboard/static/tatudashboard/resources/os-tatu-user/actions.module.js create mode 100644 tatudashboard/static/tatudashboard/resources/os-tatu-user/create.service.js create mode 100644 tatudashboard/static/tatudashboard/resources/os-tatu-user/revoke.service.js diff --git a/tatudashboard/rest_api/passthrough.py b/tatudashboard/rest_api/passthrough.py index 2cd5632..0208bf2 100644 --- a/tatudashboard/rest_api/passthrough.py +++ b/tatudashboard/rest_api/passthrough.py @@ -87,6 +87,22 @@ def _get_service_url(request, service): return service_url +@urls.register +class UserCert(generic.View): + """Pass-through API for executing service requests. + + Horizon only adds auth and CORS proxying. + """ + url_regex = r'ssh/usergen/(?P.+)$' + + @rest_utils.ajax() + def post(self, request, path): + data = dict(request.DATA) if request.DATA else {} + data['user_id'] = request.user.id + data['auth_id'] = request.user.project_id + return passthrough_post(path, request, data).json() + + @urls.register class Passthrough(generic.View): """Pass-through API for executing service requests. diff --git a/tatudashboard/static/tatudashboard/resources/os-tatu-user/actions.module.js b/tatudashboard/static/tatudashboard/resources/os-tatu-user/actions.module.js new file mode 100644 index 0000000..c0a09f7 --- /dev/null +++ b/tatudashboard/static/tatudashboard/resources/os-tatu-user/actions.module.js @@ -0,0 +1,66 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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 designatedashboard.resources.os-tatu-user.actions + * + * @description + * Provides all of the actions for Tatu User Certs. + */ + angular.module('tatudashboard.resources.os-tatu-user.actions', [ + 'horizon.framework.conf', + 'horizon.app.core' + ]) + .run(run); + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'tatudashboard.resources.os-tatu-user.resourceType', + 'tatudashboard.resources.os-tatu-user.actions.create', + 'tatudashboard.resources.os-tatu-user.actions.revoke' + ]; + + function run(registry, + resourceTypeString, + createAction, + revokeAction) { + var resourceType = registry.getResourceType(resourceTypeString); + resourceType + .globalActions + .append({ + id: 'create', + service: createAction, + template: { + text: gettext('Create User SSH Certificate') + } + }); + + resourceType + .itemActions + .append({ + id: 'revoke', + service: revokeAction, + template: { + text: gettext('Revoke'), + } + }); + } + +})(); diff --git a/tatudashboard/static/tatudashboard/resources/os-tatu-user/api.service.js b/tatudashboard/static/tatudashboard/resources/os-tatu-user/api.service.js index 192b325..1974427 100644 --- a/tatudashboard/static/tatudashboard/resources/os-tatu-user/api.service.js +++ b/tatudashboard/static/tatudashboard/resources/os-tatu-user/api.service.js @@ -36,6 +36,8 @@ */ function apiService(apiPassthroughUrl, httpService, toastService) { var service = { + create: create, + revoke: revoke, get: get, list: list }; @@ -44,6 +46,41 @@ /////////////// + /** + * @name create + * @description + * Create a User Certificate + * + * @param {Object} data + * Specifies the User Certificate information to create + * + * @returns {Object} The created user object + */ + function create(data) { + return httpService.post(apiPassthroughUrl + 'usergen/noauth/usercerts/', data) + .error(function() { + toastService.add('error', gettext('Unable to create the certificate.')); + }) + } + + /** + * @name revoke + * @description + * Revoke a single certificate + * + * @param {Object} user + * Specifies the user certificate to revoke + * + * @returns {Object} The revoked user certificate + */ + function revoke(user) { + var data = { 'serial': user.serial } + var url = apiPassthroughUrl + 'noauth/revokeduserkeys/' + user.auth_id + '/' + return httpService.post(url, data) + .error(function() { + toastService.add('error', gettext('Unable to revoke the certificate.')); + }) + /** * @name list * @description diff --git a/tatudashboard/static/tatudashboard/resources/os-tatu-user/create.service.js b/tatudashboard/static/tatudashboard/resources/os-tatu-user/create.service.js new file mode 100644 index 0000000..b0db192 --- /dev/null +++ b/tatudashboard/static/tatudashboard/resources/os-tatu-user/create.service.js @@ -0,0 +1,141 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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('tatudashboard.resources.os-tatu-user.actions') + .factory('tatudashboard.resources.os-tatu-user.actions.create', action); + + action.$inject = [ + '$q', + 'tatudashboard.resources.os-tatu-user.actions.common-forms', + 'tatudashboard.resources.os-tatu-user.api', + 'tatudashboard.resources.os-tatu-user.resourceType', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name tatudashboard.resources.os-tatu-user.actions.create + * + * @Description + * Brings up the Create User modal. + */ + function action($q, + forms, + api, + resourceTypeName, + policy, + serviceCatalog, + schemaFormModalService, + toast, + waitSpinner) { + var createUserPolicy, sshServiceEnabled; + var title = gettext("Generate User Certificate"); + var message = { + success: gettext('Certificate %s was successfully generated.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + createUserPolicy = policy.ifAllowed({rules: [['ssh', 'create_user_cert']]}); + sshServiceEnabled = serviceCatalog.ifTypeEnabled('ssh'); + } + + function allowed() { + return $q.all([ + createUserPolicy, + sshServiceEnabled + ]); + } + + function perform() { + var formConfig = forms.getCreateFormConfig(); + formConfig.title = title; + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var userModel = angular.copy(context.model); + waitSpinner.showModalSpinner(gettext('Creating User SSH Certificate')); + return api.create(userModel).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var user = response.data; + toast.add('success', interpolate(message.success, [user.serial])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [{type: resourceTypeName, id: user.serial}], + updated: [], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + /** + * Return the create User Cert form. + * @returns {object} a schema form config, including default model + */ + function getCreateFormConfig() { + return { + "schema": { + "type": "object", + "properties": { + "key.pub": { + "type": "string" + } + } + }, + "form": [ + { + "key": "key.pub", + "type": "textarea", + "title": gettext("SSH Public Key"), + "description": gettext("The user's SSH public key."), + "required": true + } + ] + }; + } + } +})(); diff --git a/tatudashboard/static/tatudashboard/resources/os-tatu-user/revoke.service.js b/tatudashboard/static/tatudashboard/resources/os-tatu-user/revoke.service.js new file mode 100644 index 0000000..09380b6 --- /dev/null +++ b/tatudashboard/static/tatudashboard/resources/os-tatu-user/revoke.service.js @@ -0,0 +1,203 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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('tatudashboard.resources.os-tatu-user.actions') + .factory('tatudashboard.resources.os-tatu-user.actions.revoke', action); + + action.$inject = [ + '$q', + 'tatudashboard.resources.os-tatu-user.api', + 'tatudashboard.resources.util', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.modal.simple-modal.service', + 'horizon.framework.widgets.toast.service', + 'tatudashboard.resources.os-tatu-user.resourceType' + ]; + + /* + * @ngdoc factory + * @name tatudashboard.resources.os-tatu-user.actions.revoke + * + * @Description + * Brings up the revoke user confirmation modal dialog. + + * On submit, revoke given user SSH certificate. + * On cancel, do nothing. + */ + function action( + $q, + userApi, + util, + policy, + actionResultService, + gettext, + $qExtensions, + simpleModalService, + toast, + resourceType + ) { + var scope, context, revokeUserPromise; + var notAllowedMessage = gettext("You are not allowed to revoke user certificates: %s"); + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ////////////// + + function initScope(newScope) { + scope = newScope; + context = { }; + revokeUserPromise = policy.ifAllowed({rules: [['ssh', 'revoke_user_cert']]}); + } + + function perform(items) { + var certs = angular.isArray(items) ? items : [items]; + context.labels = labelize(certs.length); + return $qExtensions.allSettled(users.map(checkPermission)).then(afterCheck); + } + + function allowed(user) { + // only row actions pass in user + // otherwise, assume it is a batch action + if (user) { + return $q.all([ + revokeUserPromise + ]); + } else { + return policy.ifAllowed({ rules: [['ssh', 'revoke_user_cert']] }); + } + } + + function checkPermission(user) { + return {promise: allowed(user), context: user}; + } + + function afterCheck(result) { + var outcome = $q.reject(); // Reject the promise by default + if (result.fail.length > 0) { + toast.add('error', getMessage(notAllowedMessage, result.fail.map(getUser))); + outcome = $q.reject(result.fail); + } + if (result.pass.length > 0) { + outcome = open(result.pass.map(getUser)).then(createResult); + } + return outcome; + } + + function createResult(deleteModalResult) { + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + var actionResult = actionResultService.getActionResult(); + deleteModalResult.pass.forEach(function markDeleted(item) { + actionResult.updated(resourceType, getUser(item).serial); + }); + deleteModalResult.fail.forEach(function markFailed(item) { + actionResult.failed(resourceType, getUser(item).serial); + }); + return actionResult.result; + } + + function labelize(count) { + return { + + title: ngettext( + 'Confirm Revoke User Certificate', + 'Confirm Revoke Users Certificates', count), + + message: ngettext( + 'You have selected "%s". Revoked user cert is not recoverable.', + 'You have selected "%s". Revoked users certs are not recoverable.', count), + + submit: ngettext( + 'Revoke User Certificate', + 'Revoke Users Certificates', count), + + success: ngettext( + 'Revoked User Cert: %s.', + 'Revoked Users Certs: %s.', count), + + error: ngettext( + 'Unable to revoke User Certificate: %s.', + 'Unable to revoke Users Certificates: %s.', count) + }; + } + + function open(users) { + var options = { + title: context.labels.title, + body: interpolate(context.labels.message, [users.map(getSerial).join("\", \"")]), + submit: context.labels.submit + }; + + return simpleModalService.modal(options).result.then(onModalSubmit); + + function onModalSubmit() { + return $qExtensions.allSettled(users.map(revokePromise)).then(notify); + } + + function revokePromise(user) { + return {promise: userApi.revoke(user), context: user}; + } + + function notify(result) { + if (result.pass.length > 0) { + var passEntities = result.pass.map(getUser); + scope.$emit(context.successEvent, passEntities.map(getSerial)); + toast.add('success', getMessage(context.labels.success, passEntities)); + } + + if (result.fail.length > 0) { + var failEntities = result.fail.map(getUser); + scope.$emit(context.failedEvent, failEntities.map(getSerial)); + toast.add('error', getMessage(context.labels.error, failEntities)); + } + // Return the passed and failed entities as part of resolving the promise + return result; + } + } + + function getUser(result) { + return result.context; + } + + /** + * Helper method to get the displayed message + */ + function getMessage(message, users) { + return interpolate(message, [users.map(getSerial).join(", ")]); + } + + /** + * Helper method to get the serial number of the user + */ + function getSerial(user) { + return user.serial; + } + + } +})(); diff --git a/tatudashboard/static/tatudashboard/user.html b/tatudashboard/static/tatudashboard/user.html index e26efd7..5efc031 100644 --- a/tatudashboard/static/tatudashboard/user.html +++ b/tatudashboard/static/tatudashboard/user.html @@ -1,4 +1,4 @@ + track-by="serial">