From 5ee976ca7bd7d4de0e106954a616649428277921 Mon Sep 17 00:00:00 2001 From: Jaime Perez Crespo <jaime.perez@uninett.no> Date: Mon, 2 Nov 2015 12:25:00 +0100 Subject: [PATCH] Add the possibility to copy metadata to clipboard with a single click. This resolves #75. --- modules/adfs/www/idp/metadata.php | 1 + modules/saml/www/sp/metadata.php | 1 + templates/includes/header.php | 5 ++ templates/metadata.php | 102 ++++++++++++++++-------------- www/resources/clipboard.min.js | 7 ++ www/resources/default.css | 66 ++++++++++++++++++- www/resources/icons/clipboard.svg | 3 + www/saml2/idp/metadata.php | 1 + www/shib13/idp/metadata.php | 4 +- 9 files changed, 141 insertions(+), 49 deletions(-) create mode 100644 www/resources/clipboard.min.js create mode 100644 www/resources/icons/clipboard.svg diff --git a/modules/adfs/www/idp/metadata.php b/modules/adfs/www/idp/metadata.php index a77a0a027..8bcd58a16 100644 --- a/modules/adfs/www/idp/metadata.php +++ b/modules/adfs/www/idp/metadata.php @@ -132,6 +132,7 @@ try { $t = new SimpleSAML_XHTML_Template($config, 'metadata.php', 'admin'); + $t->data['clipboard.js'] = true; $t->data['available_certs'] = $availableCerts; $t->data['header'] = 'adfs-idp'; $t->data['metaurl'] = \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(); diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php index 173c71569..4256ddcf5 100644 --- a/modules/saml/www/sp/metadata.php +++ b/modules/saml/www/sp/metadata.php @@ -240,6 +240,7 @@ if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') { $t = new SimpleSAML_XHTML_Template($config, 'metadata.php', 'admin'); + $t->data['clipboard.js'] = true; $t->data['header'] = 'saml20-sp'; $t->data['metadata'] = htmlspecialchars($xml); $t->data['metadataflat'] = '$metadata[' . var_export($entityId, TRUE) . '] = ' . var_export($metaArray20, TRUE) . ';'; diff --git a/templates/includes/header.php b/templates/includes/header.php index afa2e21c4..02ca151bf 100644 --- a/templates/includes/header.php +++ b/templates/includes/header.php @@ -76,6 +76,11 @@ if(!empty($jquery)) { } } +if (isset($this->data['clipboard.js'])) { + echo '<script type="text/javascript" src="/'. $this->data['baseurlpath'] . + 'resources/clipboard.min.js"></script>'."\n"; +} + if(!empty($this->data['htmlinject']['htmlContentHead'])) { foreach($this->data['htmlinject']['htmlContentHead'] AS $c) { echo $c; diff --git a/templates/metadata.php b/templates/metadata.php index e4f9471cb..6dcd276ce 100644 --- a/templates/metadata.php +++ b/templates/metadata.php @@ -1,51 +1,61 @@ <?php -$this->data['header'] = $this->t('metadata_' . $this->data['header']); -$this->includeAtTemplateBase('includes/header.php'); -?> +$this->data['header'] = $this->t('metadata_'.$this->data['header']); +$this->includeAtTemplateBase('includes/header.php'); ?> + <h2><?php echo $this->data['header']; ?></h2> + <p><?php echo $this->t('metadata_intro'); ?></p> +<?php if (isset($this->data['metaurl'])) { ?> + <p><?php echo($this->t('metadata_xmlurl', array('%METAURL%' => htmlspecialchars($this->data['metaurl'])))); ?></p> + <div class="input-group"> + <pre id="metadataurl" class="input-left"><?php echo htmlspecialchars($this->data['metaurl']); ?></pre> + <button data-clipboard-target="#metadataurl" id="btnurl" class="btnaddonright"> + <img src="<?php echo $this->data['baseurlpath'].'/resources/icons/clipboard.svg'; ?>" + alt="Copy to clipboard"> + </button> + </div> +<?php } ?> + <h2><?php echo($this->t('metadata_metadata')); ?></h2> + <p><?php echo($this->t('metadata_xmlformat')); ?></p> + <div class="metadatabox"> + <button data-clipboard-target="#xmlmetadata" id="btnxml" class="btn topright" style="margin-right: 0.5em;"> + <img src="<?php echo $this->data['baseurlpath'].'/resources/icons/clipboard.svg'; ?>" + alt="Copy to clipboard"> + </button> + <pre id="xmlmetadata"><?php echo $this->data['metadata']; ?></pre> + </div> + <p><?php echo($this->t('metadata_simplesamlformat')); ?></p> + <div class="metadatabox"> + <button data-clipboard-target="#phpmetadata" id="btnphp" class="btn topright" style="margin-right: 0.5em;"> + <img src="<?php echo $this->data['baseurlpath'].'/resources/icons/clipboard.svg'; ?>" + alt="Copy to clipboard"> + </button> + <pre id="phpmetadata"><?php echo $this->data['metadataflat']; ?></pre> + </div> + <script type="text/javascript"> + var clipboard1 = new Clipboard('#btnurl'), + clipboard2 = new Clipboard('#btnxml'), + clipboard3 = new Clipboard('#btnphp'); + </script> +<?php +if (array_key_exists('available_certs', $this->data)) { ?> + <h2><?php echo($this->t('metadata_cert')); ?></h2> + <p><?php echo($this->t('metadata_cert_intro')); ?></p> + <ul> +<?php + foreach (array_keys($this->data['available_certs']) as $certName) { + echo '<li><a href="'. + htmlspecialchars(SimpleSAML_Module::getModuleURL('saml/idp/certs.php').'/'.$certName).'">'.$certName. + '</a>'; - <h2><?php echo $this->data['header']; ?></h2> - - <p><?php echo $this->t('metadata_intro'); ?></p> - - <?php if (isset($this->data['metaurl'])) { ?> - <p><?php echo($this->t('metadata_xmlurl', array('%METAURL%' => htmlspecialchars($this->data['metaurl'])))); ?><br /> - <input type="text" style="width: 90%" value="<?php echo htmlspecialchars($this->data['metaurl']); ?>" /></p> - <?php } ?> - <h2><?php echo($this->t('metadata_metadata')); ?></h2> - - <p><?php echo($this->t('metadata_xmlformat')); ?></p> - - <pre class="metadatabox"><?php echo $this->data['metadata']; ?> -</pre> - - - <p><?php echo($this->t('metadata_simplesamlformat')); ?></p> - - <pre class="metadatabox"><?php echo $this->data['metadataflat']; ?> -</pre> - - + if ($this->data['available_certs'][$certName]['certFingerprint'][0] === + 'afe71c28ef740bc87425be13a2263d37971da1f9') { + echo ' <img style="display: inline;" src="/'.$this->data['baseurlpath']. + 'resources/icons/silk/exclamation.png" alt="default certificate" />'. + 'This is the default certificate. Generate a new certificate if this is a production system.'; + } + echo '</li>'; + } ?> + </ul> <?php -if(array_key_exists('available_certs', $this->data)) { ?> - <h2><?php echo($this->t('metadata_cert')); ?></h2> - <p><?php echo($this->t('metadata_cert_intro')); ?></p> - <ul> - <?php - foreach(array_keys($this->data['available_certs']) as $certName) { - echo ('<li><a href="'. - htmlspecialchars(SimpleSAML_Module::getModuleURL('saml/idp/certs.php').'/'.$certName).'">'.$certName.'</a>'); - if($this->data['available_certs'][$certName]['certFingerprint'][0] == 'afe71c28ef740bc87425be13a2263d37971da1f9') { - echo (' <img style="display: inline;" src="/' . $this->data['baseurlpath'] . - 'resources/icons/silk/exclamation.png" alt="default certificate" /> - This is the default certificate. Generate a new certificate if this is a production system.'); - } - echo '</li>'; - } - echo '</ul>'; } -?> - - - -<?php $this->includeAtTemplateBase('includes/footer.php'); +$this->includeAtTemplateBase('includes/footer.php'); diff --git a/www/resources/clipboard.min.js b/www/resources/clipboard.min.js new file mode 100644 index 000000000..69731a4d2 --- /dev/null +++ b/www/resources/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v1.5.1 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a<r.length;a++)o(r[a]);return o}({1:[function(t,e,n){var r=t("matches-selector");e.exports=function(t,e,n){for(var o=n?t:t.parentNode;o&&o!==document;){if(r(o,e))return o;o=o.parentNode}}},{"matches-selector":2}],2:[function(t,e,n){function r(t,e){if(i)return i.call(t,e);for(var n=t.parentNode.querySelectorAll(e),r=0;r<n.length;++r)if(n[r]==t)return!0;return!1}var o=Element.prototype,i=o.matchesSelector||o.webkitMatchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector;e.exports=r},{}],3:[function(t,e,n){function r(t,e,n,r){var i=o.apply(this,arguments);return t.addEventListener(n,i),{destroy:function(){t.removeEventListener(n,i)}}}function o(t,e,n,r){return function(n){var o=i(n.target,e,!0);o&&(Object.defineProperty(n,"target",{value:o}),r.call(t,n))}}var i=t("closest");e.exports=r},{closest:1}],4:[function(t,e,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.function=function(t){var e=Object.prototype.toString.call(t);return"[object Function]"===e}},{}],5:[function(t,e,n){function r(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.function(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return o(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function o(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return s(document.body,t,e,n)}var c=t("./is"),s=t("delegate");e.exports=r},{"./is":4,delegate:3}],6:[function(t,e,n){function r(t){var e;if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName)t.select(),e=t.value;else{var n=window.getSelection(),r=document.createRange();r.selectNodeContents(t),n.removeAllRanges(),n.addRange(r),e=n.toString()}return e}e.exports=r},{}],7:[function(t,e,n){function r(){}r.prototype={on:function(t,e,n){var r=this.e||(this.e={});return(r[t]||(r[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function r(){o.off(t,r),e.apply(n,arguments)}var o=this;return r._=e,this.on(t,r,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),r=0,o=n.length;for(r;o>r;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),a=t("select"),c=r(a),s=function(){function t(e){o(this,t),this.resolveOptions(e),this.initSelection()}return t.prototype.resolveOptions=function t(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action=e.action,this.emitter=e.emitter,this.target=e.target,this.text=e.text,this.trigger=e.trigger,this.selectedText=""},t.prototype.initSelection=function t(){if(this.text&&this.target)throw new Error('Multiple attributes declared, use either "target" or "text"');if(this.text)this.selectFake();else{if(!this.target)throw new Error('Missing required attributes, use either "target" or "text"');this.selectTarget()}},t.prototype.selectFake=function t(){var e=this;this.removeFake(),this.fakeHandler=document.body.addEventListener("click",function(){return e.removeFake()}),this.fakeElem=document.createElement("textarea"),this.fakeElem.style.position="absolute",this.fakeElem.style.left="-9999px",this.fakeElem.style.top=(window.pageYOffset||document.documentElement.scrollTop)+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=c.default(this.fakeElem),this.copyText()},t.prototype.removeFake=function t(){this.fakeHandler&&(document.body.removeEventListener("click"),this.fakeHandler=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)},t.prototype.selectTarget=function t(){this.selectedText=c.default(this.target),this.copyText()},t.prototype.copyText=function t(){var e=void 0;try{e=document.execCommand(this.action)}catch(n){e=!1}this.handleResult(e)},t.prototype.handleResult=function t(e){e?this.emitter.emit("success",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)}):this.emitter.emit("error",{action:this.action,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})},t.prototype.clearSelection=function t(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()},t.prototype.destroy=function t(){this.removeFake()},i(t,[{key:"action",set:function t(){var e=arguments.length<=0||void 0===arguments[0]?"copy":arguments[0];if(this._action=e,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function t(){return this._action}},{key:"target",set:function t(e){if(void 0!==e){if(!e||"object"!=typeof e||1!==e.nodeType)throw new Error('Invalid "target" value, use a valid Element');this._target=e}},get:function t(){return this._target}}]),t}();n.default=s,e.exports=n.default},{select:6}],9:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}n.__esModule=!0;var c=t("./clipboard-action"),s=r(c),u=t("tiny-emitter"),l=r(u),f=t("good-listener"),d=r(f),h=function(t){function e(n,r){o(this,e),t.call(this),this.resolveOptions(r),this.listenClick(n)}return i(e,t),e.prototype.resolveOptions=function t(){var e=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText},e.prototype.listenClick=function t(e){var n=this;this.listener=d.default(e,"click",function(t){return n.onClick(t)})},e.prototype.onClick=function t(e){this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s.default({action:this.action(e.target),target:this.target(e.target),text:this.text(e.target),trigger:e.target,emitter:this})},e.prototype.defaultAction=function t(e){return a("action",e)},e.prototype.defaultTarget=function t(e){var n=a("target",e);return n?document.querySelector(n):void 0},e.prototype.defaultText=function t(e){return a("text",e)},e.prototype.destroy=function t(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)},e}(l.default);n.default=h,e.exports=n.default},{"./clipboard-action":8,"good-listener":5,"tiny-emitter":7}]},{},[9])(9)}); \ No newline at end of file diff --git a/www/resources/default.css b/www/resources/default.css index dbce4317c..b9e984c8c 100644 --- a/www/resources/default.css +++ b/www/resources/default.css @@ -274,7 +274,8 @@ th.rowtitle { .metadatabox { overflow: scroll; border: 1px solid #eee; - padding: 2px; + padding: 0.5em; + border-radius: 3px; } div.preferredidp { border: 1px dashed #ccc; @@ -388,3 +389,66 @@ caption { font-size: 50%; } } + +.btn, .btnaddonright { + border: 1px solid #eee; + border-radius: 3px; + background-color: #eee; + background-image: linear-gradient(#fcfcfc, #eee); + text-align: center; +} + +.btn:hover, .btnaddonright:hover { + border-color: #ccc; + background-color: #ddd; + background-image: linear-gradient(#eee, #ddd); +} + +.btn img, +.btnaddonright img { + max-height: 15px; + max-width: 15px; + margin: 5px; +} + +.topright { + position: absolute; + right: 2em; +} + +.input-group { + display: table; +} + +.input-group pre { + position: relative; + width: 100%; + vertical-align: middle; + border: 1px solid #eee; + padding: 0.5em; +} + +.input-group .btnaddonright { + position: relative; + display: inline-block; + border-bottom-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-left-radius: 0; + border-top-right-radius: 3px; + border-left: none; +} + +.input-group .btnaddonright:hover { + border-left: 1px solid #ccc; +} + +.input-group pre { + display: table-cell; +} + +.input-group .input-left { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 0; + border-top-left-radius: 3px; + border-top-right-radius: 0; +} diff --git a/www/resources/icons/clipboard.svg b/www/resources/icons/clipboard.svg new file mode 100644 index 000000000..d827e82e5 --- /dev/null +++ b/www/resources/icons/clipboard.svg @@ -0,0 +1,3 @@ +<svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg"> + <path d="M128 768h256v64H128v-64z m320-384H128v64h320v-64z m128 192V448L384 640l192 192V704h320V576H576z m-288-64H128v64h160v-64zM128 704h160v-64H128v64z m576 64h64v128c-1 18-7 33-19 45s-27 18-45 19H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h192C256 57 313 0 384 0s128 57 128 128h192c35 0 64 29 64 64v320h-64V320H64v576h640V768zM128 256h512c0-35-29-64-64-64h-64c-35 0-64-29-64-64s-29-64-64-64-64 29-64 64-29 64-64 64h-64c-35 0-64 29-64 64z"/> +</svg> \ No newline at end of file diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php index 7784a117e..fcad630bd 100644 --- a/www/saml2/idp/metadata.php +++ b/www/saml2/idp/metadata.php @@ -203,6 +203,7 @@ try { $t = new SimpleSAML_XHTML_Template($config, 'metadata.php', 'admin'); + $t->data['clipboard.js'] = true; $t->data['available_certs'] = $availableCerts; $t->data['header'] = 'saml20-idp'; $t->data['metaurl'] = \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(); diff --git a/www/shib13/idp/metadata.php b/www/shib13/idp/metadata.php index f47c59148..0c3dfa999 100644 --- a/www/shib13/idp/metadata.php +++ b/www/shib13/idp/metadata.php @@ -84,9 +84,9 @@ try { $defaultidp = $config->getString('default-shib13-idp', NULL); $t = new SimpleSAML_XHTML_Template($config, 'metadata.php', 'admin'); - + + $t->data['clipboard.js'] = true; $t->data['header'] = 'shib13-idp'; - $t->data['metaurl'] = \SimpleSAML\Utils\HTTP::addURLParameters(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery(), array('output' => 'xml')); $t->data['metadata'] = htmlspecialchars($metaxml); $t->data['metadataflat'] = htmlspecialchars($metaflat); -- GitLab