diff --git a/java/google/registry/ui/css/BUILD b/java/google/registry/ui/css/BUILD index 4f02ea3ee..fc479cf07 100644 --- a/java/google/registry/ui/css/BUILD +++ b/java/google/registry/ui/css/BUILD @@ -24,6 +24,7 @@ closure_css_library( closure_css_library( name = "registrar_lib", srcs = [ + "admin-settings.css", "contact-settings.css", "contact-us.css", "dashboard.css", diff --git a/java/google/registry/ui/css/admin-settings.css b/java/google/registry/ui/css/admin-settings.css new file mode 100644 index 000000000..52df054af --- /dev/null +++ b/java/google/registry/ui/css/admin-settings.css @@ -0,0 +1,58 @@ +/** Admin Settings */ + +div#tlds div.tld { + width: 209px; +} + +#newTld { + width: 187px; + margin-left: 0.5em; +} + +div#tlds div.tld input, +div#tlds div.tld button[type=button] { + height: 27px; + line-height: 27px; + background: #ebebeb; + vertical-align: top; + border: none; + border-bottom: solid 3px white; +} + +div#tlds div.tld input { + width: 169px; + margin: 0; + padding: 0; + color: #555; + padding-left: 5px ! important; +} + +div#tlds.editing div.tld input[readonly] { + margin-left: 0.5em; +} + +div#tlds.editing div.tld button[type=button] { + display: inline-block; + float: right; + margin-left: -2px; + width: 30px; + min-width: 30px; + height: 30px; + color: grey; + font-size: 1.1em; +} + +div#tlds.editing div.tld button[type=button]:hover { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +div#tlds.editing div.tld button[type=button] i { + font-style: normal; +} + +div#tlds.editing .kd-errormessage { + margin-left: 0.5em; +} + diff --git a/java/google/registry/ui/externs/json.js b/java/google/registry/ui/externs/json.js index b5624949a..7f99b66a8 100644 --- a/java/google/registry/ui/externs/json.js +++ b/java/google/registry/ui/externs/json.js @@ -68,6 +68,7 @@ registry.json.Response.prototype.results; // XXX: Might not need undefineds here. /** * @typedef {{ + * allowedTlds: !Array, * clientIdentifier: string, * clientCertificate: string?, * clientCertificateHash: string?, diff --git a/java/google/registry/ui/js/registrar/admin_settings.js b/java/google/registry/ui/js/registrar/admin_settings.js new file mode 100644 index 000000000..2fa6f696c --- /dev/null +++ b/java/google/registry/ui/js/registrar/admin_settings.js @@ -0,0 +1,106 @@ +// Copyright 2017 The Nomulus Authors. All Rights Reserved. +// +// 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. + +goog.provide('registry.registrar.AdminSettings'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.soy'); +goog.require('registry.Resource'); +goog.require('registry.ResourceComponent'); +goog.require('registry.soy.registrar.admin'); + +goog.forwardDeclare('registry.registrar.Console'); + + + +/** + * Admin Settings page, such as allowed TLDs for this registrar. + * @param {!registry.registrar.Console} console + * @param {!registry.Resource} resource the RESTful resource for the registrar. + * @constructor + * @extends {registry.ResourceComponent} + * @final + */ +registry.registrar.AdminSettings = function(console, resource) { + registry.registrar.AdminSettings.base( + this, 'constructor', console, resource, + registry.soy.registrar.admin.settings, null); +}; +goog.inherits(registry.registrar.AdminSettings, registry.ResourceComponent); + + +/** @override */ +registry.registrar.AdminSettings.prototype.bindToDom = function(id) { + registry.registrar.AdminSettings.base(this, 'bindToDom', 'fake'); + goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back')); +}; + + +/** @override */ +registry.registrar.AdminSettings.prototype.setupEditor = + function(objArgs) { + goog.dom.classlist.add(goog.dom.getRequiredElement('tlds'), + goog.getCssName('editing')); + var tlds = goog.dom.getElementsByClass(goog.getCssName('tld'), + goog.dom.getRequiredElement('tlds')); + goog.array.forEach(tlds, function(tld) { + var remBtn = goog.dom.getChildren(tld)[0]; + goog.events.listen(remBtn, + goog.events.EventType.CLICK, + goog.bind(this.onTldRemove_, this, remBtn)); + }, this); + this.typeCounts['reg-tlds'] = objArgs.allowedTlds ? + objArgs.allowedTlds.length : 0; + + goog.events.listen(goog.dom.getRequiredElement('btn-add-tld'), + goog.events.EventType.CLICK, + this.onTldAdd_, + false, + this); +}; + + +/** + * Click handler for TLD add button. + * @private + */ +registry.registrar.AdminSettings.prototype.onTldAdd_ = function() { + const tldInputElt = goog.dom.getRequiredElement('newTld'); + const tldElt = goog.soy.renderAsFragment(registry.soy.registrar.admin.tld, { + name: 'allowedTlds[' + this.typeCounts['reg-tlds'] + ']', + tld: tldInputElt.value, + }); + goog.dom.appendChild(goog.dom.getRequiredElement('tlds'), tldElt); + var remBtn = goog.dom.getFirstElementChild(tldElt); + goog.dom.classlist.remove(remBtn, goog.getCssName('hidden')); + goog.events.listen(remBtn, goog.events.EventType.CLICK, + goog.bind(this.onTldRemove_, this, remBtn)); + this.typeCounts['reg-tlds']++; + tldInputElt.value = ''; +}; + + +/** + * Click handler for TLD remove button. + * @param {!Element} remBtn The remove button. + * @private + */ +registry.registrar.AdminSettings.prototype.onTldRemove_ = + function(remBtn) { + goog.dom.removeNode(goog.dom.getParentElement(remBtn)); +}; diff --git a/java/google/registry/ui/js/registrar/console.js b/java/google/registry/ui/js/registrar/console.js index 9c8d639da..6542c03ea 100644 --- a/java/google/registry/ui/js/registrar/console.js +++ b/java/google/registry/ui/js/registrar/console.js @@ -21,6 +21,7 @@ goog.require('goog.dom.classlist'); goog.require('goog.net.XhrIo'); goog.require('registry.Console'); goog.require('registry.Resource'); +goog.require('registry.registrar.AdminSettings'); goog.require('registry.registrar.Contact'); goog.require('registry.registrar.ContactSettings'); goog.require('registry.registrar.ContactUs'); @@ -76,20 +77,43 @@ registry.registrar.Console = function(params) { this.lastActiveNavElt; /** + * A map from the URL fragment to the component to show. + * * @type {!Object.} */ this.pageMap = {}; + // Homepage. Displayed when there's no fragment, or when the fragment doesn't + // correspond to any view + this.pageMap[''] = registry.registrar.Dashboard; + // Updating the Registrar settings this.pageMap['security-settings'] = registry.registrar.SecuritySettings; this.pageMap['contact-settings'] = registry.registrar.ContactSettings; this.pageMap['whois-settings'] = registry.registrar.WhoisSettings; this.pageMap['contact-us'] = registry.registrar.ContactUs; this.pageMap['resources'] = registry.registrar.Resources; + // For admin use. The relevant tab is only shown in Console.soy for admins, + // but we also need to remove it here, otherwise it'd still be accessible if + // the user manually puts '#admin-settings' in the URL. + // + // Both the Console.soy and here, the "hiding the admin console for non + // admins" is purely for "aesthetic / design" reasons and have NO security + // implications. + // + // The security implications are only in the backend where we make sure all + // changes are made by users with the correct access (in other words - we + // don't trust the client-side to secure our application anyway) + if (this.params.isAdmin) { + this.pageMap['admin-settings'] = registry.registrar.AdminSettings; + } + + // sending EPPs through the console. Currently hidden (doesn't have a "tab") + // but still accessible if the user manually puts #domain (or other) in the + // fragment this.pageMap['contact'] = registry.registrar.Contact; this.pageMap['domain'] = registry.registrar.Domain; this.pageMap['host'] = registry.registrar.Host; - this.pageMap[''] = registry.registrar.Dashboard; }; goog.inherits(registry.registrar.Console, registry.Console); diff --git a/java/google/registry/ui/js/registrar/main.js b/java/google/registry/ui/js/registrar/main.js index dfc5817e9..dc44a4d1b 100644 --- a/java/google/registry/ui/js/registrar/main.js +++ b/java/google/registry/ui/js/registrar/main.js @@ -28,6 +28,7 @@ goog.require('registry.registrar.Console'); * * @param {string} xsrfToken populated by server-side soy template. * @param {string} clientId The registrar clientId. + * @param {boolean} isAdmin * @param {string} productName the product name displayed by the UI. * @param {string} integrationEmail * @param {string} supportEmail @@ -36,13 +37,14 @@ goog.require('registry.registrar.Console'); * @param {string} technicalDocsUrl * @export */ -registry.registrar.main = function(xsrfToken, clientId, productName, +registry.registrar.main = function(xsrfToken, clientId, isAdmin, productName, integrationEmail, supportEmail, announcementsEmail, supportPhoneNumber, technicalDocsUrl) { new registry.registrar.Console({ xsrfToken: xsrfToken, clientId: clientId, + isAdmin: isAdmin, productName: productName, integrationEmail: integrationEmail, supportEmail: supportEmail, diff --git a/java/google/registry/ui/js/registrar/security_settings.js b/java/google/registry/ui/js/registrar/security_settings.js index 1a3f2ed34..31a2c95ba 100644 --- a/java/google/registry/ui/js/registrar/security_settings.js +++ b/java/google/registry/ui/js/registrar/security_settings.js @@ -103,5 +103,4 @@ registry.registrar.SecuritySettings.prototype.onIpAdd_ = function() { registry.registrar.SecuritySettings.prototype.onIpRemove_ = function(remBtn) { goog.dom.removeNode(goog.dom.getParentElement(remBtn)); - this.typeCounts['reg-ips']--; }; diff --git a/java/google/registry/ui/server/registrar/ConsoleUiAction.java b/java/google/registry/ui/server/registrar/ConsoleUiAction.java index be45d805a..34add2820 100644 --- a/java/google/registry/ui/server/registrar/ConsoleUiAction.java +++ b/java/google/registry/ui/server/registrar/ConsoleUiAction.java @@ -16,6 +16,7 @@ package google.registry.ui.server.registrar; import static com.google.common.net.HttpHeaders.LOCATION; import static com.google.common.net.HttpHeaders.X_FRAME_OPTIONS; +import static google.registry.ui.server.registrar.AuthenticatedRegistrarAccessor.Role.ADMIN; import static google.registry.ui.server.registrar.RegistrarConsoleModule.PARAM_CLIENT_ID; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; @@ -139,6 +140,7 @@ public final class ConsoleUiAction implements Runnable { try { clientId = paramClientId.orElse(registrarAccessor.guessClientId()); data.put("clientId", clientId); + data.put("isAdmin", roleMap.containsEntry(clientId, ADMIN)); // We want to load the registrar even if we won't use it later (even if we remove the // requireFeeExtension) - to make sure the user indeed has access to the guessed registrar. diff --git a/java/google/registry/ui/soy/registrar/AdminSettings.soy b/java/google/registry/ui/soy/registrar/AdminSettings.soy new file mode 100644 index 000000000..5bc82d9a0 --- /dev/null +++ b/java/google/registry/ui/soy/registrar/AdminSettings.soy @@ -0,0 +1,69 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// 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. + +{namespace registry.soy.registrar.admin} + + + +/** Registrar admin settings page for view and edit. */ +{template .settings} + {@param clientId: string} + {@param allowedTlds: list} + {@param readonly: bool} +
+

Administrator settings for {$clientId}

+ {if $readonly} +

Use the 'Edit' button above to switch to enable editing the information below. + {/if} + + + + + +
+ + set or remove TLDs this + client is allowed access to. + +
+
+ {for $tld in $allowedTlds} + {call .tld} + {param name: 'allowedTlds[' + index($tld) + ']' /} + {param tld: $tld /} + {/call} + {/for} +
+
+ + +
+
+
+

+{/template} + + +/** TLD form input. */ +{template .tld} + {@param name: string} + {@param tld: string} +
+ + +
+{/template} diff --git a/java/google/registry/ui/soy/registrar/Console.soy b/java/google/registry/ui/soy/registrar/Console.soy index 65ef43d21..0eea97c15 100644 --- a/java/google/registry/ui/soy/registrar/Console.soy +++ b/java/google/registry/ui/soy/registrar/Console.soy @@ -24,6 +24,7 @@ {@param xsrfToken: string} /** Security token. */ {@param clientId: string} /** Registrar client identifier. */ {@param allClientIds: list} /** All registrar client identifiers for the user. */ + {@param isAdmin: bool} {@param username: string} /** Arbitrary username to display. */ {@param logoutUrl: string} /** Generated URL for logging out of Google. */ {@param productName: string} /** Name to display for this software product. */ @@ -63,6 +64,7 @@