diff --git a/docker/shibboleth-ds/LICENSE.txt b/docker/shibboleth-ds/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..dd5b3a58aa1849f452abc9b5cd1638dc71a5e482 --- /dev/null +++ b/docker/shibboleth-ds/LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/docker/shibboleth-ds/Makefile b/docker/shibboleth-ds/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..24c88c808e454ce8374be8a20ad878235ac0075d --- /dev/null +++ b/docker/shibboleth-ds/Makefile @@ -0,0 +1,31 @@ +# +# Note: Changes made here should be reflected in the windows .bat file at src/build.bat +# +INSTALL=/usr/bin/install +JAVA=java +TAR=tar +ZIP=zip +TARGET=shibboleth-embedded-ds-1.2.0 +prefix= +sysconfdir=${prefix}/etc + +all: + +install: index.html + ${INSTALL} -d $(DESTDIR)${sysconfdir}/shibboleth-ds + ${INSTALL} -m 644 *.txt *.html *.css *.gif *.js *.conf $(DESTDIR)${sysconfdir}/shibboleth-ds + +clean: + rm -rf ${TARGET} + +kit: clean + mkdir ${TARGET} + mkdir ${TARGET}/nonminimised + cat src/javascript/idpselect_languages.js src/javascript/typeahead.js src/javascript/idpselect.js | ${JAVA} -jar build/yuicompressor-2.4.8.jar -o ${TARGET}/idpselect.js --type js + cp Makefile shibboleth-embedded-ds.spec LICENSE.txt doc/*.txt src/resources/index.html src/resources/idpselect.css src/resources/blank.gif src/javascript/idpselect_config.js src/apache/*.conf ${TARGET} + cp src/javascript/*.js ${TARGET}/nonminimised + +dist: kit + ${TAR} czf ${TARGET}.tar.gz ${TARGET}/* + ${ZIP} ${TARGET}.zip ${TARGET}/* + rm -rf ${TARGET} diff --git a/docker/shibboleth-ds/README.txt b/docker/shibboleth-ds/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..d765a1630af11f0b04b601e73292454b03f9c646 --- /dev/null +++ b/docker/shibboleth-ds/README.txt @@ -0,0 +1,52 @@ +Welcome to the Shibboleth Embedded Discovery Service. + +Shibboleth is a federated web authentication and attribute exchange +system based on SAML. The Embedded Discovery Service allows anyone +running a suitable SP to quickly and easily deploy IdP discovery. + +For full instructions on installation and deployment please read: + +https://wiki.shibboleth.net/confluence/display/EDS10 + +INSTALLED FILES. +================ + +The following files will be installed, please consult the documentation +for more details. + +index.html - An example file showing how to embed the EDS into a + standard webpage. + +idpselect.js - The minified sources for the EDS. DO NOT EDIT THIS FILE + since it will be updated by future releases. + +idpselect_config.js - Configuration. Edit this file according to the + documentation (cited above). + +idpselect.css - CSS for the EDS. You may wish to edit this file. + +README.TXT - This file + +RELEASE-NOTES.TXT - The list of bugs fixed in this and previous releases. + +FURTHER DETAILS. +================ + +Shibboleth is licensed under the Apache 2.0 license which is provided in the +LICENSE.txt file. + +Shibboleth Project Site: +http://shibboleth.internet2.edu/ + +Shibboleth Documentation Site: +https://wiki.shibboleth.net/confluence/display/SHIB2/Home + +Source and binary distributions +http://www.shibboleth.net/downloads/ + +Bug Tracker: +https://issues.shibboleth.net/ + +Other sources: +This tools embeds the JSON parsing tool from https://github.com/douglascrockford/JSON-js +The build process uses the yui compressor (http://developer.yahoo.com/yui/compressor/) diff --git a/docker/shibboleth-ds/RELEASE-NOTES.txt b/docker/shibboleth-ds/RELEASE-NOTES.txt new file mode 100644 index 0000000000000000000000000000000000000000..9eca8df766f98bdee0daacce7aaefb630380a137 --- /dev/null +++ b/docker/shibboleth-ds/RELEASE-NOTES.txt @@ -0,0 +1 @@ +See https://issues.shibboleth.net/jira/projects/EDS diff --git a/docker/shibboleth-ds/blank.gif b/docker/shibboleth-ds/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..2799b45c6591f1db05c8c00bd1fd0c5c01f57614 Binary files /dev/null and b/docker/shibboleth-ds/blank.gif differ diff --git a/docker/shibboleth-ds/idpselect.css b/docker/shibboleth-ds/idpselect.css new file mode 100644 index 0000000000000000000000000000000000000000..a0accceb9c9902c6b7a58464453673152bb77402 --- /dev/null +++ b/docker/shibboleth-ds/idpselect.css @@ -0,0 +1,213 @@ +/* Top level is idpSelectIdPSelector */ +#idpSelectIdPSelector +{ + width: 512px; + text-align: left; + background-color: #FFFFFF; + border: 2px #A40000 solid; + padding: 10px; +} + +/* Next down are the idpSelectPreferredIdPTile, idpSelectIdPEntryTile & idpSelectIdPListTile */ + +/** + * The preferred IdP tile (if present) has a specified height, so + * we can fit the preselected * IdPs in there + */ +#idpSelectPreferredIdPTile +{ + height:138px; /* Force the height so that the selector box + * goes below when there is only one preslect + */ +} +#idpSelectPreferredIdPTileNoImg +{ + height:60px; +} + +/*** + * The preselect buttons + */ +div.IdPSelectPreferredIdPButton +{ + margin: 3px; + width: 120px; /* Make absolute because 3 of these must fit inside + div.IdPSelect{width} with not much each side. */ + float: left; +} + +/* + * Make the entire box look like a hyperlink + */ +div.IdPSelectPreferredIdPButton a +{ + float: left; + width: 99%; /* Need a specified width otherwise we'll fit + the contents which we don't want because + they have auto margins */ + +} +div.IdPSelectTextDiv{ + height: 3.5ex; /* Add some height to separate the text from the boxes */ + font-size: 15px; + clear: left; +} + +div.IdPSelectPreferredIdPImg +{ +/* max-width: 95%; */ + height: 69px; /* We need the absolute height to force all buttons to the same size */ + margin: 2px; +} + +img.IdPSelectIdPImg { + width:auto; +} + +div.IdPSelectautoDispatchTile { + display: block; +} + +div.IdPSelectautoDispatchArea { + margin-top: 30px ; +} + +div.IdPSelectPreferredIdPButton img +{ + display: block; /* Block display to allow auto centring */ + max-width: 114px; /* Specify max to allow scaling, percent does work */ + max-height: 64px; /* Specify max to allow scaling, percent doesn't work */ + margin-top: 3px ; + margin-bottom: 3px ; + border: solid 0px #000000; /* Strip any embellishments the brower may give us */ + margin-left: auto; /* Auto centring */ + margin-right: auto; /* Auto centring */ + +} + +div.IdPSelectPreferredIdPButton div.IdPSelectTextDiv +{ + text-align: center; + font-size: 12px; + font-weight: normal; + max-width: 95%; + height: 30px; /* Specify max height to allow two lines. The + * Javascript controlls the max length of the + * strings + */ +} + +/* + * Force the size of the selectors and the buttons + */ +#idpSelectInput, #idpSelectSelector +{ + width: 80%; +} +/* + * For some reason a <select> width includes the border and an + * <input> doesn't hence we have to force a margin onto the <select> + */ +#idpSelectSelector +{ + margin-left: 2px; + margin-right: 2px; + +} +#idpSelectSelectButton, #idpSelectListButton +{ + margin-left: 5px; + width: 16%; +} +#idpSelectSelectButton +{ + padding-left: 2px; + padding-right: 2px; +} + +/* + * change underlining of HREFS + */ +#idpSelectIdPSelector a:link +{ + text-decoration: none; +} + +#idpSelectIdPSelector a:visited +{ + text-decoration: none; +} + +#idpSelectIdPSelector a:hover +{ + text-decoration: underline; +} + + + +/* + * Arrange to have the dropdown/list aref on the left and the + * help button on the right + */ + +a.IdPSelectDropDownToggle +{ + display: inline-block; + width: 80%; +} + +a.IdPSelectHelpButton +{ + display: inline-block; + text-align: right; + width: 20%; +} + +/** + * Drop down (incremental search) stuff - see the associated javascript for reference + */ +ul.IdPSelectDropDown { + -moz-box-sizing: border-box; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: small; + box-sizing: border-box; + list-style: none; + padding-left: 0px; + border: 1px solid black; + z-index: 6; + position: absolute; +} + +ul.IdPSelectDropDown li { + background-color: white; + cursor: default; + padding: 0px 3px; +} + +ul.IdPSelectDropDown li.IdPSelectCurrent { + background-color: #3366cc; + color: white; +} + +/* Legacy */ +div.IdPSelectDropDown { + -moz-box-sizing: border-box; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: small; + box-sizing: border-box; + border: 1px solid black; + z-index: 6; + position: absolute; +} + +div.IdPSelectDropDown div { + background-color: white; + cursor: default; + padding: 0px 3px; +} + + div.IdPSelectDropDown div.IdPSelectCurrent { + background-color: #3366cc; + color: white; +} +/* END */ diff --git a/docker/shibboleth-ds/idpselect.js b/docker/shibboleth-ds/idpselect.js new file mode 100644 index 0000000000000000000000000000000000000000..db075f9960b723ab995e9ebbbe9d27e9a9f127ce --- /dev/null +++ b/docker/shibboleth-ds/idpselect.js @@ -0,0 +1 @@ +function IdPSelectLanguages(){this.langBundles={en:{"fatal.divMissing":'<div> specified as "insertAtDiv" could not be located in the HTML',"fatal.noXMLHttpRequest":"Browser does not support XMLHttpRequest, unable to load IdP selection data","fatal.wrongProtocol":'Policy supplied to DS was not "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"',"fatal.wrongEntityId":"entityId supplied by SP did not match configuration","fatal.noData":"Metadata download returned no data","fatal.loadFailed":"Failed to download metadata from ","fatal.noparms":"No parameters to discovery session and no defaultReturn parameter configured","fatal.noReturnURL":"No URL return parameter provided","fatal.badProtocol":"Return request must start with https:// or http://","idpPreferred.label":"Use a suggested selection:","idpEntry.label":"Or enter your organization's name","idpEntry.NoPreferred.label":"Enter your organization's name","idpList.label":"Or select your organization from the list below","idpList.NoPreferred.label":"Select your organization from the list below","idpList.defaultOptionLabel":"Please select your organization...","idpList.showList":"Allow me to pick from a list","idpList.showSearch":"Allow me to specify the site","submitButton.label":"Continue",helpText:"Help",defaultLogoAlt:"DefaultLogo","autoFollow.message":"Always follows this selection","autoFollow.never":"Never","autoFollow.time0":"One day","autoFollow.time1":"3 months","autoFollow.time2":"9 months"},de:{"fatal.divMissing":"Das notwendige Div Element fehlt","fatal.noXMLHttpRequest":"Ihr Webbrowser unterst\u00fctzt keine XMLHttpRequests, IdP-Auswahl kann nicht geladen werden","fatal.wrongProtocol":'DS bekam eine andere Policy als "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"',"fatal.wrongEntityId":"Die entityId ist nicht korrekt","fatal.loadFailed":"Metadaten konnten nicht heruntergeladen werden: ","fatal.noparms":"Parameter f\u00fcr das Discovery Service oder 'defaultReturn' fehlen","fatal.noReturnURL":"URL return Parmeter fehlt","fatal.badProtocol":"return Request muss mit https:// oder http:// beginnen","idpPreferred.label":"Vorherige Auswahl:","idpEntry.label":"Oder geben Sie den Namen (oder Teile davon) an:","idpEntry.NoPreferred.label":"Namen (oder Teile davon) der Institution angeben:","idpList.label":"Oder w\u00e4hlen Sie Ihre Institution aus einer Liste:","idpList.NoPreferred.label":"Institution aus folgender Liste w\u00e4hlen:","idpList.defaultOptionLabel":"W\u00e4hlen Sie Ihre Institution aus...","idpList.showList":"Institution aus einer Liste w\u00e4hlen","idpList.showSearch":"Institution selbst angeben","submitButton.label":"OK",helpText:"Hilfe",defaultLogoAlt:"Standard logo"},ja:{"fatal.divMissing":'"insertAtDiv" ã® ID ã‚’æŒã¤ <div> ㌠HTML ä¸ã«å˜åœ¨ã—ã¾ã›ã‚“',"fatal.noXMLHttpRequest":"ブラウザ㌠XMLHttpRequest をサãƒãƒ¼ãƒˆã—ã¦ã„ãªã„ã®ã§ IdP æƒ…å ±ã‚’å–å¾—ã§ãã¾ã›ã‚“","fatal.wrongProtocol":'DSã¸æ¸¡ã•ã‚ŒãŸ Policy パラメータ㌠"urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single" ã§ã¯ã‚ã‚Šã¾ã›ã‚“',"fatal.wrongEntityId":"SP ã‹ã‚‰æ¸¡ã•ã‚ŒãŸ entityId ãŒè¨å®šå€¤ã¨ç•°ãªã‚Šã¾ã™","fatal.noData":"メタデータãŒç©ºã§ã™","fatal.loadFailed":"次㮠URL ã‹ã‚‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã‚’ダウンãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ: ","fatal.noparms":"DSã«ãƒ‘ラメータãŒæ¸¡ã•ã‚Œã¦ãŠã‚‰ãš defaultReturn ã‚‚è¨å®šã•ã‚Œã¦ã„ã¾ã›ã‚“","fatal.noReturnURL":"戻り URL ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã›ã‚“","fatal.badProtocol":"戻り URL 㯠https:// ã‹ http:// ã§å§‹ã¾ã‚‰ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“","idpPreferred.label":"é¸æŠžå€™è£œã® IdP:","idpEntry.label":"ã‚‚ã—ãã¯ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢åを入力ã—ã¦ãã ã•ã„","idpEntry.NoPreferred.label":"ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢åを入力ã—ã¦ãã ã•ã„","idpList.label":"ã‚‚ã—ãã¯ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢ã‚’é¸æŠžã—ã¦ãã ã•ã„","idpList.NoPreferred.label":"ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢ã‚’一覧ã‹ã‚‰é¸æŠžã—ã¦ãã ã•ã„","idpList.defaultOptionLabel":"所属機関をé¸æŠžã—ã¦ãã ã•ã„...","idpList.showList":"一覧ã‹ã‚‰é¸æŠžã™ã‚‹","idpList.showSearch":"æ©Ÿé–¢åを入力ã™ã‚‹","submitButton.label":"é¸æŠž","autoFollow.message":"次ã®æœŸé–“é¸æŠžã—ãŸæ©Ÿé–¢ã«è‡ªå‹•çš„ã«é·ç§»ã™ã‚‹:","autoFollow.never":"自動é·ç§»ã—ãªã„","autoFollow.time0":"1æ—¥","autoFollow.time1":"3ã‹æœˆ","autoFollow.time2":"9ã‹æœˆ",helpText:"Help",defaultLogoAlt:"DefaultLogo"},"pt-br":{"fatal.divMissing":'A tag <div> com "insertAtDiv" não foi encontrada no arquivo HTML',"fatal.noXMLHttpRequest":'Seu navegador não suporta "XMLHttpRequest", impossÃvel de carregador os dados do IdP selecionado',"fatal.wrongProtocol":'A polÃtica "Policy" fornecida para o DS não foi "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"',"fatal.wrongEntityId":"entityId oferecido pelo SP não confere com o da configuração","fatal.noData":"O arquivo de metadados não retornou nada;","fatal.loadFailed":"Falhou ao realizar download do metadado de ","fatal.noparms":'Sem parâmetros para sessão de descoberta e sem parâmetro "defaultReturn" configurado',"fatal.noReturnURL":"Não foi definida um endereço (URL) de retorno no parâmetro","fatal.badProtocol":"Retorno do endereço requisitado deve começar com https:// ou http://","idpPreferred.label":"Use estas Instituições sugeridas: ","idpEntry.label":"Ou informe o nome da sua Instituição","idpEntry.NoPreferred.label":"Informe o nome da sua Instituição","idpList.label":"Ou selecione sua Instituição através da lista abaixo","idpList.NoPreferred.label":"Selecione sua Instituição através da lista abaixo","idpList.defaultOptionLabel":"Por favor, selecione sua Instituição: ","idpList.showList":"Permitir que eu escolha um IdP através de uma lista","idpList.showSearch":"Permitir que eu especifique o IdP","submitButton.label":"Continuar ",helpText:"Ajuda",defaultLogoAlt:"Logo padrão"}}}function TypeAheadControl(l,f,j,g,i,b,h,e,a,c,d,k){this.elementList=l;this.textBox=f;this.origin=j;this.submit=g;this.results=0;this.alwaysShow=c;this.maxResults=d;this.ie6hack=a;this.maxchars=i;this.getName=b;this.getEntityId=h;this.geticon=e;this.getKeywords=k}TypeAheadControl.prototype.draw=function(b){var a=this;this.dropDown=document.createElement("ul");this.dropDown.className="IdPSelectDropDown";this.dropDown.style.visibility="hidden";this.dropDown.style.width=this.textBox.offsetWidth;this.dropDown.current=-1;this.textBox.setAttribute("role","listbox");document.body.appendChild(this.dropDown);this.textBox.setAttribute("role","combobox");this.textBox.setAttribute("aria-controls","IdPSelectDropDown");this.textBox.setAttribute("aria-owns","IdPSelectDropDown");this.dropDown.onmouseover=function(c){if(!c){c=window.event}var d;if(c.target){d=c.target}if(typeof d=="undefined"){d=c.srcElement}a.select(d)};this.dropDown.onmousedown=function(c){if(-1!=a.dropDown.current){a.textBox.value=a.results[a.dropDown.current][0]}};this.textBox.onkeyup=function(c){if(!c){c=window.event}a.handleKeyUp(c)};this.textBox.onkeydown=function(c){if(!c){c=window.event}a.handleKeyDown(c)};this.textBox.onblur=function(){a.hideDrop()};this.textBox.onfocus=function(){a.handleChange()};if(null==b||b){this.textBox.focus()}};TypeAheadControl.prototype.getPossible=function(b){var h=[];var j=0;var f=0;var e=0;var g;var i;b=b.toLowerCase();while(f<=this.maxResults&&j<this.elementList.length){var a=false;var c=this.getName(this.elementList[j]);if(c.toLowerCase().indexOf(b)!=-1){a=true}if(!a&&this.getEntityId(this.elementList[j]).toLowerCase().indexOf(b)!=-1){a=true}if(!a){var d=this.getKeywords(this.elementList[j]);if(null!=d&&d.toLowerCase().indexOf(b)!=-1){a=true}}if(a){h[f]=[c,this.getEntityId(this.elementList[j]),this.geticon(this.elementList[j])];f++}j++}this.dropDown.current=-1;return h};TypeAheadControl.prototype.handleKeyUp=function(b){var a=b.keyCode;if(27==a){this.textBox.value="";this.handleChange()}else{if(8==a||32==a||(a>=46&&a<112)||a>123){this.handleChange()}}};TypeAheadControl.prototype.handleKeyDown=function(b){var a=b.keyCode;if(38==a){this.upSelect()}else{if(40==a){this.downSelect()}}};TypeAheadControl.prototype.hideDrop=function(){var a=0;if(null!==this.ie6hack){while(a<this.ie6hack.length){this.ie6hack[a].style.visibility="visible";a++}}this.dropDown.style.visibility="hidden";this.textBox.setAttribute("aria-expanded","false");if(-1==this.dropDown.current){this.doUnselected()}};TypeAheadControl.prototype.showDrop=function(){var a=0;if(null!==this.ie6hack){while(a<this.ie6hack.length){this.ie6hack[a].style.visibility="hidden";a++}}this.dropDown.style.visibility="visible";this.dropDown.style.width=this.textBox.offsetWidth+"px";this.textBox.setAttribute("aria-expanded","true")};TypeAheadControl.prototype.doSelected=function(){this.submit.disabled=false};TypeAheadControl.prototype.doUnselected=function(){this.submit.disabled=true;this.textBox.setAttribute("aria-activedescendant","")};TypeAheadControl.prototype.handleChange=function(){var b=this.textBox.value;var a=this.getPossible(b);if(0===b.length||0===a.length||(!this.alwaysShow&&this.maxResults<a.length)){this.hideDrop();this.doUnselected();this.results=[];this.dropDown.current=-1}else{this.results=a;this.populateDropDown(a);if(1==a.length){this.select(this.dropDown.childNodes[0]);this.doSelected()}else{this.doUnselected()}}};TypeAheadControl.prototype.populateDropDown=function(d){this.dropDown.innerHTML="";var c=0;var a;var b;var f;while(c<d.length){a=document.createElement("li");a.id="IdPSelectOption"+c;f=d[c][0];if(null!==d[c][2]){b=document.createElement("img");b.src=d[c][2];b.width=16;b.height=16;b.alt="";a.appendChild(b);if(f.length>this.maxchars-2){f=f.substring(0,this.maxchars-2)}f=" "+f}else{if(f.length>this.maxchars){f=f.substring(0,this.maxchars)}}a.appendChild(document.createTextNode(f));a.setAttribute("role","option");this.dropDown.appendChild(a);c++}var e=this.getXY();this.dropDown.style.left=e[0]+"px";this.dropDown.style.top=e[1]+"px";this.showDrop()};TypeAheadControl.prototype.getXY=function(){var a=this.textBox;var c=0;var b=a.offsetHeight;while(a.tagName!="BODY"){c+=a.offsetLeft;b+=a.offsetTop;a=a.offsetParent}c+=a.offsetLeft;b+=a.offsetTop;return[c,b]};TypeAheadControl.prototype.select=function(b){var a=0;var c;this.dropDown.current=-1;this.doUnselected();while(a<this.dropDown.childNodes.length){c=this.dropDown.childNodes[a];if(c==b){c.className="IdPSelectCurrent";c.setAttribute("aria-selected","true");this.textBox.setAttribute("aria-activedescendant","IdPSelectOption"+a);this.doSelected();this.dropDown.current=a;this.origin.value=this.results[a][1];this.origin.textValue=this.results[a][0]}else{c.setAttribute("aria-selected","false");c.className=""}a++}this.textBox.focus()};TypeAheadControl.prototype.downSelect=function(){if(this.results.length>0){if(-1==this.dropDown.current){this.dropDown.current=0;this.dropDown.childNodes[0].className="IdPSelectCurrent";this.dropDown.childNodes[0].setAttribute("aria-selected","true");this.textBox.setAttribute("aria-activedescendant","IdPSelectOption"+0);this.doSelected();this.origin.value=this.results[0][1];this.origin.textValue=this.results[0][0]}else{if(this.dropDown.current<(this.results.length-1)){this.dropDown.childNodes[this.dropDown.current].className="";this.dropDown.current++;this.dropDown.childNodes[this.dropDown.current].className="IdPSelectCurrent";this.dropDown.childNodes[this.dropDown.current].setAttribute("aria-selected","true");this.textBox.setAttribute("aria-activedescendant","IdPSelectOption"+this.dropDown.current);this.doSelected();this.origin.value=this.results[this.dropDown.current][1];this.origin.textValue=this.results[this.dropDown.current][0]}}}};TypeAheadControl.prototype.upSelect=function(){if((this.results.length>0)&&(this.dropDown.current>0)){this.dropDown.childNodes[this.dropDown.current].className="";this.dropDown.current--;this.dropDown.childNodes[this.dropDown.current].className="IdPSelectCurrent";this.dropDown.childNodes[this.dropDown.current].setAttribute("aria-selected","true");this.textBox.setAttribute("aria-activedescendant","IdPSelectOption"+this.dropDown.current);this.doSelected();this.origin.value=this.results[this.dropDown.current][1];this.origin.textValue=this.results[this.dropDown.current][0]}};function IdPSelectUI(){var q;var V="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var aH;var R;var az;var al;var Z;var d;var E;var m;var x;var j;var au;var f;var s;var aa;var af;var C;var ae;var Q;var g;var J;var ar;var M;var S;var aj;var av;var ao;var c;var ad;var F;var P;var Y;var O;var T;var i;var ax;var U;var aJ="idpSelect";var ah="IdPSelect";var ap;var A="";var X="";var ay=[];var aE="entityID";this.draw=function(aN){if(!l(aN)){return}aH=document.getElementById(aN.insertAtDiv);if(!aH){N(z("fatal.divMissing"));return}if((null!=P)&&(null!=h(P))){var aK=b();if(aK.length!=0){var aM=aE+"="+encodeURIComponent(aK[0]);if(A.indexOf("?")==-1){aM="?"+aM}else{aM="&"+aM}p(aH,A+aM);return}}if(!e(aN.dataSource)){return}aI();t(aN.hiddenIdPs);q.sort(function(aP,aO){return aC(aP).localeCompare(aC(aO))});var aL=ab();aH.appendChild(aL);ap.draw(aN.setFocusTextBox)};var l=function(aR){var aL;C=aR.preferredIdP;ae=aR.maxPreferredIdPs;Q=aR.helpURL;g=aR.ie6Hack;J=aR.samlIdPCookieTTL;aj=aR.alwaysShow;av=aR.maxResults;ao=aR.ignoreKeywords;if(aR.showListFirst){c=aR.showListFirst}else{c=false}if(aR.noWriteCookie){ad=aR.noWriteCookie}else{ad=false}if(aR.ignoreURLParams){F=aR.ignoreURLParams}else{F=false}E=aR.defaultLogo;m=aR.defaultLogoWidth;x=aR.defaultLogoHeight;j=aR.minWidth;au=aR.minHeight;f=aR.maxWidth;s=aR.maxHeight;aa=aR.bestRatio;if(null==aR.doNotCollapse){af=true}else{af=aR.doNotCollapse}M=aR.maxIdPCharsButton;ar=aR.maxIdPCharsDropDown;S=aR.maxIdPCharsAltTxt;P=aR.autoFollowCookie;Y=aR.autoFollowCookieTTLs;var a1;if(typeof navigator=="undefined"){a1=aR.defaultLanguage}else{a1=navigator.language||navigator.userLanguage||aR.defaultLanguage}a1=a1.toLowerCase();if(a1.indexOf("-")>0){az=a1.substring(0,a1.indexOf("-"))}var aV=new IdPSelectLanguages();al=aR.defaultLanguage;if(typeof aR.langBundles!="undefined"&&typeof aR.langBundles[a1]!="undefined"){Z=aR.langBundles[a1]}else{if(typeof aV.langBundles[a1]!="undefined"){Z=aV.langBundles[a1]}else{if(typeof az!="undefined"){if(typeof aR.langBundles!="undefined"&&typeof aR.langBundles[az]!="undefined"){Z=aR.langBundles[az]}else{if(typeof aV.langBundles[az]!="undefined"){Z=aV.langBundles[az]}}}}}if(typeof aR.langBundles!="undefined"&&typeof aR.langBundles[aR.defaultLanguage]!="undefined"){d=aR.langBundles[aR.defaultLanguage]}else{d=aV.langBundles[aR.defaultLanguage]}if(!d){N("No languages work");return false}if(!Z){r("No language support for "+a1);Z=d}if(aR.testGUI){return true}var aW="urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single";var aZ;var a0=false;var aP;var aS;var aO=window;while(null!==aO.parent&&aO!==aO.parent){aO=aO.parent}var aU=aO.location;var aQ=aU.search;if(F||null==aQ||0==aQ.length||aQ.charAt(0)!="?"){if((null==aR.defaultReturn)&&!F){N(z("fatal.noparms"));return false}aL=aR.myEntityID;A=aR.defaultReturn;if(null!=aR.defaultReturnIDParam){aE=aR.defaultReturnIDParam}}else{aQ=aQ.substring(1);aP=aQ.split("&");if(aP.length===0){N(z("fatal.noparms"));return false}for(aZ=0;aZ<aP.length;aZ++){aS=aP[aZ].split("=");if(aS.length!=2){continue}if(aS[0]=="entityID"){aL=decodeURIComponent(aS[1])}else{if(aS[0]=="return"){A=decodeURIComponent(aS[1])}else{if(aS[0]=="returnIDParam"){aE=decodeURIComponent(aS[1])}else{if(aS[0]=="policy"){aW=decodeURIComponent(aS[1])}else{if(aS[0]=="isPassive"){a0=(aS[1].toUpperCase()=="TRUE")}}}}}}}var aK;if(null==aR.allowableProtocols){aK=["urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"]}else{aK=aR.allowableProtocols}var aY=false;for(var aZ=0;aZ<aK.length;aZ++){var aX=aK[aZ];if(aW==aX){aY=true;break}}if(!aY){N(z("fatal.wrongProtocol"));return false}if(aR.myEntityID!==null&&aR.myEntityID!=aL){N(z("fatal.wrongEntityId")+'"'+aL+'" != "'+aR.myEntityID+'"');return false}if(null===A||A.length===0){N(z("fatal.noReturnURL"));return false}if(!am(A)){N(z("fatal.badProtocol"));return false}if(a0){var aN=b();var aT=document.getElementById(parmsSupplied.insertAtDiv);if(aN.length==0){p(aT,A);return false}else{var aM=aE+"="+encodeURIComponent(aN[0]);if(A.indexOf("?")==-1){aM="?"+aM}else{aM="&"+aM}p(aT,A+aM);return false}}aZ=A.indexOf("?");if(aZ<0){X=A;return true}X=A.substring(0,aZ);aQ=A.substring(aZ+1);aP=aQ.split("&");for(aZ=0;aZ<aP.length;aZ++){aS=aP[aZ].split("=");if(aS.length!=2){continue}aS[1]=decodeURIComponent(aS[1]);ay.push(aS)}return true};var aI=function(){var aM=[];var aL;for(aL=0;aL<q.length;){var aK=y(q[aL]);if(null==aM[aK]){aM[aK]=aK;aL=aL+1}else{q.splice(aL,1)}}};var t=function(aM){if(null==aM||0==aM.length){return}var aL;var aK;for(aL=0;aL<aM.length;aL++){for(aK=0;aK<q.length;aK++){if(y(q[aK])==aM[aL]){q.splice(aK,1);break}}}};var am=function(aL){if(null===aL){return false}var aK="://";var aM=aL.indexOf(aK);if(aM<0){return false}aL=aL.substring(0,aM);if(aL=="http"||aL=="https"){return true}return false};var aG=function(){if(null==navigator){return false}var aK=navigator.appName;if(null==aK){return false}return(aK=="Microsoft Internet Explorer")};var p=function(aL,aM){var aK=document.createElement("a");aK.href=aM;aL.appendChild(aK);aK.click()};var e=function(aN){var aM=null;try{aM=new XMLHttpRequest()}catch(aL){}if(null==aM){try{aM=new ActiveXObject("Microsoft.XMLHTTP")}catch(aL){}}if(null==aM){try{aM=new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(aL){}}if(null==aM){N(z("fatal.noXMLHttpRequest"));return false}if(aG()){aN+="?random="+(Math.random()*1000000)}aM.open("GET",aN,false);if(typeof aM.overrideMimeType=="function"){aM.overrideMimeType("application/json")}aM.send(null);if(aM.status==200){var aK=aM.responseText;if(aK===null){N(z("fatal.noData"));return false}q=JSON.parse(aK)}else{N(z("fatal.loadFailed")+aN);return false}return true};var ac=function(aK){for(var aL=0;aL<q.length;aL++){if(y(q[aL])==aK){return q[aL]}}return null};var G=function(aR,aL){var aQ=function(aU){var aS=null;var aT;if(null==aR.Logos){return null}for(aT in aR.Logos){if(aR.Logos[aT].lang==aU&&aR.Logos[aT].width!=null&&aR.Logos[aT].width>=j&&aR.Logos[aT].height!=null&&aR.Logos[aT].height>=au){if(aS===null){aS=aR.Logos[aT]}else{me=Math.abs(aa-Math.log(aR.Logos[aT].width/aR.Logos[aT].height));him=Math.abs(aa-Math.log(aS.width/aS.height));if(him>me){aS=aR.Logos[aT]}}}}return aS};var aN=null;var aM=document.createElement("img");ak(aM,"IdPImg");aN=aQ(R);if(null===aN&&typeof az!="undefined"){aN=aQ(az)}if(null===aN){aN=aQ(null)}if(null===aN){aN=aQ(al)}if(null===aN){if(!aL){return null}aM.src=E;aM.width=m;aM.height=x;aM.alt=z("defaultLogoAlt");return aM}aM.src=aN.value;var aO=aC(aR);if(aO.length>S){aO=aO.substring(0,S)+"..."}aM.alt=aO;var aK=aN.width;var aP=aN.height;if(aK>f){aP=(f/aK)*aP;aK=f}if(aP>s){aK=(s/aP)*aK;aP=s}aM.setAttribute("width",aK);aM.setAttribute("height",aP);return aM};var ab=function(){var aL=an("IdPSelector");var aK;aK=aA(aL);n(aL,aK);W(aL,aK);if(null!=P){B(aL)}return aL};var L=function(aM,aT,aL){var aK=an(undefined,"PreferredIdPButton");var aS=document.createElement("a");var aR=aE+"="+encodeURIComponent(y(aM));var aN=A;var aP=G(aM,aL);if(aN.indexOf("?")==-1){aR="?"+aR}else{aR="&"+aR}aS.href=aN+aR;aS.onclick=function(){aF(y(aM))};if(null!=aP){var aU=an(undefined,"PreferredIdPImg");aU.appendChild(aP);aS.appendChild(aU)}var aQ=an(undefined,"TextDiv");var aO=aC(aM);if(aO.length>M){aO=aO.substring(0,M)+"..."}aK.title=aO;aQ.appendChild(document.createTextNode(aO));aS.appendChild(aQ);aK.appendChild(aS);return aK};var aD=function(aK,aN){var aM=an(undefined,"TextDiv");var aL=document.createTextNode(z(aN));aM.appendChild(aL);aK.appendChild(aM)};var a=function(aK,aM){if(null===aM||0===aM.length||"-"==aM.value){return}var aL=0;while(aL<aK.options.length){if(aK.options[aL].value==aM){aK.options[aL].selected=true;break}aL++}};var aA=function(aP){var aO=K();if(0===aO.length){return false}var aK=af;for(var aM=0;aM<ae&&aM<aO.length;aM++){if(aO[aM]&&G(aO[aM],false)){aK=true}}var aN;if(aK){aN=an("PreferredIdPTile")}else{aN=an("PreferredIdPTileNoImg")}aD(aN,"idpPreferred.label");for(var aM=0;aM<ae&&aM<aO.length;aM++){if(aO[aM]){var aL=L(aO[aM],aM,aK);aN.appendChild(aL)}}aP.appendChild(aN);return true};var ag=function(){var aL=document.createElement("form");T.appendChild(aL);aL.action=X;aL.method="GET";aL.setAttribute("autocomplete","OFF");var aK=0;for(aK=0;aK<ay.length;aK++){var aM=document.createElement("input");aM.setAttribute("type","hidden");aM.name=ay[aK][0];aM.value=ay[aK][1];aL.appendChild(aM)}return aL};var n=function(aR,aL){T=an("IdPEntryTile");if(c){T.style.display="none"}var aM=document.createElement("label");aM.setAttribute("for",aJ+"Input");if(aL){aD(aM,"idpEntry.label")}else{aD(aM,"idpEntry.NoPreferred.label")}var aP=ag();aP.appendChild(aM);var aO=document.createElement("input");aP.appendChild(aO);aO.type="text";k(aO,"Input");var aQ=document.createElement("input");aQ.setAttribute("type","hidden");aP.appendChild(aQ);aQ.name=aE;aQ.value="-";var aN=u("Select");aN.disabled=true;aP.appendChild(aN);aP.onsubmit=function(){if(null===aQ.value||0===aQ.value.length||"-"==aQ.value){return false}aO.value=aQ.textValue;aF(aQ.value);return true};ap=new TypeAheadControl(q,aO,aQ,aN,ar,aC,y,ai,g,aj,av,H);var aK=document.createElement("a");aK.appendChild(document.createTextNode(z("idpList.showList")));aK.href="#";ak(aK,"DropDownToggle");aK.onclick=function(){T.style.display="none";a(ax,aQ.value);i.style.display="";U.focus();return false};T.appendChild(aK);w(T);aR.appendChild(T)};var W=function(aK,aN){i=an("IdPListTile");if(!c){i.style.display="none"}var aR=document.createElement("label");aR.setAttribute("for",aJ+"Selector");if(aN){aD(aR,"idpList.label")}else{aD(aR,"idpList.NoPreferred.label")}ax=document.createElement("select");k(ax,"Selector");ax.name=aE;i.appendChild(ax);var aS=o("-",z("idpList.defaultOptionLabel"));aS.selected=true;ax.appendChild(aS);var aM;for(var aO=0;aO<q.length;aO++){aM=q[aO];aS=o(y(aM),aC(aM));ax.appendChild(aS)}var aL=ag();aL.appendChild(aR);aL.appendChild(ax);aL.onsubmit=function(){if(ax.selectedIndex<1){return false}aF(ax.options[ax.selectedIndex].value);return true};var aP=u("List");U=aP;aL.appendChild(aP);i.appendChild(aL);var aQ=document.createElement("a");aQ.appendChild(document.createTextNode(z("idpList.showSearch")));aQ.href="#";ak(aQ,"DropDownToggle");aQ.onclick=function(){T.style.display="";i.style.display="none";return false};i.appendChild(aQ);w(i);aK.appendChild(i)};var B=function(aN){var aL="IdPSelectAutoDisp";autoDispatchTile=an(undefined,"autoDispatchArea");autoDispatchTile.appendChild(document.createTextNode(z("autoFollow.message")));var aK=document.createElement("input");aK.setAttribute("type","radio");aK.setAttribute("checked","checked");aK.setAttribute("name",aL);aK.onclick=function(){D(0)};div=an(undefined,"autoDispatchTile");div.appendChild(aK);div.appendChild(document.createTextNode(z("autoFollow.never")));autoDispatchTile.appendChild(div);var aM;for(aM=0;aM<Y.length;aM++){aK=document.createElement("input");aK.setAttribute("type","radio");aK.setAttribute("name",aL);aK.life=Y[aM];aK.onclick=function(){var aO=this.life;D(aO)};div=an(undefined,"autoDispatchTile");div.appendChild(aK);div.appendChild(document.createTextNode(z("autoFollow.time"+aM)));autoDispatchTile.appendChild(div)}aN.appendChild(autoDispatchTile)};var u=function(aL){var aK=document.createElement("input");aK.setAttribute("type","submit");aK.value=z("submitButton.label");k(aK,aL+"Button");return aK};var w=function(aL){var aK=document.createElement("a");aK.href=Q;aK.appendChild(document.createTextNode(z("helpText")));ak(aK,"HelpButton");aL.appendChild(aK)};var an=function(aM,aK){var aL=document.createElement("div");if(undefined!==aM){k(aL,aM)}if(undefined!==aK){ak(aL,aK)}return aL};var o=function(aL,aM){var aK=document.createElement("option");aK.value=aL;if(aM.length>ar){aM=aM.substring(0,ar)}aK.appendChild(document.createTextNode(aM));return aK};var k=function(aL,aK){aL.id=aJ+aK};var ak=function(aL,aK){aL.setAttribute("class",ah+aK)};var aB=function(aK){return document.getElementById(aJ+aK)};var aF=function(aK){I(aK);aq(O)};var z=function(aK){var aL=Z[aK];if(!aL){aL=d[aK]}if(!aL){aL="Missing message for "+aK}return aL};var y=function(aK){return aK.entityID};var ai=function(aM){var aK;if(null==aM.Logos){return null}for(aK=0;aK<aM.Logos.length;aK++){var aL=aM.Logos[aK];if(aL.height=="16"&&aL.width=="16"){if(null==aL.lang||R==aL.lang||(typeof az!="undefined"&&az==aL.lang)||al==aL.lang){return aL.value}}}return null};var aC=function(aL){var aK=aw(aL.DisplayNames);if(null!==aK){return aK}r("No Name entry in any language for "+y(aL));return y(aL)};var H=function(aL){if(ao||null==aL.Keywords){return null}var aK=aw(aL.Keywords);return aK};var aw=function(aK){var aL;for(aL in aK){if(aK[aL].lang==R){return aK[aL].value}}if(typeof az!="undefined"){for(aL in aK){if(aK[aL].lang==az){return aK[aL].value}}}for(aL in aK){if(aK[aL].lang==null){return aK[aL].value}}for(aL in aK){if(aK[aL].lang==al){return aK[aL].value}}return null};var K=function(){var aO=[];var aN=0;var aM;var aL;if(null!=C){for(aM=0;aM<C.length&&aM<ae;aM++){aO[aM]=ac(C[aM]);aN++}}O=b();for(aM=aN,aL=0;aL<O.length&&aM<ae;aL++){var aK=ac(O[aL]);if(typeof aO.indexOf==="undefined"){aO.push(aK);aM++}else{if(aO.indexOf(aK)===-1){aO.push(aK);aM++}}}return aO};var I=function(aK){var aL=[];while(0!==O.length){var aM=O.pop();if(aM!=aK){aL.unshift(aM)}}aL.unshift(aK);O=aL;return};var D=function(aM){var aK;if(aM>0){var aL=new Date();cookieTTL=aM*24*60*60*1000;aK=new Date(aL.getTime()+cookieTTL)}else{aK=new Date(0)}document.cookie=P+"=1;path=/;expires="+aK.toUTCString()};var h=function(aM){var aO,aL;var aP;aP=document.cookie.split(";");for(aO=0;aO<aP.length;aO++){var aN=aP[aO];var aK=aN.indexOf("=");var aQ=aN.substring(0,aK);if(aM==(aQ.replace(/^\s+|\s+$/g,""))){return aN.substring(aK+1)}}return null};var b=function(){var aK=[];var aL;var aM=h("_saml_idp");if(aM!=null){aM=aM.replace(/^\s+|\s+$/g,"");aM=aM.replace("+","%20");aM=aM.split("%20");for(aL=aM.length;aL>0;aL--){if(0===aM[aL-1].length){continue}var aN=at(decodeURIComponent(aM[aL-1]));if(aN.length>0){aK.push(aN)}}}return aK};var aq=function(aP){var aM=[];var aO=aP.length;if(ad){return}if(aO>5){aO=5}for(var aN=aO;aN>0;aN--){if(aP[aN-1].length>0){aM.push(encodeURIComponent(v(aP[aN-1])))}}var aK=null;if(J){var aL=new Date();cookieTTL=J*24*60*60*1000;aK=new Date(aL.getTime()+cookieTTL)}document.cookie="_saml_idp="+aM.join("%20")+"; path = /"+((aK===null)?"":"; expires="+aK.toUTCString())};var v=function(aT){var aK="",aO,aM,aL,aS,aR,aQ,aP;for(var aN=0;aN<aT.length;){aO=aT.charCodeAt(aN++);aM=aT.charCodeAt(aN++);aL=aT.charCodeAt(aN++);aS=aO>>2;aR=((aO&3)<<4)+(aM>>4);aQ=((aM&15)<<2)+(aL>>6);aP=aL&63;if(isNaN(aM)){aQ=aP=64}else{if(isNaN(aL)){aP=64}}aK+=V.charAt(aS)+V.charAt(aR)+V.charAt(aQ)+V.charAt(aP)}return aK};var at=function(aN){var aL="",aU,aS,aQ,aT,aR,aP,aO;var aM=0;var aK=/[^A-Za-z0-9\+\/\=]/g;aN=aN.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{aT=V.indexOf(aN.charAt(aM++));aR=V.indexOf(aN.charAt(aM++));aP=V.indexOf(aN.charAt(aM++));aO=V.indexOf(aN.charAt(aM++));aU=(aT<<2)|(aR>>4);aS=((aR&15)<<4)|(aP>>2);aQ=((aP&3)<<6)|aO;aL=aL+String.fromCharCode(aU);if(aP!=64){aL=aL+String.fromCharCode(aS)}if(aO!=64){aL=aL+String.fromCharCode(aQ)}aU=aS=aQ="";aT=aR=aP=aO=""}while(aM<aN.length);return aL};var N=function(aL){alert("FATAL - DISCO UI:"+aL);var aK=document.createTextNode(aL);aH.appendChild(aK)};var r=function(){}}(new IdPSelectUI()).draw(new IdPSelectUIParms()); \ No newline at end of file diff --git a/docker/shibboleth-ds/idpselect_config.js b/docker/shibboleth-ds/idpselect_config.js new file mode 100644 index 0000000000000000000000000000000000000000..be2cb66689df11f69a391b1b9b7b3724c352a7a2 --- /dev/null +++ b/docker/shibboleth-ds/idpselect_config.js @@ -0,0 +1,81 @@ + +/** @class IdP Selector UI */ +function IdPSelectUIParms(){ + // + // Adjust the following to fit into your local configuration + // + this.alwaysShow = true; // If true, this will show results as soon as you start typing + this.dataSource = '/Shibboleth.sso/DiscoFeed'; // Where to get the data from + this.defaultLanguage = 'en'; // Language to use if the browser local doesnt have a bundle + this.defaultLogo = 'blank.gif'; // Replace with your own logo + this.defaultLogoWidth = 1; + this.defaultLogoHeight = 1 ; + this.defaultReturn = null; // If non null, then the default place to send users who are not + // Approaching via the Discovery Protocol for example + //this.defaultReturn = "https://example.org/Shibboleth.sso/DS?SAMLDS=1&target=https://example.org/secure"; + this.defaultReturnIDParam = null; + this.helpURL = 'https://wiki.shibboleth.net/confluence/display/SHIB2/DiscoveryService' + //this.helpURL = 'https://wiki.shibboleth.net/confluence/display/SHIB2/DSRoadmap'; + this.ie6Hack = null; // An array of structures to disable when drawing the pull down (needed to + // handle the ie6 z axis problem + this.insertAtDiv = 'idpSelect'; // The div where we will insert the data + this.maxResults = 10; // How many results to show at once or the number at which to + // start showing if alwaysShow is false + this.myEntityID = null; // If non null then this string must match the string provided in the DS parms + this.preferredIdP = ['https://login.ligo.org/idp/shibboleth', 'https://login2.ligo.org/idp/shibboleth', 'https://google.cirrusidentity.com/gateway']; + this.hiddenIdPs = null; // Array of entityIds to delete + this.ignoreKeywords = false; // Do we ignore the <mdui:Keywords/> when looking for candidates + this.showListFirst = false; // Do we start with a list of IdPs or just the dropdown + this.samlIdPCookieTTL = 730; // in days + this.setFocusTextBox = true; // Set to false to supress focus + this.testGUI = false; + + this.autoFollowCookie = null; // If you want auto-dispatch, set this to the cookie name to use + this.autoFollowCookieTTLs = [ 1, 60, 270 ]; // Cookie life (in days). Changing this requires changes to idp_select_languages + + // + // Language support. + // + // The minified source provides "en", "de", "pt-br" and "jp". + // + // Override any of these below, or provide your own language + // + //this.langBundles = { + //'en': { + // 'fatal.divMissing': '<div> specified as "insertAtDiv" could not be located in the HTML', + // 'fatal.noXMLHttpRequest': 'Browser does not support XMLHttpRequest, unable to load IdP selection data', + // 'fatal.wrongProtocol' : 'Policy supplied to DS was not "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"', + // 'fatal.wrongEntityId' : 'entityId supplied by SP did not match configuration', + // 'fatal.noData' : 'Metadata download returned no data', + // 'fatal.loadFailed': 'Failed to download metadata from ', + // 'fatal.noparms' : 'No parameters to discovery session and no defaultReturn parameter configured', + // 'fatal.noReturnURL' : "No URL return parameter provided", + // 'fatal.badProtocol' : "Return request must start with https:// or http://", + // 'idpPreferred.label': 'Use a suggested selection:', + // 'idpEntry.label': 'Or enter your organization\'s name', + // 'idpEntry.NoPreferred.label': 'Enter your organization\'s name', + // 'idpList.label': 'Or select your organization from the list below', + // 'idpList.NoPreferred.label': 'Select your organization from the list below', + // 'idpList.defaultOptionLabel': 'Please select your organization...', + // 'idpList.showList' : 'Allow me to pick from a list', + // 'idpList.showSearch' : 'Allow me to specify the site', + // 'submitButton.label': 'Continue', + // 'helpText': 'Help', + // 'defaultLogoAlt' : 'DefaultLogo' + //} + //}; + + // + // The following should not be changed without changes to the css. Consider them as mandatory defaults + // + this.maxPreferredIdPs = 4; + this.maxIdPCharsButton = 33; + this.maxIdPCharsDropDown = 58; + this.maxIdPCharsAltTxt = 60; + + this.minWidth = 20; + this.minHeight = 20; + this.maxWidth = 115; + this.maxHeight = 69; + this.bestRatio = Math.log(80 / 60); +} diff --git a/docker/shibboleth-ds/index.html b/docker/shibboleth-ds/index.html new file mode 100644 index 0000000000000000000000000000000000000000..a4f85bc907d461c8b24f56dc9e8c5ebc14f54373 --- /dev/null +++ b/docker/shibboleth-ds/index.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" +"http://www.w3.org/TR/html4/loose.dtd"> +<html lang="en"> +<head> + <title>IDP select test bed</title> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-5" /> + <link rel="stylesheet" type="text/css" href="idpselect.css" /> +</head> + +<body> + <div id="idpSelect"></div> + + <script src="idpselect_config.js" type="text/javascript" language="javascript"></script> + + <script src="idpselect.js" type="text/javascript" language="javascript"></script> + + + <noscript> + <!-- If you need to care about non javascript browsers you will need to + generate a hyperlink to a non-js DS. + + To build you will need: + - URL: The base URL of the DS you use + - EI: Your entityId, URLencoded. You can get this from the line that + this page is called with. + - RET: Your return address dlib-adidp.ucs.ed.ac.uk. Again you can get + this from the page this is called with, but beware of the + target%3Dcookie%253A5269905f bit.. + + < href=${URL}?entityID=${EI}&return=${RET} + --> + + Your Browser does not support javascript. Please use + <a href="http://federation.org/DS/DS?entityID=https%3A%2F%2FyourentityId.edu.edu%2Fshibboleth&return=https%3A%2F%2Fyourreturn.edu%2FShibboleth.sso%2FDS%3FSAMLDS%3D1%26target%3Dhttps%3A%2F%2Fyourreturn.edu%2F">this link</a>. + + </noscript> +</body> +</html> diff --git a/docker/shibboleth-ds/nonminimised/idpselect.js b/docker/shibboleth-ds/nonminimised/idpselect.js new file mode 100644 index 0000000000000000000000000000000000000000..6f355a44f140851fe9436949ec7935b6dbe736f3 --- /dev/null +++ b/docker/shibboleth-ds/nonminimised/idpselect.js @@ -0,0 +1,1582 @@ +function IdPSelectUI() { + // + // module locals + // + var idpData; + var base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var idpSelectDiv; + var lang; + var majorLang; + var defaultLang; + var langBundle; + var defaultLangBundle; + var defaultLogo; + var defaultLogoWidth; + var defaultLogoHeight; + var minWidth; + var minHeight; + var maxWidth; + var maxHeight; + var bestRatio; + var doNotCollapse; + + // + // Parameters passed into our closure + // + var preferredIdP; + var maxPreferredIdPs; + var helpURL; + var ie6Hack; + var samlIdPCookieTTL; + var maxIdPCharsDropDown; + var maxIdPCharsButton; + var maxIdPCharsAltTxt; + var alwaysShow; + var maxResults; + var ignoreKeywords; + var showListFirst; + var noWriteCookie; + var ignoreURLParams; + + var autoFollowCookie; + var autoFollowCookieTTLs; + + // + // The cookie contents + // + var userSelectedIdPs; + // + // Anchors used inside autofunctions + // + var idpEntryDiv; + var idpListDiv; + var idpSelect; + var listButton; + + // + // local configuration + // + var idPrefix = 'idpSelect'; + var classPrefix = 'IdPSelect'; + var dropDownControl; + + // + // DS protocol configuration + // + var returnString = ''; + var returnBase=''; + var returnParms= []; + var returnIDParam = 'entityID'; + + // ************************************* + // Public functions + // ************************************* + + /** + Draws the IdP Selector UI on the screen. This is the main + method for the IdPSelectUI class. + */ + this.draw = function(parms){ + + if (!setupLocals(parms)) { + return; + } + + idpSelectDiv = document.getElementById(parms.insertAtDiv); + if(!idpSelectDiv){ + fatal(getLocalizedMessage('fatal.divMissing')); + return; + } + + // + // Quick test for auto-dispatch + // + if ((null != autoFollowCookie) && (null != getCookieCalled( autoFollowCookie ))) { + + var prefs = retrieveUserSelectedIdPs(); + if (prefs.length != 0) { + var retString = returnIDParam + '=' + encodeURIComponent(prefs[0]); + // + // Compose up the URL + // + if (returnString.indexOf('?') == -1) { + retString = '?' + retString; + } else { + retString = '&' + retString; + } + // + // Go there + // + dispatchTo(idpSelectDiv, returnString + retString); + return; + } + } + + if (!load(parms.dataSource)) { + return; + } + deDupe(); + stripHidden(parms.hiddenIdPs); + + idpData.sort(function(a,b) {return getLocalizedName(a).localeCompare(getLocalizedName(b));}); + + var idpSelector = buildIdPSelector(); + idpSelectDiv.appendChild(idpSelector); + dropDownControl.draw(parms.setFocusTextBox); + } ; + + // ************************************* + // Private functions + // + // Data Manipulation + // + // ************************************* + + /** + Copies the "parameters" in the function into namesspace local + variables. This means most of the work is done outside the + IdPSelectUI object + */ + + var setupLocals = function (paramsSupplied) { + // + // Copy parameters in + // + var suppliedEntityId; + + preferredIdP = paramsSupplied.preferredIdP; + maxPreferredIdPs = paramsSupplied.maxPreferredIdPs; + helpURL = paramsSupplied.helpURL; + ie6Hack = paramsSupplied.ie6Hack; + samlIdPCookieTTL = paramsSupplied.samlIdPCookieTTL; + alwaysShow = paramsSupplied.alwaysShow; + maxResults = paramsSupplied.maxResults; + ignoreKeywords = paramsSupplied.ignoreKeywords; + if (paramsSupplied.showListFirst) { + showListFirst = paramsSupplied.showListFirst; + } else { + showListFirst = false; + } + if (paramsSupplied.noWriteCookie) { + noWriteCookie = paramsSupplied.noWriteCookie; + } else { + noWriteCookie = false; + } + if (paramsSupplied.ignoreURLParams) { + ignoreURLParams = paramsSupplied.ignoreURLParams; + } else { + ignoreURLParams = false; + } + + defaultLogo = paramsSupplied.defaultLogo; + defaultLogoWidth = paramsSupplied.defaultLogoWidth; + defaultLogoHeight = paramsSupplied.defaultLogoHeight; + minWidth = paramsSupplied.minWidth; + minHeight = paramsSupplied.minHeight; + maxWidth = paramsSupplied.maxWidth; + maxHeight = paramsSupplied.maxHeight; + bestRatio = paramsSupplied.bestRatio; + if (null == paramsSupplied.doNotCollapse) { + doNotCollapse = true; + } else { + doNotCollapse = paramsSupplied.doNotCollapse; + } + + maxIdPCharsButton = paramsSupplied.maxIdPCharsButton; + maxIdPCharsDropDown = paramsSupplied.maxIdPCharsDropDown; + maxIdPCharsAltTxt = paramsSupplied.maxIdPCharsAltTxt; + + autoFollowCookie = paramsSupplied.autoFollowCookie; + autoFollowCookieTTLs = paramsSupplied.autoFollowCookieTTLs; + + var lang; + + if (typeof navigator == 'undefined') { + lang = paramsSupplied.defaultLanguage; + } else { + lang = navigator.language || navigator.userLanguage || paramsSupplied.defaultLanguage; + } + lang = lang.toLowerCase(); + + if (lang.indexOf('-') > 0) { + majorLang = lang.substring(0, lang.indexOf('-')); + } + + var providedLangs = new IdPSelectLanguages(); + + defaultLang = paramsSupplied.defaultLanguage; + + if (typeof paramsSupplied.langBundles != 'undefined' && typeof paramsSupplied.langBundles[lang] != 'undefined') { + langBundle = paramsSupplied.langBundles[lang]; + } else if (typeof providedLangs.langBundles[lang] != 'undefined') { + langBundle = providedLangs.langBundles[lang]; + } else if (typeof majorLang != 'undefined') { + if (typeof paramsSupplied.langBundles != 'undefined' && typeof paramsSupplied.langBundles[majorLang] != 'undefined') { + langBundle = paramsSupplied.langBundles[majorLang]; + } else if (typeof providedLangs.langBundles[majorLang] != 'undefined') { + langBundle = providedLangs.langBundles[majorLang]; + } + } + + if (typeof paramsSupplied.langBundles != 'undefined' && typeof paramsSupplied.langBundles[paramsSupplied.defaultLanguage] != 'undefined') { + defaultLangBundle = paramsSupplied.langBundles[paramsSupplied.defaultLanguage]; + } else { + defaultLangBundle = providedLangs.langBundles[paramsSupplied.defaultLanguage]; + } + + // + // Setup Language bundles + // + if (!defaultLangBundle) { + fatal('No languages work'); + return false; + } + if (!langBundle) { + debug('No language support for ' + lang); + langBundle = defaultLangBundle; + } + + if (paramsSupplied.testGUI) { + // + // no policing of parms + // + return true; + } + // + // Now set up the return values from the URL + // + var policy = 'urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single'; + var i; + var isPassive = false; + var parms; + var parmPair; + var win = window; + while (null !== win.parent && win !== win.parent) { + win = win.parent; + } + var loc = win.location; + var parmlist = loc.search; + if (ignoreURLParams || null == parmlist || 0 == parmlist.length || parmlist.charAt(0) != '?') { + + if ((null == paramsSupplied.defaultReturn)&& !ignoreURLParams) { + + fatal(getLocalizedMessage('fatal.noparms')); + return false; + } + // + // No parameters, so just collect the defaults + // + suppliedEntityId = paramsSupplied.myEntityID; + returnString = paramsSupplied.defaultReturn; + if (null != paramsSupplied.defaultReturnIDParam) { + returnIDParam = paramsSupplied.defaultReturnIDParam; + } + + } else { + parmlist = parmlist.substring(1); + + // + // protect against various hideousness by decoding. We re-encode just before we push + // + + parms = parmlist.split('&'); + if (parms.length === 0) { + + fatal(getLocalizedMessage('fatal.noparms')); + return false; + } + + for (i = 0; i < parms.length; i++) { + parmPair = parms[i].split('='); + if (parmPair.length != 2) { + continue; + } + if (parmPair[0] == 'entityID') { + suppliedEntityId = decodeURIComponent(parmPair[1]); + } else if (parmPair[0] == 'return') { + returnString = decodeURIComponent(parmPair[1]); + } else if (parmPair[0] == 'returnIDParam') { + returnIDParam = decodeURIComponent(parmPair[1]); + } else if (parmPair[0] == 'policy') { + policy = decodeURIComponent(parmPair[1]); + } else if (parmPair[0] == 'isPassive') { + isPassive = (parmPair[1].toUpperCase() == "TRUE"); + } + } + } + // Test protocol + var allowableProtocols; + if (null == paramsSupplied.allowableProtocols) { + allowableProtocols = ["urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"]; + } else { + allowableProtocols = paramsSupplied.allowableProtocols; + } + + var protocolOk = false; + for (var i = 0 ; i < allowableProtocols.length; i++) { + var protocol = allowableProtocols[i]; + if (policy == protocol) { + protocolOk = true; + break; + } + } + + if (!protocolOk) { + fatal(getLocalizedMessage('fatal.wrongProtocol')); + return false; + } + if (paramsSupplied.myEntityID !== null && paramsSupplied.myEntityID != suppliedEntityId) { + fatal(getLocalizedMessage('fatal.wrongEntityId') + '"' + suppliedEntityId + '" != "' + paramsSupplied.myEntityID + '"'); + return false; + } + if (null === returnString || returnString.length === 0) { + fatal(getLocalizedMessage('fatal.noReturnURL')); + return false; + } + if (!validProtocol(returnString)) { + fatal(getLocalizedMessage('fatal.badProtocol')); + return false; + } + + // + // isPassive + // + if (isPassive) { + var prefs = retrieveUserSelectedIdPs(); + var parentDiv = document.getElementById(parmsSupplied.insertAtDiv); + if (prefs.length == 0) { + // + // no preference, go back + // + dispatchTo(parentDiv, returnString); + return false; + } else { + var retString = returnIDParam + '=' + encodeURIComponent(prefs[0]); + // + // Compose up the URL + // + if (returnString.indexOf('?') == -1) { + retString = '?' + retString; + } else { + retString = '&' + retString; + } + + dispatchTo(parentDiv, returnString + retString); + return false; + } + } + + // + // Now split up returnString + // + i = returnString.indexOf('?'); + if (i < 0) { + returnBase = returnString; + return true; + } + returnBase = returnString.substring(0, i); + parmlist = returnString.substring(i+1); + parms = parmlist.split('&'); + for (i = 0; i < parms.length; i++) { + parmPair = parms[i].split('='); + if (parmPair.length != 2) { + continue; + } + parmPair[1] = decodeURIComponent(parmPair[1]); + returnParms.push(parmPair); + } + return true; + }; + + /** Deduplicate by entityId */ + var deDupe = function() { + var names = []; + var j; + for (j = 0; j < idpData.length; ) { + var eid = getEntityId(idpData[j]); + if (null == names[eid]) { + names[eid] = eid; + j = j + 1; + } else { + idpData.splice(j, 1); + } + } + } + + /** + Strips the supllied IdP list from the idpData + */ + var stripHidden = function(hiddenList) { + + if (null == hiddenList || 0 == hiddenList.length) { + return; + } + var i; + var j; + for (i = 0; i < hiddenList.length; i++) { + for (j = 0; j < idpData.length; j++) { + if (getEntityId(idpData[j]) == hiddenList[i]) { + idpData.splice(j, 1); + break; + } + } + } + } + + + /** + * Strip the "protocol://host" bit out of the URL and check the protocol + * @param the URL to process + * @return whether it starts with http: or https:// + */ + + var validProtocol = function(s) { + if (null === s) { + return false; + } + var marker = "://"; + var protocolEnd = s.indexOf(marker); + if (protocolEnd < 0) { + return false; + } + s = s.substring(0, protocolEnd); + if (s == "http" || s== "https") { + return true; + } + return false; + }; + + /** + * We need to cache bust on IE. So how do we know? Use a bigger hammer. + */ + var isIE = function() { + if (null == navigator) { + return false; + } + var browserName = navigator.appName; + if (null == browserName) { + return false; + } + return (browserName == 'Microsoft Internet Explorer') ; + } ; + + /** + * Alternative to location.href=string + * + * Needed to cache bust Firefox + */ + + var dispatchTo = function(theParent, whereTo) { + var aval = document.createElement('a'); + + aval.href = whereTo; + theParent.appendChild(aval); + + aval.click(); + } + + /** + Loads the data used by the IdP selection UI. Data is loaded + from a JSON document fetched from the given url. + + @param {Function} failureCallback A function called if the JSON + document can not be loaded from the source. This function will + passed the {@link XMLHttpRequest} used to request the JSON data. + */ + var load = function(dataSource){ + var xhr = null; + + try { + xhr = new XMLHttpRequest(); + } catch (e) {} + if (null == xhr) { + // + // EDS24. try to get 'Microsoft.XMLHTTP' + // + try { + xhr = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + } + if (null == xhr) { + // + // EDS35. try to get 'Microsoft.XMLHTTP' + // + try { + xhr = new ActiveXObject('MSXML2.XMLHTTP.3.0'); + } catch (e) {} + } + if (null == xhr) { + fatal(getLocalizedMessage('fatal.noXMLHttpRequest')); + return false; + } + + if (isIE()) { + // + // cache bust (for IE) + // + dataSource += '?random=' + (Math.random()*1000000); + } + + // + // Grab the data + // + xhr.open('GET', dataSource, false); + if (typeof xhr.overrideMimeType == 'function') { + xhr.overrideMimeType('application/json'); + } + xhr.send(null); + + if(xhr.status == 200){ + // + // 200 means we got it OK from as web source + // if locally loading its 0. Go figure + // + var jsonData = xhr.responseText; + if(jsonData === null){ + fatal(getLocalizedMessage('fatal.noData')); + return false; + } + + // + // Parse it + // + + idpData = JSON.parse(jsonData); + + }else{ + fatal(getLocalizedMessage('fatal.loadFailed') + dataSource); + return false; + } + return true; + }; + + /** + Returns the idp object with the given name. + + @param (String) the name we are interested in + @return (Object) the IdP we care about + */ + + var getIdPFor = function(idpName) { + + for (var i = 0; i < idpData.length; i++) { + if (getEntityId(idpData[i]) == idpName) { + return idpData[i]; + } + } + return null; + }; + + /** + Returns a suitable image from the given IdP + + @param (Object) The IdP + @return Object) a DOM object suitable for insertion + + TODO - rather more careful selection + */ + + var getImageForIdP = function(idp, useDefault) { + + var getBestFit = function(language) { + // + // See GetLocalizedEntry + // + var bestFit = null; + var i; + if (null == idp.Logos) { + return null; + } + for (i in idp.Logos) { + if (idp.Logos[i].lang == language && + idp.Logos[i].width != null && + idp.Logos[i].width >= minWidth && + idp.Logos[i].height != null && + idp.Logos[i].height >= minHeight) { + if (bestFit === null) { + bestFit = idp.Logos[i]; + } else { + me = Math.abs(bestRatio - Math.log(idp.Logos[i].width/idp.Logos[i].height)); + him = Math.abs(bestRatio - Math.log(bestFit.width/bestFit.height)); + if (him > me) { + bestFit = idp.Logos[i]; + } + } + } + } + return bestFit; + } ; + + var bestFit = null; + var img = document.createElement('img'); + setClass(img, 'IdPImg'); + + bestFit = getBestFit(lang); + if (null === bestFit && typeof majorLang != 'undefined') { + bestFit = getBestFit(majorLang); + } + if (null === bestFit) { + bestFit = getBestFit(null); + } + if (null === bestFit) { + bestFit = getBestFit(defaultLang); + } + + if (null === bestFit) { + if (!useDefault) { + return null; + } + img.src = defaultLogo; + img.width = defaultLogoWidth; + img.height = defaultLogoHeight; + img.alt = getLocalizedMessage('defaultLogoAlt'); + return img; + } + + img.src = bestFit.value; + var altTxt = getLocalizedName(idp); + if (altTxt.length > maxIdPCharsAltTxt) { + altTxt = altTxt.substring(0, maxIdPCharsAltTxt) + '...'; + } + img.alt = altTxt; + + var w = bestFit.width; + var h = bestFit.height; + if (w>maxWidth) { + h = (maxWidth/w) * h; + w = maxWidth; + } + if (h> maxHeight) { + w = (maxHeight/h) * w; + h = maxHeight; + } + + img.setAttribute('width', w); + img.setAttribute('height', h); + return img; + }; + + // ************************************* + // Private functions + // + // GUI Manipulation + // + // ************************************* + + /** + Builds the IdP selection UI. + + Three divs. PreferredIdPTime, EntryTile and DropdownTile + Optional div AutoDispatchPane + + @return {Element} IdP selector UI + */ + var buildIdPSelector = function(){ + var containerDiv = buildDiv('IdPSelector'); + var preferredTileExists; + preferredTileExists = buildPreferredIdPTile(containerDiv); + buildIdPEntryTile(containerDiv, preferredTileExists); + buildIdPDropDownListTile(containerDiv, preferredTileExists); + if (null != autoFollowCookie) { + buildAutoDispatchPane(containerDiv); + } + return containerDiv; + }; + + /** + Builds a button for the provided IdP + <div class="preferredIdPButton"> + <a href="XYX" onclick=setparm('ABCID')> + <div class= + <img src="https:\\xyc.gif"> <!-- optional --> + XYX Text + </a> + </div> + + @param (Object) The IdP + + @return (Element) preselector for the IdP + */ + + var composePreferredIdPButton = function(idp, uniq, useDefault) { + var div = buildDiv(undefined, 'PreferredIdPButton'); + var aval = document.createElement('a'); + var retString = returnIDParam + '=' + encodeURIComponent(getEntityId(idp)); + var retVal = returnString; + var img = getImageForIdP(idp, useDefault); + // + // Compose up the URL + // + if (retVal.indexOf('?') == -1) { + retString = '?' + retString; + } else { + retString = '&' + retString; + } + aval.href = retVal + retString; + aval.onclick = function () { + selectIdP(getEntityId(idp)); + }; + if (null != img) { + var imgDiv=buildDiv(undefined, 'PreferredIdPImg'); + imgDiv.appendChild(img); + aval.appendChild(imgDiv); + } + + var nameDiv = buildDiv(undefined, 'TextDiv'); + var nameStr = getLocalizedName(idp); + if (nameStr.length > maxIdPCharsButton) { + nameStr = nameStr.substring(0, maxIdPCharsButton) + '...'; + } + div.title = nameStr; + nameDiv.appendChild(document.createTextNode(nameStr)); + aval.appendChild(nameDiv); + + div.appendChild(aval); + return div; + }; + + /** + * Builds and populated a text Div + */ + var buildTextDiv = function(parent, textId) + { + var div = buildDiv(undefined, 'TextDiv'); + var introTxt = document.createTextNode(getLocalizedMessage(textId)); + div.appendChild(introTxt); + parent.appendChild(div); + } ; + + var setSelector = function (selector, selected) { + if (null === selected || 0 === selected.length || '-' == selected.value) { + return; + } + var i = 0; + while (i < selector.options.length) { + if (selector.options[i].value == selected) { + selector.options[i].selected = true; + break; + } + i++; + } + } + + /** + Builds the preferred IdP selection UI (top half of the UI w/ the + IdP buttons) + + <div id=prefix+"PreferredIdPTile"> + <div> [see comprosePreferredIdPButton </div> + [repeated] + </div> + + @return {Element} preferred IdP selection UI + */ + var buildPreferredIdPTile = function(parentDiv) { + + var preferredIdPs = getPreferredIdPs(); + if (0 === preferredIdPs.length) { + return false; + } + + var atLeastOneImg = doNotCollapse; + for(var i = 0 ; i < maxPreferredIdPs && i < preferredIdPs.length; i++){ + if (preferredIdPs[i] && getImageForIdP(preferredIdPs[i], false)) { + atLeastOneImg = true; + } + } + + var preferredIdPDIV; + if (atLeastOneImg) { + preferredIdPDIV = buildDiv('PreferredIdPTile'); + } else { + preferredIdPDIV = buildDiv('PreferredIdPTileNoImg'); + } + + + buildTextDiv(preferredIdPDIV, 'idpPreferred.label'); + + + for(var i = 0 ; i < maxPreferredIdPs && i < preferredIdPs.length; i++){ + if (preferredIdPs[i]) { + var button = composePreferredIdPButton(preferredIdPs[i],i, atLeastOneImg); + preferredIdPDIV.appendChild(button); + } + } + + parentDiv.appendChild(preferredIdPDIV); + return true; + }; + + /** + * Build the <form> from the return parameters + */ + + var buildSelectForm = function () + { + var form = document.createElement('form'); + idpEntryDiv.appendChild(form); + + form.action = returnBase; + form.method = 'GET'; + form.setAttribute('autocomplete', 'OFF'); + var i = 0; + for (i = 0; i < returnParms.length; i++) { + var hidden = document.createElement('input'); + hidden.setAttribute('type', 'hidden'); + hidden.name = returnParms[i][0]; + hidden.value= returnParms[i][1]; + form.appendChild(hidden); + } + + return form; + } ; + + + /** + Build the manual IdP Entry tile (bottom half of UI with + search-as-you-type field). + + <div id = prefix+"IdPEntryTile"> + <form> + <input type="text", id=prefix+"IdPSelectInput/> // select text box + <input type="hidden" /> param to send + <input type="submit" /> + + + @return {Element} IdP entry UI tile + */ + var buildIdPEntryTile = function(parentDiv, preferredTile) { + + + idpEntryDiv = buildDiv('IdPEntryTile'); + if (showListFirst) { + idpEntryDiv.style.display = 'none'; + } + + var label = document.createElement('label'); + label.setAttribute('for', idPrefix + 'Input'); + + if (preferredTile) { + buildTextDiv(label, 'idpEntry.label'); + } else { + buildTextDiv(label, 'idpEntry.NoPreferred.label'); + } + + var form = buildSelectForm(); + form.appendChild(label); + + var textInput = document.createElement('input'); + form.appendChild(textInput); + + textInput.type='text'; + setID(textInput, 'Input'); + + var hidden = document.createElement('input'); + hidden.setAttribute('type', 'hidden'); + form.appendChild(hidden); + + hidden.name = returnIDParam; + hidden.value='-'; + + var button = buildContinueButton('Select'); + button.disabled = true; + form.appendChild(button); + + form.onsubmit = function () { + // + // Make sure we cannot ask for garbage + // + if (null === hidden.value || 0 === hidden.value.length || '-' == hidden.value) { + return false; + } + // + // And always ask for the cookie to be updated before we continue + // + textInput.value = hidden.textValue; + selectIdP(hidden.value); + return true; + }; + + dropDownControl = new TypeAheadControl(idpData, textInput, hidden, button, maxIdPCharsDropDown, getLocalizedName, getEntityId, geticon, ie6Hack, alwaysShow, maxResults, getKeywords); + + var a = document.createElement('a'); + a.appendChild(document.createTextNode(getLocalizedMessage('idpList.showList'))); + a.href = '#'; + setClass(a, 'DropDownToggle'); + a.onclick = function() { + idpEntryDiv.style.display='none'; + setSelector(idpSelect, hidden.value); + idpListDiv.style.display=''; + listButton.focus(); + return false; + }; + idpEntryDiv.appendChild(a); + buildHelpText(idpEntryDiv); + + parentDiv.appendChild(idpEntryDiv); + }; + + /** + Builds the drop down list containing all the IdPs from which a + user may choose. + + <div id=prefix+"IdPListTile"> + <label for="idplist">idpList.label</label> + <form action="URL from IDP Data" method="GET"> + <select name="param from IdP data"> + <option value="EntityID">Localized Entity Name</option> + [...] + </select> + <input type="submit"/> + </div> + + @return {Element} IdP drop down selection UI tile + */ + var buildIdPDropDownListTile = function(parentDiv, preferredTile) { + idpListDiv = buildDiv('IdPListTile'); + if (!showListFirst) { + idpListDiv.style.display = 'none'; + } + + var label = document.createElement('label'); + label.setAttribute('for', idPrefix + 'Selector'); + + if (preferredTile) { + buildTextDiv(label, 'idpList.label'); + } else { + buildTextDiv(label, 'idpList.NoPreferred.label'); + } + + idpSelect = document.createElement('select'); + setID(idpSelect, 'Selector'); + idpSelect.name = returnIDParam; + idpListDiv.appendChild(idpSelect); + + var idpOption = buildSelectOption('-', getLocalizedMessage('idpList.defaultOptionLabel')); + idpOption.selected = true; + + idpSelect.appendChild(idpOption); + + var idp; + for(var i=0; i<idpData.length; i++){ + idp = idpData[i]; + idpOption = buildSelectOption(getEntityId(idp), getLocalizedName(idp)); + idpSelect.appendChild(idpOption); + } + + var form = buildSelectForm(); + form.appendChild(label); + form.appendChild(idpSelect); + + form.onsubmit = function () { + // + // The first entery isn't selectable + // + if (idpSelect.selectedIndex < 1) { + return false; + } + // + // otherwise update the cookie + // + selectIdP(idpSelect.options[idpSelect.selectedIndex].value); + return true; + }; + + var button = buildContinueButton('List'); + listButton = button; + form.appendChild(button); + + idpListDiv.appendChild(form); + + // + // The switcher + // + var a = document.createElement('a'); + a.appendChild(document.createTextNode(getLocalizedMessage('idpList.showSearch'))); + a.href = '#'; + setClass(a, 'DropDownToggle'); + a.onclick = function() { + idpEntryDiv.style.display=''; + idpListDiv.style.display='none'; + return false; + }; + idpListDiv.appendChild(a); + buildHelpText(idpListDiv); + + parentDiv.appendChild(idpListDiv); + }; + + var buildAutoDispatchPane = function(parent) { + var inputName = 'IdPSelectAutoDisp' + + autoDispatchTile = buildDiv(undefined, 'autoDispatchArea'); + + autoDispatchTile.appendChild(document.createTextNode(getLocalizedMessage('autoFollow.message'))); + // + // The "clear" button + // + var but = document.createElement('input'); + but.setAttribute('type', 'radio'); + but.setAttribute('checked', 'checked'); + but.setAttribute('name', inputName); + but.onclick = function () { + setAutoDispatchCookie(0); + } + + div = buildDiv(undefined, 'autoDispatchTile'); + div.appendChild(but); + div.appendChild(document.createTextNode(getLocalizedMessage('autoFollow.never'))); + autoDispatchTile.appendChild(div); + + var i; + for (i = 0; i < autoFollowCookieTTLs.length; i++) { + // + // The timed buttons + // + but = document.createElement('input'); + but.setAttribute('type', 'radio'); + but.setAttribute('name', inputName); + + but.life = autoFollowCookieTTLs[i]; + but.onclick = function () { + var f = this.life; + setAutoDispatchCookie(f); + } + + div = buildDiv(undefined, 'autoDispatchTile'); + div.appendChild(but); + div.appendChild(document.createTextNode( + getLocalizedMessage('autoFollow.time'+i))); + autoDispatchTile.appendChild(div); + } + + parent.appendChild(autoDispatchTile); + } + + /** + Builds the 'continue' button used to submit the IdP selection. + + @return {Element} HTML button used to submit the IdP selection + */ + var buildContinueButton = function(which) { + var button = document.createElement('input'); + button.setAttribute('type', 'submit'); + button.value = getLocalizedMessage('submitButton.label'); + setID(button, which + 'Button'); + + return button; + }; + + /** + Builds an aref to point to the helpURL + */ + + var buildHelpText = function(containerDiv) { + var aval = document.createElement('a'); + aval.href = helpURL; + aval.appendChild(document.createTextNode(getLocalizedMessage('helpText'))); + setClass(aval, 'HelpButton'); + containerDiv.appendChild(aval); + } ; + + /** + Creates a div element whose id attribute is set to the given ID. + + @param {String} id ID for the created div element + @param {String} [class] class of the created div element + @return {Element} DOM 'div' element with an 'id' attribute + */ + var buildDiv = function(id, whichClass){ + var div = document.createElement('div'); + if (undefined !== id) { + setID(div, id); + } + if(undefined !== whichClass) { + + setClass(div, whichClass); + } + return div; + }; + + /** + Builds an HTML select option element + + @param {String} value value of the option when selected + @param {String} label displayed label of the option + */ + var buildSelectOption = function(value, text){ + var option = document.createElement('option'); + option.value = value; + if (text.length > maxIdPCharsDropDown) { + text = text.substring(0, maxIdPCharsDropDown); + } + option.appendChild(document.createTextNode(text)); + return option; + }; + + /** + Sets the attribute 'id' on the provided object + We do it through this function so we have a single + point where we can prepend a value + + @param (Object) The [DOM] Object we want to set the attribute on + @param (String) The Id we want to set + */ + + var setID = function(obj, name) { + obj.id = idPrefix + name; + }; + + var setClass = function(obj, name) { + obj.setAttribute('class', classPrefix + name); + }; + + /** + Returns the DOM object with the specified id. We abstract + through a function to allow us to prepend to the name + + @param (String) the (unprepended) id we want + */ + var locateElement = function(name) { + return document.getElementById(idPrefix + name); + }; + + // ************************************* + // Private functions + // + // GUI actions. Note that there is an element of closure going on + // here since these names are invisible outside this module. + // + // + // ************************************* + + /** + * Base helper function for when an IdP is selected + * @param (String) The UN-encoded entityID of the IdP + */ + + var selectIdP = function(idP) { + updateSelectedIdPs(idP); + saveUserSelectedIdPs(userSelectedIdPs); + }; + + // ************************************* + // Private functions + // + // Localization handling + // + // ************************************* + + /** + Gets a localized string from the given language pack. This + method uses the {@link langBundles} given during construction + time. + + @param {String} messageId ID of the message to retrieve + + @return (String) the message + */ + var getLocalizedMessage = function(messageId){ + + var message = langBundle[messageId]; + if(!message){ + message = defaultLangBundle[messageId]; + } + if(!message){ + message = 'Missing message for ' + messageId; + } + + return message; + }; + + var getEntityId = function(idp) { + return idp.entityID; + }; + + /** + Returns the icon information for the provided idp + + @param (Object) an idp. This should have an array 'names' with sub + elements 'lang' and 'name'. + + @return (String) The localized name + */ + var geticon = function(idp) { + var i; + + if (null == idp.Logos) { + return null; + } + for (i =0; i < idp.Logos.length; i++) { + var logo = idp.Logos[i]; + + if (logo.height == "16" && logo.width == "16") { + if (null == logo.lang || + lang == logo.lang || + (typeof majorLang != 'undefined' && majorLang == logo.lang) || + defaultLang == logo.lang) { + return logo.value; + } + } + } + + return null; + } ; + + /** + Returns the localized name information for the provided idp + + @param (Object) an idp. This should have an array 'names' with sub + elements 'lang' and 'name'. + + @return (String) The localized name + */ + var getLocalizedName = function(idp) { + var res = getLocalizedEntry(idp.DisplayNames); + if (null !== res) { + return res; + } + debug('No Name entry in any language for ' + getEntityId(idp)); + return getEntityId(idp); + } ; + + var getKeywords = function(idp) { + if (ignoreKeywords || null == idp.Keywords) { + return null; + } + var s = getLocalizedEntry(idp.Keywords); + + return s; + } + + var getLocalizedEntry = function(theArray){ + var i; + + // + // try by full name + // + for (i in theArray) { + if (theArray[i].lang == lang) { + return theArray[i].value; + } + } + // + // then by major language + // + if (typeof majorLang != 'undefined') { + for (i in theArray) { + if (theArray[i].lang == majorLang) { + return theArray[i].value; + } + } + } + // + // then by null language in metadata + // + for (i in theArray) { + if (theArray[i].lang == null) { + return theArray[i].value; + } + } + + // + // then by default language + // + for (i in theArray) { + if (theArray[i].lang == defaultLang) { + return theArray[i].value; + } + } + + return null; + }; + + + // ************************************* + // Private functions + // + // Cookie and preferred IdP Handling + // + // ************************************* + + /** + Gets the preferred IdPs. The first elements in the array will + be the preselected preferred IdPs. The following elements will + be those past IdPs selected by a user. The size of the array + will be no larger than the maximum number of preferred IdPs. + */ + var getPreferredIdPs = function() { + var idps = []; + var offset = 0; + var i; + var j; + + // + // populate start of array with preselected IdPs + // + if(null != preferredIdP){ + for(i=0; i < preferredIdP.length && i < maxPreferredIdPs; i++){ + idps[i] = getIdPFor(preferredIdP[i]); + offset++; + } + } + + // + // And then the cookie based ones + // + userSelectedIdPs = retrieveUserSelectedIdPs(); + for (i = offset, j=0; j < userSelectedIdPs.length && i < maxPreferredIdPs; j++){ + var cur_idp = getIdPFor(userSelectedIdPs[j]); + if (typeof idps.indexOf === 'undefined') { + idps.push(cur_idp); + i++; + } + else if (idps.indexOf(cur_idp) === -1) { + idps.push(cur_idp); + i++; + } + } + return idps; + }; + + /** + Update the userSelectedIdPs list with the new value. + + @param (String) the newly selected IdP + */ + var updateSelectedIdPs = function(newIdP) { + + // + // We cannot use split since it does not appear to + // work as per spec on ie8. + // + var newList = []; + + // + // iterate through the list copying everything but the old + // name + // + while (0 !== userSelectedIdPs.length) { + var what = userSelectedIdPs.pop(); + if (what != newIdP) { + newList.unshift(what); + } + } + + // + // And shove it in at the top + // + newList.unshift(newIdP); + userSelectedIdPs = newList; + return; + }; + + /* + Set the autoFollowCookie with a life of the specified number of days + or clear it. + + @parm (integer) days The cookie lifetime if >0. If <=0 clear cookie + + */ + + var setAutoDispatchCookie = function(days) { + var expireDate; + if(days > 0){ + var now = new Date(); + cookieTTL = days * 24 * 60 * 60 * 1000; + expireDate = new Date(now.getTime() + cookieTTL); + } else { + expireDate = new Date(0); + } + document.cookie=autoFollowCookie + '=1;path=/;expires=' + expireDate.toUTCString(); + } + + /** + Gets the value of the cookie with the provided name + + @param (string) name - the name to look for + @return the value or null if no cookie of that name + */ + + var getCookieCalled = function (name) { + + var i, j; + var cookies; + + cookies = document.cookie.split( ';' ); + for (i = 0; i < cookies.length; i++) { + // + // Do not use split('='), '=' is valid in Base64 encoding! + // + var cookie = cookies[i]; + var splitPoint = cookie.indexOf( '=' ); + var cookieName = cookie.substring(0, splitPoint); + + if ( name == ( cookieName.replace(/^\s+|\s+$/g, ''))) { + return cookie.substring(splitPoint+1); + } + } + return null; + } + + /** + Gets the IdP previously selected by the user. + + @return {Array} user selected IdPs identified by their entity ID + */ + var retrieveUserSelectedIdPs = function(){ + var userSelectedIdPs = []; + var j; + + var cookieValues = getCookieCalled( '_saml_idp' ); + + if ( cookieValues != null) { + cookieValues = cookieValues.replace(/^\s+|\s+$/g, ''); + cookieValues = cookieValues.replace('+','%20'); + cookieValues = cookieValues.split('%20'); + for(j=cookieValues.length; j > 0; j--){ + if (0 === cookieValues[j-1].length) { + continue; + } + var dec = base64Decode(decodeURIComponent(cookieValues[j-1])); + if (dec.length > 0) { + userSelectedIdPs.push(dec); + } + } + } + + return userSelectedIdPs; + }; + + /** + Saves the IdPs selected by the user. + + @param {Array} idps idps selected by the user + */ + var saveUserSelectedIdPs = function(idps){ + var cookieData = []; + var length = idps.length; + + if (noWriteCookie) { + return; + } + + if (length > 5) { + length = 5; + } + for(var i=length; i > 0; i--){ + if (idps[i-1].length > 0) { + cookieData.push(encodeURIComponent(base64Encode(idps[i-1]))); + } + } + + var expireDate = null; + if(samlIdPCookieTTL){ + var now = new Date(); + cookieTTL = samlIdPCookieTTL * 24 * 60 * 60 * 1000; + expireDate = new Date(now.getTime() + cookieTTL); + } + + document.cookie='_saml_idp' + '=' + cookieData.join('%20') + '; path = /' + + ((expireDate===null) ? '' : '; expires=' + expireDate.toUTCString()); + + }; + + /** + Base64 encodes the given string. + + @param {String} input string to be encoded + + @return {String} base64 encoded string + */ + var base64Encode = function(input) { + var output = '', c1, c2, c3, e1, e2, e3, e4; + + for ( var i = 0; i < input.length; ) { + c1 = input.charCodeAt(i++); + c2 = input.charCodeAt(i++); + c3 = input.charCodeAt(i++); + e1 = c1 >> 2; + e2 = ((c1 & 3) << 4) + (c2 >> 4); + e3 = ((c2 & 15) << 2) + (c3 >> 6); + e4 = c3 & 63; + if (isNaN(c2)){ + e3 = e4 = 64; + } else if (isNaN(c3)){ + e4 = 64; + } + output += base64chars.charAt(e1) + + base64chars.charAt(e2) + + base64chars.charAt(e3) + + base64chars.charAt(e4); + } + + return output; + }; + + /** + Base64 decodes the given string. + + @param {String} input string to be decoded + + @return {String} base64 decoded string + */ + var base64Decode = function(input) { + var output = '', chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + // Remove all characters that are not A-Z, a-z, 0-9, +, /, or = + var base64test = /[^A-Za-z0-9\+\/\=]/g; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + + do { + enc1 = base64chars.indexOf(input.charAt(i++)); + enc2 = base64chars.indexOf(input.charAt(i++)); + enc3 = base64chars.indexOf(input.charAt(i++)); + enc4 = base64chars.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + chr1 = chr2 = chr3 = ''; + enc1 = enc2 = enc3 = enc4 = ''; + + } while (i < input.length); + + return output; + }; + + // ************************************* + // Private functions + // + // Error Handling. we'll keep it separate with a view to eventual + // exbedding into log4js + // + // ************************************* + /** + + */ + + var fatal = function(message) { + alert('FATAL - DISCO UI:' + message); + var txt = document.createTextNode(message); + idpSelectDiv.appendChild(txt); + }; + + var debug = function() { + // + // Nothing + }; +} + +(new IdPSelectUI()).draw(new IdPSelectUIParms()); diff --git a/docker/shibboleth-ds/nonminimised/idpselect_config.js b/docker/shibboleth-ds/nonminimised/idpselect_config.js new file mode 100644 index 0000000000000000000000000000000000000000..90ac048c536797701a3bed7a4c12069eac653df9 --- /dev/null +++ b/docker/shibboleth-ds/nonminimised/idpselect_config.js @@ -0,0 +1,80 @@ + +/** @class IdP Selector UI */ +function IdPSelectUIParms(){ + // + // Adjust the following to fit into your local configuration + // + this.alwaysShow = true; // If true, this will show results as soon as you start typing + this.dataSource = '/Shibboleth.sso/DiscoFeed'; // Where to get the data from + this.defaultLanguage = 'en'; // Language to use if the browser local doesnt have a bundle + this.defaultLogo = 'blank.gif'; // Replace with your own logo + this.defaultLogoWidth = 1; + this.defaultLogoHeight = 1 ; + this.defaultReturn = null; // If non null, then the default place to send users who are not + // Approaching via the Discovery Protocol for example + //this.defaultReturn = "https://example.org/Shibboleth.sso/DS?SAMLDS=1&target=https://example.org/secure"; + this.defaultReturnIDParam = null; + this.helpURL = 'https://wiki.shibboleth.net/confluence/display/SHIB2/DSRoadmap'; + this.ie6Hack = null; // An array of structures to disable when drawing the pull down (needed to + // handle the ie6 z axis problem + this.insertAtDiv = 'idpSelect'; // The div where we will insert the data + this.maxResults = 10; // How many results to show at once or the number at which to + // start showing if alwaysShow is false + this.myEntityID = null; // If non null then this string must match the string provided in the DS parms + this.preferredIdP = null; // Array of entityIds to always show + this.hiddenIdPs = null; // Array of entityIds to delete + this.ignoreKeywords = false; // Do we ignore the <mdui:Keywords/> when looking for candidates + this.showListFirst = false; // Do we start with a list of IdPs or just the dropdown + this.samlIdPCookieTTL = 730; // in days + this.setFocusTextBox = true; // Set to false to supress focus + this.testGUI = false; + + this.autoFollowCookie = null; // If you want auto-dispatch, set this to the cookie name to use + this.autoFollowCookieTTLs = [ 1, 60, 270 ]; // Cookie life (in days). Changing this requires changes to idp_select_languages + + // + // Language support. + // + // The minified source provides "en", "de", "pt-br" and "jp". + // + // Override any of these below, or provide your own language + // + //this.langBundles = { + //'en': { + // 'fatal.divMissing': '<div> specified as "insertAtDiv" could not be located in the HTML', + // 'fatal.noXMLHttpRequest': 'Browser does not support XMLHttpRequest, unable to load IdP selection data', + // 'fatal.wrongProtocol' : 'Policy supplied to DS was not "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"', + // 'fatal.wrongEntityId' : 'entityId supplied by SP did not match configuration', + // 'fatal.noData' : 'Metadata download returned no data', + // 'fatal.loadFailed': 'Failed to download metadata from ', + // 'fatal.noparms' : 'No parameters to discovery session and no defaultReturn parameter configured', + // 'fatal.noReturnURL' : "No URL return parameter provided", + // 'fatal.badProtocol' : "Return request must start with https:// or http://", + // 'idpPreferred.label': 'Use a suggested selection:', + // 'idpEntry.label': 'Or enter your organization\'s name', + // 'idpEntry.NoPreferred.label': 'Enter your organization\'s name', + // 'idpList.label': 'Or select your organization from the list below', + // 'idpList.NoPreferred.label': 'Select your organization from the list below', + // 'idpList.defaultOptionLabel': 'Please select your organization...', + // 'idpList.showList' : 'Allow me to pick from a list', + // 'idpList.showSearch' : 'Allow me to specify the site', + // 'submitButton.label': 'Continue', + // 'helpText': 'Help', + // 'defaultLogoAlt' : 'DefaultLogo' + //} + //}; + + // + // The following should not be changed without changes to the css. Consider them as mandatory defaults + // + this.maxPreferredIdPs = 3; + this.maxIdPCharsButton = 33; + this.maxIdPCharsDropDown = 58; + this.maxIdPCharsAltTxt = 60; + + this.minWidth = 20; + this.minHeight = 20; + this.maxWidth = 115; + this.maxHeight = 69; + this.bestRatio = Math.log(80 / 60); +} diff --git a/docker/shibboleth-ds/nonminimised/idpselect_languages.js b/docker/shibboleth-ds/nonminimised/idpselect_languages.js new file mode 100644 index 0000000000000000000000000000000000000000..6041f062cc65aaa2c75a197154fba58b3c5caaef --- /dev/null +++ b/docker/shibboleth-ds/nonminimised/idpselect_languages.js @@ -0,0 +1,106 @@ + +/** @class IdP Selector UI */ +function IdPSelectLanguages(){ + // + // Globalization stuff + // + this.langBundles = { + 'en': { + 'fatal.divMissing': '<div> specified as "insertAtDiv" could not be located in the HTML', + 'fatal.noXMLHttpRequest': 'Browser does not support XMLHttpRequest, unable to load IdP selection data', + 'fatal.wrongProtocol' : 'Policy supplied to DS was not "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"', + 'fatal.wrongEntityId' : 'entityId supplied by SP did not match configuration', + 'fatal.noData' : 'Metadata download returned no data', + 'fatal.loadFailed': 'Failed to download metadata from ', + 'fatal.noparms' : 'No parameters to discovery session and no defaultReturn parameter configured', + 'fatal.noReturnURL' : "No URL return parameter provided", + 'fatal.badProtocol' : "Return request must start with https:// or http://", + 'idpPreferred.label': 'Use a suggested selection:', + 'idpEntry.label': 'Or enter your organization\'s name', + 'idpEntry.NoPreferred.label': 'Enter your organization\'s name', + 'idpList.label': 'Or select your organization from the list below', + 'idpList.NoPreferred.label': 'Select your organization from the list below', + 'idpList.defaultOptionLabel': 'Please select your organization...', + 'idpList.showList' : 'Allow me to pick from a list', + 'idpList.showSearch' : 'Allow me to specify the site', + 'submitButton.label': 'Continue', + 'helpText': 'Help', + 'defaultLogoAlt' : 'DefaultLogo', + 'autoFollow.message' : 'Always follows this selection', + 'autoFollow.never' : 'Never', + 'autoFollow.time0' : 'One day', + 'autoFollow.time1' : '3 months', + 'autoFollow.time2' : '9 months' + }, + 'de': { + 'fatal.divMissing': 'Das notwendige Div Element fehlt', + 'fatal.noXMLHttpRequest': 'Ihr Webbrowser unterst\u00fctzt keine XMLHttpRequests, IdP-Auswahl kann nicht geladen werden', + 'fatal.wrongProtocol' : 'DS bekam eine andere Policy als "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"', + 'fatal.wrongEntityId' : 'Die entityId ist nicht korrekt', + 'fatal.loadFailed': 'Metadaten konnten nicht heruntergeladen werden: ', + 'fatal.noparms' : 'Parameter f\u00fcr das Discovery Service oder \'defaultReturn\' fehlen', + 'fatal.noReturnURL' : "URL return Parmeter fehlt", + 'fatal.badProtocol' : "return Request muss mit https:// oder http:// beginnen", + 'idpPreferred.label': 'Vorherige Auswahl:', + 'idpEntry.label': 'Oder geben Sie den Namen (oder Teile davon) an:', + 'idpEntry.NoPreferred.label': 'Namen (oder Teile davon) der Institution angeben:', + 'idpList.label': 'Oder w\u00e4hlen Sie Ihre Institution aus einer Liste:', + 'idpList.NoPreferred.label': 'Institution aus folgender Liste w\u00e4hlen:', + 'idpList.defaultOptionLabel': 'W\u00e4hlen Sie Ihre Institution aus...', + 'idpList.showList' : 'Institution aus einer Liste w\u00e4hlen', + 'idpList.showSearch' : 'Institution selbst angeben', + 'submitButton.label': 'OK', + 'helpText': 'Hilfe', + 'defaultLogoAlt' : 'Standard logo' + }, + 'ja': { + 'fatal.divMissing': '"insertAtDiv" ã® ID ã‚’æŒã¤ <div> ㌠HTML ä¸ã«å˜åœ¨ã—ã¾ã›ã‚“', + 'fatal.noXMLHttpRequest': 'ブラウザ㌠XMLHttpRequest をサãƒãƒ¼ãƒˆã—ã¦ã„ãªã„ã®ã§ IdP æƒ…å ±ã‚’å–å¾—ã§ãã¾ã›ã‚“', + 'fatal.wrongProtocol' : 'DSã¸æ¸¡ã•ã‚ŒãŸ Policy パラメータ㌠"urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single" ã§ã¯ã‚ã‚Šã¾ã›ã‚“', + 'fatal.wrongEntityId' : 'SP ã‹ã‚‰æ¸¡ã•ã‚ŒãŸ entityId ãŒè¨å®šå€¤ã¨ç•°ãªã‚Šã¾ã™', + 'fatal.noData' : 'メタデータãŒç©ºã§ã™', + 'fatal.loadFailed': '次㮠URL ã‹ã‚‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã‚’ダウンãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ: ', + 'fatal.noparms' : 'DSã«ãƒ‘ラメータãŒæ¸¡ã•ã‚Œã¦ãŠã‚‰ãš defaultReturn ã‚‚è¨å®šã•ã‚Œã¦ã„ã¾ã›ã‚“', + 'fatal.noReturnURL' : "戻り URL ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã›ã‚“", + 'fatal.badProtocol' : "戻り URL 㯠https:// ã‹ http:// ã§å§‹ã¾ã‚‰ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“", + 'idpPreferred.label': 'é¸æŠžå€™è£œã® IdP:', + 'idpEntry.label': 'ã‚‚ã—ãã¯ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢åを入力ã—ã¦ãã ã•ã„', + 'idpEntry.NoPreferred.label': 'ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢åを入力ã—ã¦ãã ã•ã„', + 'idpList.label': 'ã‚‚ã—ãã¯ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢ã‚’é¸æŠžã—ã¦ãã ã•ã„', + 'idpList.NoPreferred.label': 'ã‚ãªãŸã®æ‰€å±žæ©Ÿé–¢ã‚’一覧ã‹ã‚‰é¸æŠžã—ã¦ãã ã•ã„', + 'idpList.defaultOptionLabel': '所属機関をé¸æŠžã—ã¦ãã ã•ã„...', + 'idpList.showList' : '一覧ã‹ã‚‰é¸æŠžã™ã‚‹', + 'idpList.showSearch' : 'æ©Ÿé–¢åを入力ã™ã‚‹', + 'submitButton.label': 'é¸æŠž', + 'autoFollow.message' : '次ã®æœŸé–“é¸æŠžã—ãŸæ©Ÿé–¢ã«è‡ªå‹•çš„ã«é·ç§»ã™ã‚‹:', + 'autoFollow.never' : '自動é·ç§»ã—ãªã„', + 'autoFollow.time0' : '1æ—¥', + 'autoFollow.time1' : '3ã‹æœˆ', + 'autoFollow.time2' : '9ã‹æœˆ', + 'helpText': 'Help', + 'defaultLogoAlt' : 'DefaultLogo' + }, + 'pt-br': { + 'fatal.divMissing': 'A tag <div> com "insertAtDiv" não foi encontrada no arquivo HTML', + 'fatal.noXMLHttpRequest': 'Seu navegador não suporta "XMLHttpRequest", impossÃvel de carregador os dados do IdP selecionado', + 'fatal.wrongProtocol' : 'A polÃtica "Policy" fornecida para o DS não foi "urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"', + 'fatal.wrongEntityId' : 'entityId oferecido pelo SP não confere com o da configuração', + 'fatal.noData' : 'O arquivo de metadados não retornou nada;', + 'fatal.loadFailed': 'Falhou ao realizar download do metadado de ', + 'fatal.noparms' : 'Sem parâmetros para sessão de descoberta e sem parâmetro "defaultReturn" configurado', + 'fatal.noReturnURL' : "Não foi definida um endereço (URL) de retorno no parâmetro", + 'fatal.badProtocol' : "Retorno do endereço requisitado deve começar com https:// ou http://", + 'idpPreferred.label': 'Use estas Instituições sugeridas: ', + 'idpEntry.label': 'Ou informe o nome da sua Instituição', + 'idpEntry.NoPreferred.label': 'Informe o nome da sua Instituição', + 'idpList.label': 'Ou selecione sua Instituição através da lista abaixo', + 'idpList.NoPreferred.label': 'Selecione sua Instituição através da lista abaixo', + 'idpList.defaultOptionLabel': 'Por favor, selecione sua Instituição: ', + 'idpList.showList' : 'Permitir que eu escolha um IdP através de uma lista', + 'idpList.showSearch' : 'Permitir que eu especifique o IdP', + 'submitButton.label': 'Continuar ', + 'helpText': 'Ajuda', + 'defaultLogoAlt' : 'Logo padrão' + } + }; +} diff --git a/docker/shibboleth-ds/nonminimised/json2.js b/docker/shibboleth-ds/nonminimised/json2.js new file mode 100644 index 0000000000000000000000000000000000000000..5e61c56fc67fcce0e3cf4de8595cb77b1dc73164 --- /dev/null +++ b/docker/shibboleth-ds/nonminimised/json2.js @@ -0,0 +1,481 @@ +/* + http://www.JSON.org/json2.js + 2011-02-23 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false, regexp: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + "use strict"; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); + diff --git a/docker/shibboleth-ds/nonminimised/typeahead.js b/docker/shibboleth-ds/nonminimised/typeahead.js new file mode 100644 index 0000000000000000000000000000000000000000..329ecd82425c3385514ccf974799ac120ea30711 --- /dev/null +++ b/docker/shibboleth-ds/nonminimised/typeahead.js @@ -0,0 +1,426 @@ +function TypeAheadControl(jsonObj, box, orig, submit, maxchars, getName, getEntityId, geticon, ie6hack, alwaysShow, maxResults, getKeywords) +{ + // + // Squirrel away the parameters we were given + // + this.elementList = jsonObj; + this.textBox = box; + this.origin = orig; + this.submit = submit; + this.results = 0; + this.alwaysShow = alwaysShow; + this.maxResults = maxResults; + this.ie6hack = ie6hack; + this.maxchars = maxchars; + this.getName = getName; + this.getEntityId = getEntityId; + this.geticon = geticon; + this.getKeywords = getKeywords; +} + +TypeAheadControl.prototype.draw = function(setFocus) { + + // + // Make a closure on this so that the embedded functions + // get access to it. + // + var myThis = this; + + + // + // Set up the 'dropDown' + // + this.dropDown = document.createElement('ul'); + this.dropDown.className = 'IdPSelectDropDown'; + this.dropDown.style.visibility = 'hidden'; + + this.dropDown.style.width = this.textBox.offsetWidth; + this.dropDown.current = -1; + this.textBox.setAttribute('role', 'listbox'); + document.body.appendChild(this.dropDown); + + // + // Set ARIA on the input + // + this.textBox.setAttribute('role', 'combobox'); + this.textBox.setAttribute('aria-controls', 'IdPSelectDropDown'); + this.textBox.setAttribute('aria-owns', 'IdPSelectDropDown'); + + // + // mouse listeners for the dropdown box + // + this.dropDown.onmouseover = function(event) { + if (!event) { + event = window.event; + } + var target; + if (event.target){ + target = event.target; + } + if (typeof target == 'undefined') { + target = event.srcElement; + } + myThis.select(target); + }; + + this.dropDown.onmousedown = function(event) { + if (-1 != myThis.dropDown.current) { + myThis.textBox.value = myThis.results[myThis.dropDown.current][0]; + } + }; + + // + // Add the listeners to the text box + // + this.textBox.onkeyup = function(event) { + // + // get window event if needed (because of browser oddities) + // + if (!event) { + event = window.event; + } + myThis.handleKeyUp(event); + }; + + this.textBox.onkeydown = function(event) { + if (!event) { + event = window.event; + } + + myThis.handleKeyDown(event); + }; + + this.textBox.onblur = function() { + myThis.hideDrop(); + }; + + this.textBox.onfocus = function() { + myThis.handleChange(); + }; + + if (null == setFocus || setFocus) { + this.textBox.focus(); + } +}; + +// +// Given a name return the first maxresults, or all possibles +// +TypeAheadControl.prototype.getPossible = function(name) { + var possibles = []; + var inIndex = 0; + var outIndex = 0; + var strIndex = 0; + var str; + var ostr; + + name = name.toLowerCase(); + + while (outIndex <= this.maxResults && inIndex < this.elementList.length) { + var hit = false; + var thisName = this.getName(this.elementList[inIndex]); + + // + // Check name + // + if (thisName.toLowerCase().indexOf(name) != -1) { + hit = true; + } + // + // Check entityID + // + if (!hit && this.getEntityId(this.elementList[inIndex]).toLowerCase().indexOf(name) != -1) { + hit = true; + } + + if (!hit) { + var thisKeywords = this.getKeywords(this.elementList[inIndex]); + if (null != thisKeywords && + thisKeywords.toLowerCase().indexOf(name) != -1) { + hit = true; + } + } + + if (hit) { + possibles[outIndex] = [thisName, this.getEntityId(this.elementList[inIndex]), this.geticon(this.elementList[inIndex])]; + outIndex ++; + } + + inIndex ++; + } + // + // reset the cursor to the top + // + this.dropDown.current = -1; + + return possibles; +}; + +TypeAheadControl.prototype.handleKeyUp = function(event) { + var key = event.keyCode; + + if (27 == key) { + // + // Escape - clear + // + this.textBox.value = ''; + this.handleChange(); + } else if (8 == key || 32 == key || (key >= 46 && key < 112) || key > 123) { + // + // Backspace, Space and >=Del to <F1 and > F12 + // + this.handleChange(); + } +}; + +TypeAheadControl.prototype.handleKeyDown = function(event) { + + var key = event.keyCode; + + if (38 == key) { + // + // up arrow + // + this.upSelect(); + + } else if (40 == key) { + // + // down arrow + // + this.downSelect(); + } +}; + +TypeAheadControl.prototype.hideDrop = function() { + var i = 0; + if (null !== this.ie6hack) { + while (i < this.ie6hack.length) { + this.ie6hack[i].style.visibility = 'visible'; + i++; + } + } + this.dropDown.style.visibility = 'hidden'; + this.textBox.setAttribute('aria-expanded', 'false'); + + + if (-1 == this.dropDown.current) { + this.doUnselected(); + } +}; + +TypeAheadControl.prototype.showDrop = function() { + var i = 0; + if (null !== this.ie6hack) { + while (i < this.ie6hack.length) { + this.ie6hack[i].style.visibility = 'hidden'; + i++; + } + } + this.dropDown.style.visibility = 'visible'; + this.dropDown.style.width = this.textBox.offsetWidth +"px"; + this.textBox.setAttribute('aria-expanded', 'true'); +}; + + +TypeAheadControl.prototype.doSelected = function() { + this.submit.disabled = false; +}; + +TypeAheadControl.prototype.doUnselected = function() { + this.submit.disabled = true; + this.textBox.setAttribute('aria-activedescendant', ''); +}; + +TypeAheadControl.prototype.handleChange = function() { + + var val = this.textBox.value; + var res = this.getPossible(val); + + + if (0 === val.length || + 0 === res.length || + (!this.alwaysShow && this.maxResults < res.length)) { + this.hideDrop(); + this.doUnselected(); + this.results = []; + this.dropDown.current = -1; + } else { + this.results = res; + this.populateDropDown(res); + if (1 == res.length) { + this.select(this.dropDown.childNodes[0]); + this.doSelected(); + } else { + this.doUnselected(); + } + } +}; + +// +// A lot of the stuff below comes from +// http://www.webreference.com/programming/javascript/ncz/column2 +// +// With thanks to Nicholas C Zakas +// +TypeAheadControl.prototype.populateDropDown = function(list) { + this.dropDown.innerHTML = ''; + var i = 0; + var li; + var img; + var str; + + while (i < list.length) { + li = document.createElement('li'); + li.id='IdPSelectOption' + i; + str = list[i][0]; + + if (null !== list[i][2]) { + + img = document.createElement('img'); + img.src = list[i][2]; + img.width = 16; + img.height = 16; + img.alt = ''; + li.appendChild(img); + // + // trim string back further in this case + // + if (str.length > this.maxchars - 2) { + str = str.substring(0, this.maxchars - 2); + } + str = ' ' + str; + } else { + if (str.length > this.maxchars) { + str = str.substring(0, this.maxchars); + } + } + li.appendChild(document.createTextNode(str)); + li.setAttribute('role', 'option'); + this.dropDown.appendChild(li); + i++; + } + var off = this.getXY(); + this.dropDown.style.left = off[0] + 'px'; + this.dropDown.style.top = off[1] + 'px'; + this.showDrop(); +}; + +TypeAheadControl.prototype.getXY = function() { + + var node = this.textBox; + var sumX = 0; + var sumY = node.offsetHeight; + + while(node.tagName != 'BODY') { + sumX += node.offsetLeft; + sumY += node.offsetTop; + node = node.offsetParent; + } + // + // And add in the offset for the Body + // + sumX += node.offsetLeft; + sumY += node.offsetTop; + + return [sumX, sumY]; +}; + +TypeAheadControl.prototype.select = function(selected) { + var i = 0; + var node; + this.dropDown.current = -1; + this.doUnselected(); + while (i < this.dropDown.childNodes.length) { + node = this.dropDown.childNodes[i]; + if (node == selected) { + // + // Highlight it + // + node.className = 'IdPSelectCurrent'; + node.setAttribute('aria-selected', 'true'); + this.textBox.setAttribute('aria-activedescendant', 'IdPSelectOption' + i); + + // + // turn on the button + // + this.doSelected(); + // + // setup the cursor + // + this.dropDown.current = i; + // + // and the value for the Server + // + this.origin.value = this.results[i][1]; + this.origin.textValue = this.results[i][0]; + } else { + node.setAttribute('aria-selected', 'false'); + node.className = ''; + } + i++; + } + this.textBox.focus(); +}; + +TypeAheadControl.prototype.downSelect = function() { + if (this.results.length > 0) { + + if (-1 == this.dropDown.current) { + // + // mimic a select() + // + this.dropDown.current = 0; + this.dropDown.childNodes[0].className = 'IdPSelectCurrent'; + this.dropDown.childNodes[0].setAttribute('aria-selected', 'true'); + this.textBox.setAttribute('aria-activedescendant', 'IdPSelectOption' + 0); + this.doSelected(); + this.origin.value = this.results[0][1]; + this.origin.textValue = this.results[0][0]; + + } else if (this.dropDown.current < (this.results.length-1)) { + // + // turn off highlight + // + this.dropDown.childNodes[this.dropDown.current].className = ''; + // + // move cursor + // + this.dropDown.current++; + // + // and 'select' + // + this.dropDown.childNodes[this.dropDown.current].className = 'IdPSelectCurrent'; + this.dropDown.childNodes[this.dropDown.current].setAttribute('aria-selected', 'true'); + this.textBox.setAttribute('aria-activedescendant', 'IdPSelectOption' + this.dropDown.current); + this.doSelected(); + this.origin.value = this.results[this.dropDown.current][1]; + this.origin.textValue = this.results[this.dropDown.current][0]; + + } + } +}; + + +TypeAheadControl.prototype.upSelect = function() { + if ((this.results.length > 0) && + (this.dropDown.current > 0)) { + + // + // turn off highlight + // + this.dropDown.childNodes[this.dropDown.current].className = ''; + // + // move cursor + // + this.dropDown.current--; + // + // and 'select' + // + this.dropDown.childNodes[this.dropDown.current].className = 'IdPSelectCurrent'; + this.dropDown.childNodes[this.dropDown.current].setAttribute('aria-selected', 'true'); + this.textBox.setAttribute('aria-activedescendant', 'IdPSelectOption' + this.dropDown.current); + this.doSelected(); + this.origin.value = this.results[this.dropDown.current][1]; + this.origin.textValue = this.results[this.dropDown.current][0]; + } +}; diff --git a/docker/shibboleth-ds/shibboleth-ds.conf b/docker/shibboleth-ds/shibboleth-ds.conf new file mode 100644 index 0000000000000000000000000000000000000000..fd46068ce90086074e80bd42497305c884c1b791 --- /dev/null +++ b/docker/shibboleth-ds/shibboleth-ds.conf @@ -0,0 +1,17 @@ +# Basic Apache configuration + +<IfModule mod_alias.c> + <Location /shibboleth-ds> + Allow from all + <IfModule mod_shib.c> + AuthType shibboleth + ShibRequestSetting requireSession false + require shibboleth + </IfModule> + </Location> + Alias /shibboleth-ds/idpselect_config.js /etc/shibboleth-ds/idpselect_config.js + Alias /shibboleth-ds/idpselect.js /etc/shibboleth-ds/idpselect.js + Alias /shibboleth-ds/idpselect.css /etc/shibboleth-ds/idpselect.css + Alias /shibboleth-ds/index.html /etc/shibboleth-ds/index.html + Alias /shibboleth-ds/blank.gif /etc/shibboleth-ds/blank.gif +</IfModule> diff --git a/docker/shibboleth-ds/shibboleth-embedded-ds.spec b/docker/shibboleth-ds/shibboleth-embedded-ds.spec new file mode 100644 index 0000000000000000000000000000000000000000..d6d50ab9574ca20f2af9e3fcb80cc6b53492cbac --- /dev/null +++ b/docker/shibboleth-ds/shibboleth-embedded-ds.spec @@ -0,0 +1,106 @@ +Name: shibboleth-embedded-ds +Version: 1.2.0 +Release: 1 +Summary: Client-side federation discovery service for SAML-based SSO +Group: Productivity/Networking/Security +Vendor: Shibboleth Consortium +License: Apache-2.0 +URL: http://shibboleth.net/ +Source: %{name}-%{version}.tar.gz +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-root +%if "%{_vendor}" == "redhat" +BuildRequires: redhat-rpm-config +%{!?_without_builtinapache:BuildRequires: httpd} +%endif +%if "%{_vendor}" == "suse" +%{!?_without_builtinapache:BuildRequires: apache2} +%endif + +%description +The Embedded Discovery Service is a JS/CSS/HTML-based tool for +identity provider selection in conjunction with SAML-based web +single sign-on implementations such as Shibboleth. + +%prep +%setup -q + +%build + + +%install +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +# Plug the DS into the built-in Apache on a recognized system. +touch rpm.filelist +APACHE_CONFIG="shibboleth-ds.conf" +%{?_without_builtinapache:APACHE_CONFIG="no"} +if [ "$APACHE_CONFIG" != "no" ] ; then + APACHE_CONFD="no" + if [ -d %{_sysconfdir}/httpd/conf.d ] ; then + APACHE_CONFD="%{_sysconfdir}/httpd/conf.d" + fi + if [ -d %{_sysconfdir}/apache2/conf.d ] ; then + APACHE_CONFD="%{_sysconfdir}/apache2/conf.d" + fi + if [ "$APACHE_CONFD" != "no" ] ; then + %{__mkdir} -p $RPM_BUILD_ROOT$APACHE_CONFD + %{__cp} -p $RPM_BUILD_ROOT%{_sysconfdir}/shibboleth-ds/$APACHE_CONFIG $RPM_BUILD_ROOT$APACHE_CONFD/$APACHE_CONFIG + echo "%config(noreplace) $APACHE_CONFD/$APACHE_CONFIG" > rpm.filelist + fi +fi + +%clean +[ "$RPM_BUILD_ROOT" != "/" ] && %{__rm} -rf $RPM_BUILD_ROOT + +%post +%if "%{_vendor}" == "redhat" + # On upgrade, restart components if they're already running. + if [ "$1" -gt "1" ] ; then + %{!?_without_builtinapache:/sbin/service httpd status 1>/dev/null && /sbin/service httpd restart 1>/dev/null} + exit 0 + fi +%endif + +%preun +%if "%{_vendor}" == "redhat" + if [ "$1" = 0 ] ; then + %{!?_without_builtinapache:/sbin/service httpd status 1>/dev/null && /sbin/service httpd restart 1>/dev/null} + fi +%endif +%if "%{_vendor}" == "suse" + if [ "$1" = 0 ] ; then + %{!?_without_builtinapache:/sbin/service apache2 status 1>/dev/null && /sbin/service apache2 restart 1>/dev/null} + fi +%endif +exit 0 + +%postun +%if "%{_vendor}" == "suse" +cd / +%{!?_without_builtinapache:%restart_on_update apache2} +%endif + +%files -f rpm.filelist +%defattr(-,root,root,-) +%dir %{_sysconfdir}/shibboleth-ds +%{_sysconfdir}/shibboleth-ds/*.txt +%{_sysconfdir}/shibboleth-ds/*.gif +%config(noreplace) %{_sysconfdir}/shibboleth-ds/index.html +%config(noreplace) %{_sysconfdir}/shibboleth-ds/idpselect.css +%config(noreplace) %{_sysconfdir}/shibboleth-ds/idpselect_config.js +%config %{_sysconfdir}/shibboleth-ds/idpselect.js +%config %{_sysconfdir}/shibboleth-ds/shibboleth-ds.conf + +%changelog +* Mon Jun 6 2016 Scott Cantor <cantor.2@osu.edu> - 1.2.0-1 +- Update version +- Fix license name + +* Wed Apr 29 2015 Scott Cantor <cantor.2@osu.edu> - 1.1.0-1 +- Update version +- Stop marking text files as configs +- Add gif to package + +* Mon Apr 11 2011 Scott Cantor <cantor.2@osu.edu> - 1.0-1 +- First version.