diff --git a/Dockerfile b/Dockerfile
index a47c16e77b0d959b02758b77382719b6767131e5..e5cb9d3e5e2bb2fe9ab9d15c0b4f4d992b0de164 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,8 @@ RUN apt-get -q update  && \
     apt-get -y autoremove && \
     apt-get -y clean
 
-RUN a2enmod rewrite
+RUN a2enmod rewrite && \
+    a2enmod shib
 
 #RUN git clone https://github.com/Edugate/Jagger /opt/rr3 && cd /opt/rr3 && git checkout v1.8.0
 RUN git clone https://github.com/Edugate/Jagger /opt/rr3
@@ -25,6 +26,9 @@ RUN cd /opt/rr3 && bash install.sh && \
     cd /opt/rr3/application/config && \
     cp memcached-default.php memcached.php
 
+COPY ./conf/etc/shibboleth/keygen.sh /etc/shibboleth/keygen.sh
+COPY ./conf/etc/shibboleth-ds/ /etc/shibboleth-ds/
+
 COPY ./conf/etc/registry/application/config/config.php /opt/rr3/application/config/config.php
 COPY ./conf/etc/registry/application/config/config_rr.php /opt/rr3/application/config/config_rr.php
 COPY ./conf/etc/registry/application/config/database.php /opt/rr3/application/config/database.php
diff --git a/conf/etc/apache2/sites-available/000-default.conf b/conf/etc/apache2/sites-available/000-default.conf
index 814361ccd5dea3549148ad2d0bacf3103c12411f..139285e82b1db66f1cf4eb783de797c907a93bb3 100644
--- a/conf/etc/apache2/sites-available/000-default.conf
+++ b/conf/etc/apache2/sites-available/000-default.conf
@@ -9,19 +9,19 @@
         #ServerName www.example.com
 
         ServerAdmin webmaster@localhost
-        DocumentRoot /opt/rr3
+        DocumentRoot /opt/rr3/
 
         ErrorLog ${APACHE_LOG_DIR}/error.log
         CustomLog ${APACHE_LOG_DIR}/access.log combined
 
-        Alias /rr3 /opt/rr3
-        <Directory /opt/rr3>
+        Alias / /opt/rr3/
+        <Directory /opt/rr3/>
             Require all granted
 
             RewriteEngine On
-            RewriteBase /rr3
+            RewriteBase /
             RewriteCond $1 !^(Shibboleth\.sso|index\.php|logos|signedmetadata|flags|images|app|schemas|fonts|styles|images|js|robots\.txt|pub|includes)
-            RewriteRule  ^(.*)$ /rr3/index.php?/$1 [L]
+            RewriteRule  ^(.*)$ /index.php?/$1 [L]
         </Directory>
 
         <Directory /opt/rr3/application>
@@ -34,6 +34,24 @@
             require valid-user
         </Location>
 
+        <Location /shibboleth-ds>
+            Require all granted
+            <IfModule mod_shib.c>
+                AuthType shibboleth
+                ShibRequestSetting requireSession false
+                require shibboleth
+            </IfModule>
+        </Location>
+        <Directory /etc/shibboleth-ds/>
+            Require all granted
+        </Directory>
+
+        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
+
         # For most configuration files from conf-available/, which are
         # enabled or disabled at a global level, it is possible to
         # include a line for only one particular virtual host. For example the
diff --git a/conf/etc/entrypoint b/conf/etc/entrypoint
index d96b1a5790a432e47b2c98b006a2f97c41e553d6..76eef498813f2242d291a11ff9d04d44a0386490 100755
--- a/conf/etc/entrypoint
+++ b/conf/etc/entrypoint
@@ -45,4 +45,9 @@ cd /opt/rr3/application
 ./doctrine orm:generate-proxies
 
 
+cd /etc/shibboleth/
+./keygen.sh -n "sp-signing" -h ${FAAS_REGISTRY_HOSTNAME} || echo "file exists"
+cp /etc/shibboleth/sp-signing-cert.pem /etc/shibboleth/sp-encrypt-cert.pem
+cp /etc/shibboleth/sp-signing-key.pem /etc/shibboleth/sp-encrypt-key.pem
+
 exec supervisord -c /etc/supervisord.conf
diff --git a/conf/etc/shibboleth-ds/LICENSE.txt b/conf/etc/shibboleth-ds/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dd5b3a58aa1849f452abc9b5cd1638dc71a5e482
--- /dev/null
+++ b/conf/etc/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/conf/etc/shibboleth-ds/blank.gif b/conf/etc/shibboleth-ds/blank.gif
new file mode 100644
index 0000000000000000000000000000000000000000..2799b45c6591f1db05c8c00bd1fd0c5c01f57614
Binary files /dev/null and b/conf/etc/shibboleth-ds/blank.gif differ
diff --git a/conf/etc/shibboleth-ds/idpselect.css b/conf/etc/shibboleth-ds/idpselect.css
new file mode 100644
index 0000000000000000000000000000000000000000..9b480b4bd15be76734866edb03d138adbd335577
--- /dev/null
+++ b/conf/etc/shibboleth-ds/idpselect.css
@@ -0,0 +1,213 @@
+/* Top level is idpSelectIdPSelector */
+#idpSelectIdPSelector
+{
+    width: 389px;
+    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/conf/etc/shibboleth-ds/idpselect.js b/conf/etc/shibboleth-ds/idpselect.js
new file mode 100644
index 0000000000000000000000000000000000000000..b9c191b26a6598585173712a072340b8f3303ab9
--- /dev/null
+++ b/conf/etc/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 parameter must start with https:// or http://","fatal.badReturnString":"Return parameter is not whitelisted","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.noData":"Heruntergeladene Metadata waren leer","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","fatal.badReturnString":"Return Parameter ist nicht auf Positivliste enthalten","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","autoFollow.message":"Auswahl merken und diesen Dialog nicht mehr anzeigen","autoFollow.never":"Nicht merken","autoFollow.time0":"Nur f\u00fcr Heute","autoFollow.time1":"Drei Monate lang","autoFollow.time2":"Neun Monate lang"},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か月","fatal.badReturnString":"戻り URL が不正です",helpText:"ヘルプ",defaultLogoAlt:"ロゴ未設定"},"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 r;var W="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var aJ;var S;var aB;var an;var aa;var d;var F;var n;var y;var k;var aw;var g;var t;var ab;var ah;var D;var ag;var R;var h;var K;var au;var N;var T;var al;var ax;var aq;var c;var af;var G;var Q;var Z;var P;var U;var j;var az;var V;var aL="idpSelect";var aj="IdPSelect";var ar;var B="";var Y="";var aA=[];var aG="entityID";this.draw=function(aP){aJ=document.getElementById(aP.insertAtDiv);if(!m(aP)){return}if(!aJ){O(A("fatal.divMissing"));return}if((null!=Q)&&(null!=i(Q))){var aM=b();if(aM.length!=0){var aO=aG+"="+encodeURIComponent(aM[0]);if(B.indexOf("?")==-1){aO="?"+aO}else{aO="&"+aO}q(aJ,B+aO);return}}if(!f(aP.dataSource)){return}aK();u(aP.hiddenIdPs);r.sort(function(aR,aQ){return aE(aR).localeCompare(aE(aQ))});var aN=ac();aJ.appendChild(aN);ar.draw(aP.setFocusTextBox)};var m=function(aT){var aN;D=aT.preferredIdP;ag=aT.maxPreferredIdPs;R=aT.helpURL;h=aT.ie6Hack;K=aT.samlIdPCookieTTL;al=aT.alwaysShow;ax=aT.maxResults;aq=aT.ignoreKeywords;if(aT.showListFirst){c=aT.showListFirst}else{c=false}if(aT.noWriteCookie){af=aT.noWriteCookie}else{af=false}if(aT.ignoreURLParams){G=aT.ignoreURLParams}else{G=false}F=aT.defaultLogo;n=aT.defaultLogoWidth;y=aT.defaultLogoHeight;k=aT.minWidth;aw=aT.minHeight;g=aT.maxWidth;t=aT.maxHeight;ab=aT.bestRatio;if(null==aT.doNotCollapse){ah=true}else{ah=aT.doNotCollapse}N=aT.maxIdPCharsButton;au=aT.maxIdPCharsDropDown;T=aT.maxIdPCharsAltTxt;Q=aT.autoFollowCookie;Z=aT.autoFollowCookieTTLs;var a3;if(typeof navigator=="undefined"){a3=aT.defaultLanguage}else{a3=navigator.language||navigator.userLanguage||aT.defaultLanguage}a3=a3.toLowerCase();if(a3.indexOf("-")>0){aB=a3.substring(0,a3.indexOf("-"))}var aX=new IdPSelectLanguages();an=aT.defaultLanguage;if(typeof aT.langBundles!="undefined"&&typeof aT.langBundles[a3]!="undefined"){aa=aT.langBundles[a3]}else{if(typeof aX.langBundles[a3]!="undefined"){aa=aX.langBundles[a3]}else{if(typeof aB!="undefined"){if(typeof aT.langBundles!="undefined"&&typeof aT.langBundles[aB]!="undefined"){aa=aT.langBundles[aB]}else{if(typeof aX.langBundles[aB]!="undefined"){aa=aX.langBundles[aB]}}}}}if(typeof aT.langBundles!="undefined"&&typeof aT.langBundles[aT.defaultLanguage]!="undefined"){d=aT.langBundles[aT.defaultLanguage]}else{d=aX.langBundles[aT.defaultLanguage]}if(!d){O("No languages work");return false}if(!aa){s("No language support for "+a3);aa=d}if(aT.testGUI){return true}var aY="urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single";var a1;var a2=false;var aR;var aU;var aQ=window;while(null!==aQ.parent&&aQ!==aQ.parent){aQ=aQ.parent}var aW=aQ.location;var aS=aW.search;if(G||null==aS||0==aS.length||aS.charAt(0)!="?"){if((null==aT.defaultReturn)&&!G){O(A("fatal.noparms"));return false}aN=aT.myEntityID;B=aT.defaultReturn;if(null!=aT.defaultReturnIDParam){aG=aT.defaultReturnIDParam}}else{aS=aS.substring(1);aR=aS.split("&");if(aR.length===0){O(A("fatal.noparms"));return false}for(a1=0;a1<aR.length;a1++){aU=aR[a1].split("=");if(aU.length!=2){continue}if(aU[0]=="entityID"){aN=decodeURIComponent(aU[1])}else{if(aU[0]=="return"){B=decodeURIComponent(aU[1])}else{if(aU[0]=="returnIDParam"){aG=decodeURIComponent(aU[1])}else{if(aU[0]=="policy"){aY=decodeURIComponent(aU[1])}else{if(aU[0]=="isPassive"){a2=(aU[1].toUpperCase()=="TRUE")}}}}}}}var aM;if(null==aT.allowableProtocols){aM=["urn:oasis:names:tc:SAML:profiles:SSO:idpdiscovery-protocol:single"]}else{aM=aT.allowableProtocols}var a0=false;for(var a1=0;a1<aM.length;a1++){var aZ=aM[a1];if(aY==aZ){a0=true;break}}if(!a0){O(A("fatal.wrongProtocol"));return false}if(aT.myEntityID!==null&&aT.myEntityID!=aN){O(A("fatal.wrongEntityId")+'"'+aN+'" != "'+aT.myEntityID+'"');return false}if(null===B||B.length===0){O(A("fatal.noReturnURL"));return false}if(!ao(B)){O(A("fatal.badProtocol"));return false}if(!e(aT.returnWhiteList,B)){ae(A("fatal.badReturnString"));return false}if(a2){var aP=b();var aV=document.getElementById(parmsSupplied.insertAtDiv);if(aP.length==0){q(aV,B);return false}else{var aO=aG+"="+encodeURIComponent(aP[0]);if(B.indexOf("?")==-1){aO="?"+aO}else{aO="&"+aO}q(aV,B+aO);return false}}a1=B.indexOf("?");if(a1<0){Y=B;return true}Y=B.substring(0,a1);aS=B.substring(a1+1);aR=aS.split("&");for(a1=0;a1<aR.length;a1++){aU=aR[a1].split("=");if(aU.length!=2){continue}aU[1]=decodeURIComponent(aU[1]);aA.push(aU)}return true};var aK=function(){var aO=[];var aN;for(aN=0;aN<r.length;){var aM=z(r[aN]);if(null==aO[aM]){aO[aM]=aM;aN=aN+1}else{r.splice(aN,1)}}};var u=function(aO){if(null==aO||0==aO.length){return}var aN;var aM;for(aN=0;aN<aO.length;aN++){for(aM=0;aM<r.length;aM++){if(z(r[aM])==aO[aN]){r.splice(aM,1);break}}}};var ao=function(aN){if(null===aN){return false}var aM="://";var aO=aN.indexOf(aM);if(aO<0){return false}aN=aN.substring(0,aO);if(aN=="http"||aN=="https"){return true}return false};var e=function(aM,aN){if(null==aM){return true}for(var aO=0;aO<aM.length;aO++){var aP=new RegExp(aM[aO]);if(aP.test(aN)){return true}}return false};var aI=function(){if(null==navigator){return false}var aM=navigator.appName;if(null==aM){return false}return(aM=="Microsoft Internet Explorer")};var q=function(aN,aO){var aM=document.createElement("a");aM.href=aO;aN.appendChild(aM);aM.click()};var f=function(aP){var aO=null;try{aO=new XMLHttpRequest()}catch(aN){}if(null==aO){try{aO=new ActiveXObject("Microsoft.XMLHTTP")}catch(aN){}}if(null==aO){try{aO=new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(aN){}}if(null==aO){O(A("fatal.noXMLHttpRequest"));return false}if(aI()){aP+="?random="+(Math.random()*1000000)}aO.open("GET",aP,false);if(typeof aO.overrideMimeType=="function"){aO.overrideMimeType("application/json")}aO.send(null);if(aO.status==200){var aM=aO.responseText;if(aM===null){O(A("fatal.noData"));return false}r=JSON.parse(aM)}else{O(A("fatal.loadFailed")+aP);return false}return true};var ad=function(aM){for(var aN=0;aN<r.length;aN++){if(z(r[aN])==aM){return r[aN]}}return null};var H=function(aT,aN){var aS=function(aW){var aU=null;var aV;if(null==aT.Logos){return null}for(aV in aT.Logos){if(aT.Logos[aV].lang==aW&&aT.Logos[aV].width!=null&&aT.Logos[aV].width>=k&&aT.Logos[aV].height!=null&&aT.Logos[aV].height>=aw){if(aU===null){aU=aT.Logos[aV]}else{me=Math.abs(ab-Math.log(aT.Logos[aV].width/aT.Logos[aV].height));him=Math.abs(ab-Math.log(aU.width/aU.height));if(him>me){aU=aT.Logos[aV]}}}}return aU};var aP=null;var aO=document.createElement("img");am(aO,"IdPImg");aP=aS(S);if(null===aP&&typeof aB!="undefined"){aP=aS(aB)}if(null===aP){aP=aS(null)}if(null===aP){aP=aS(an)}if(null===aP){if(!aN){return null}aO.src=F;aO.width=n;aO.height=y;aO.alt=A("defaultLogoAlt");return aO}aO.src=aP.value;var aQ=aE(aT);if(aQ.length>T){aQ=aQ.substring(0,T)+"..."}aO.alt=aQ;var aM=aP.width;var aR=aP.height;if(aM>g){aR=(g/aM)*aR;aM=g}if(aR>t){aM=(t/aR)*aM;aR=t}aO.setAttribute("width",aM);aO.setAttribute("height",aR);return aO};var ac=function(){var aN=ap("IdPSelector");var aM;aM=aC(aN);o(aN,aM);X(aN,aM);if(null!=Q){C(aN)}return aN};var M=function(aO,aV,aN){var aM=ap(undefined,"PreferredIdPButton");var aU=document.createElement("a");var aT=aG+"="+encodeURIComponent(z(aO));var aP=B;var aR=H(aO,aN);if(aP.indexOf("?")==-1){aT="?"+aT}else{aT="&"+aT}aU.href=aP+aT;aU.onclick=function(){aH(z(aO))};if(null!=aR){var aW=ap(undefined,"PreferredIdPImg");aW.appendChild(aR);aU.appendChild(aW)}var aS=ap(undefined,"TextDiv");var aQ=aE(aO);if(aQ.length>N){aQ=aQ.substring(0,N)+"..."}aM.title=aQ;aS.appendChild(document.createTextNode(aQ));aU.appendChild(aS);aM.appendChild(aU);return aM};var aF=function(aM,aP){var aO=ap(undefined,"TextDiv");var aN=document.createTextNode(A(aP));aO.appendChild(aN);aM.appendChild(aO)};var a=function(aM,aO){if(null===aO||0===aO.length||"-"==aO.value){return}var aN=0;while(aN<aM.options.length){if(aM.options[aN].value==aO){aM.options[aN].selected=true;break}aN++}};var aC=function(aR){var aQ=L();if(0===aQ.length){return false}var aM=ah;for(var aO=0;aO<ag&&aO<aQ.length;aO++){if(aQ[aO]&&H(aQ[aO],false)){aM=true}}var aP;if(aM){aP=ap("PreferredIdPTile")}else{aP=ap("PreferredIdPTileNoImg")}aF(aP,"idpPreferred.label");for(var aO=0;aO<ag&&aO<aQ.length;aO++){if(aQ[aO]){var aN=M(aQ[aO],aO,aM);aP.appendChild(aN)}}aR.appendChild(aP);return true};var ai=function(){var aN=document.createElement("form");U.appendChild(aN);aN.action=Y;aN.method="GET";aN.setAttribute("autocomplete","OFF");var aM=0;for(aM=0;aM<aA.length;aM++){var aO=document.createElement("input");aO.setAttribute("type","hidden");aO.name=aA[aM][0];aO.value=aA[aM][1];aN.appendChild(aO)}return aN};var o=function(aT,aN){U=ap("IdPEntryTile");if(c){U.style.display="none"}var aO=document.createElement("label");aO.setAttribute("for",aL+"Input");if(aN){aF(aO,"idpEntry.label")}else{aF(aO,"idpEntry.NoPreferred.label")}var aR=ai();aR.appendChild(aO);var aQ=document.createElement("input");aR.appendChild(aQ);aQ.type="text";l(aQ,"Input");var aS=document.createElement("input");aS.setAttribute("type","hidden");aR.appendChild(aS);aS.name=aG;aS.value="-";var aP=v("Select");aP.disabled=true;aR.appendChild(aP);aR.onsubmit=function(){if(null===aS.value||0===aS.value.length||"-"==aS.value){return false}aQ.value=aS.textValue;aH(aS.value);return true};ar=new TypeAheadControl(r,aQ,aS,aP,au,aE,z,ak,h,al,ax,I);var aM=document.createElement("a");aM.appendChild(document.createTextNode(A("idpList.showList")));aM.href="#";am(aM,"DropDownToggle");aM.onclick=function(){U.style.display="none";a(az,aS.value);j.style.display="";V.focus();return false};U.appendChild(aM);x(U);aT.appendChild(U)};var X=function(aM,aP){j=ap("IdPListTile");if(!c){j.style.display="none"}var aT=document.createElement("label");aT.setAttribute("for",aL+"Selector");if(aP){aF(aT,"idpList.label")}else{aF(aT,"idpList.NoPreferred.label")}az=document.createElement("select");l(az,"Selector");az.name=aG;j.appendChild(az);var aU=p("-",A("idpList.defaultOptionLabel"));aU.selected=true;az.appendChild(aU);var aO;for(var aQ=0;aQ<r.length;aQ++){aO=r[aQ];aU=p(z(aO),aE(aO));az.appendChild(aU)}var aN=ai();aN.appendChild(aT);aN.appendChild(az);aN.onsubmit=function(){if(az.selectedIndex<1){return false}aH(az.options[az.selectedIndex].value);return true};var aR=v("List");V=aR;aN.appendChild(aR);j.appendChild(aN);var aS=document.createElement("a");aS.appendChild(document.createTextNode(A("idpList.showSearch")));aS.href="#";am(aS,"DropDownToggle");aS.onclick=function(){U.style.display="";j.style.display="none";return false};j.appendChild(aS);x(j);aM.appendChild(j)};var C=function(aP){var aN="IdPSelectAutoDisp";autoDispatchTile=ap(undefined,"autoDispatchArea");autoDispatchTile.appendChild(document.createTextNode(A("autoFollow.message")));var aM=document.createElement("input");aM.setAttribute("type","radio");aM.setAttribute("checked","checked");aM.setAttribute("name",aN);aM.onclick=function(){E(0)};div=ap(undefined,"autoDispatchTile");div.appendChild(aM);div.appendChild(document.createTextNode(A("autoFollow.never")));autoDispatchTile.appendChild(div);var aO;for(aO=0;aO<Z.length;aO++){aM=document.createElement("input");aM.setAttribute("type","radio");aM.setAttribute("name",aN);aM.life=Z[aO];aM.onclick=function(){var aQ=this.life;E(aQ)};div=ap(undefined,"autoDispatchTile");div.appendChild(aM);div.appendChild(document.createTextNode(A("autoFollow.time"+aO)));autoDispatchTile.appendChild(div)}aP.appendChild(autoDispatchTile)};var v=function(aN){var aM=document.createElement("input");aM.setAttribute("type","submit");aM.value=A("submitButton.label");l(aM,aN+"Button");return aM};var x=function(aN){var aM=document.createElement("a");aM.href=R;aM.appendChild(document.createTextNode(A("helpText")));am(aM,"HelpButton");aN.appendChild(aM)};var ap=function(aO,aM){var aN=document.createElement("div");if(undefined!==aO){l(aN,aO)}if(undefined!==aM){am(aN,aM)}return aN};var p=function(aN,aO){var aM=document.createElement("option");aM.value=aN;if(aO.length>au){aO=aO.substring(0,au)}aM.appendChild(document.createTextNode(aO));return aM};var l=function(aN,aM){aN.id=aL+aM};var am=function(aN,aM){aN.setAttribute("class",aj+aM)};var aD=function(aM){return document.getElementById(aL+aM)};var aH=function(aM){J(aM);at(P)};var A=function(aM){var aN=aa[aM];if(!aN){aN=d[aM]}if(!aN){aN="Missing message for "+aM}return aN};var z=function(aM){return aM.entityID};var ak=function(aO){var aM;if(null==aO.Logos){return null}for(aM=0;aM<aO.Logos.length;aM++){var aN=aO.Logos[aM];if(aN.height=="16"&&aN.width=="16"){if(null==aN.lang||S==aN.lang||(typeof aB!="undefined"&&aB==aN.lang)||an==aN.lang){return aN.value}}}return null};var aE=function(aN){var aM=ay(aN.DisplayNames);if(null!==aM){return aM}s("No Name entry in any language for "+z(aN));return z(aN)};var I=function(aN){if(aq||null==aN.Keywords){return null}var aM=ay(aN.Keywords);return aM};var ay=function(aM){var aN;for(aN in aM){if(aM[aN].lang==S){return aM[aN].value}}if(typeof aB!="undefined"){for(aN in aM){if(aM[aN].lang==aB){return aM[aN].value}}}for(aN in aM){if(aM[aN].lang==null){return aM[aN].value}}for(aN in aM){if(aM[aN].lang==an){return aM[aN].value}}return null};var L=function(){var aQ=[];var aP=0;var aO;var aN;if(null!=D){for(aO=0;aO<D.length&&aO<ag;aO++){aQ[aO]=ad(D[aO]);aP++}}P=b();for(aO=aP,aN=0;aN<P.length&&aO<ag;aN++){var aM=ad(P[aN]);if(typeof aQ.indexOf==="undefined"){aQ.push(aM);aO++}else{if(aQ.indexOf(aM)===-1){aQ.push(aM);aO++}}}return aQ};var J=function(aM){var aN=[];while(0!==P.length){var aO=P.pop();if(aO!=aM){aN.unshift(aO)}}aN.unshift(aM);P=aN;return};var E=function(aO){var aM;if(aO>0){var aN=new Date();cookieTTL=aO*24*60*60*1000;aM=new Date(aN.getTime()+cookieTTL)}else{aM=new Date(0)}document.cookie=Q+"=1;path=/;expires="+aM.toUTCString()};var i=function(aO){var aQ,aN;var aR;aR=document.cookie.split(";");for(aQ=0;aQ<aR.length;aQ++){var aP=aR[aQ];var aM=aP.indexOf("=");var aS=aP.substring(0,aM);if(aO==(aS.replace(/^\s+|\s+$/g,""))){return aP.substring(aM+1)}}return null};var b=function(){var aM=[];var aN;var aO=i("_saml_idp");if(aO!=null){aO=aO.replace(/^\s+|\s+$/g,"");aO=aO.replace("+","%20");aO=aO.split("%20");for(aN=aO.length;aN>0;aN--){if(0===aO[aN-1].length){continue}var aP=av(decodeURIComponent(aO[aN-1]));if(aP.length>0){aM.push(aP)}}}return aM};var at=function(aR){var aO=[];var aQ=aR.length;if(af){return}if(aQ>5){aQ=5}for(var aP=aQ;aP>0;aP--){if(aR[aP-1].length>0){aO.push(encodeURIComponent(w(aR[aP-1])))}}var aM=null;if(K){var aN=new Date();cookieTTL=K*24*60*60*1000;aM=new Date(aN.getTime()+cookieTTL)}document.cookie="_saml_idp="+aO.join("%20")+"; path = /"+((aM===null)?"":"; expires="+aM.toUTCString())};var w=function(aV){var aM="",aQ,aO,aN,aU,aT,aS,aR;for(var aP=0;aP<aV.length;){aQ=aV.charCodeAt(aP++);aO=aV.charCodeAt(aP++);aN=aV.charCodeAt(aP++);aU=aQ>>2;aT=((aQ&3)<<4)+(aO>>4);aS=((aO&15)<<2)+(aN>>6);aR=aN&63;if(isNaN(aO)){aS=aR=64}else{if(isNaN(aN)){aR=64}}aM+=W.charAt(aU)+W.charAt(aT)+W.charAt(aS)+W.charAt(aR)}return aM};var av=function(aP){var aN="",aW,aU,aS,aV,aT,aR,aQ;var aO=0;var aM=/[^A-Za-z0-9\+\/\=]/g;aP=aP.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{aV=W.indexOf(aP.charAt(aO++));aT=W.indexOf(aP.charAt(aO++));aR=W.indexOf(aP.charAt(aO++));aQ=W.indexOf(aP.charAt(aO++));aW=(aV<<2)|(aT>>4);aU=((aT&15)<<4)|(aR>>2);aS=((aR&3)<<6)|aQ;aN=aN+String.fromCharCode(aW);if(aR!=64){aN=aN+String.fromCharCode(aU)}if(aQ!=64){aN=aN+String.fromCharCode(aS)}aW=aU=aS="";aV=aT=aR=aQ=""}while(aO<aP.length);return aN};var ae=function(aN){if(aJ){var aM=document.createTextNode(aN);aJ.appendChild(aM)}else{alert("FATAL (NoDiv):"+aN)}};var O=function(aM){alert("FATAL - DISCO UI:"+aM);if(aJ){ae(aM)}};var s=function(){}}(new IdPSelectUI()).draw(new IdPSelectUIParms());
\ No newline at end of file
diff --git a/conf/etc/shibboleth-ds/idpselect_config.js b/conf/etc/shibboleth-ds/idpselect_config.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4252195f2498d7dcada5a6ab272556f01a3f6d3
--- /dev/null
+++ b/conf/etc/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.returnWhiteList = [ "^https:\/\/example\.org\/Shibboleth\.sso\/Login.*$" , "^https:\/\/example\.com\/Shibboleth\.sso\/Login.*$" ];
+    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/conf/etc/shibboleth-ds/index.html b/conf/etc/shibboleth-ds/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..a4f85bc907d461c8b24f56dc9e8c5ebc14f54373
--- /dev/null
+++ b/conf/etc/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/conf/etc/shibboleth-ds/nonminimised/idpselect.js b/conf/etc/shibboleth-ds/nonminimised/idpselect.js
new file mode 100644
index 0000000000000000000000000000000000000000..08a546ca444add6d8c3a80dfa4ec7a4a6c4fbb1a
--- /dev/null
+++ b/conf/etc/shibboleth-ds/nonminimised/idpselect.js
@@ -0,0 +1,1618 @@
+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){
+
+        //
+        // try to set up the div first so that errors from setUpLocals
+        // will go to the screen.
+        //
+        idpSelectDiv = document.getElementById(parms.insertAtDiv);
+
+        if (!setupLocals(parms)) {
+            return;
+        }
+
+        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;
+        }
+        if (!validateReturn(paramsSupplied.returnWhiteList, returnString)) {
+
+            fatalNoAlert(getLocalizedMessage('fatal.badReturnString'));
+            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;
+    };
+
+    /**
+     * Validate the return String against the regexps
+     */
+
+    var validateReturn = function(regexps, testString) {
+        if (null == regexps) {
+            return true;
+        }
+
+        // use .length in case regexps isnt an array
+        for (var i = 0; i < regexps.length; i++) {
+            var regexp = new RegExp(regexps[i]);
+            if (regexp.test(testString)) {
+                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 fatalNoAlert = function(message) {
+        if (idpSelectDiv) {
+            var txt = document.createTextNode(message);
+            idpSelectDiv.appendChild(txt);
+        } else {
+            alert('FATAL (NoDiv):' + message);
+        }
+    };
+
+    var fatal = function(message) {
+        alert('FATAL - DISCO UI:' + message);
+        if (idpSelectDiv) {
+            fatalNoAlert(message);
+        }
+    };
+
+    var debug = function() {
+        //
+        // Nothing
+    };
+}
+
+(new IdPSelectUI()).draw(new IdPSelectUIParms());
diff --git a/conf/etc/shibboleth-ds/nonminimised/idpselect_config.js b/conf/etc/shibboleth-ds/nonminimised/idpselect_config.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4252195f2498d7dcada5a6ab272556f01a3f6d3
--- /dev/null
+++ b/conf/etc/shibboleth-ds/nonminimised/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.returnWhiteList = [ "^https:\/\/example\.org\/Shibboleth\.sso\/Login.*$" , "^https:\/\/example\.com\/Shibboleth\.sso\/Login.*$" ];
+    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/conf/etc/shibboleth-ds/nonminimised/idpselect_languages.js b/conf/etc/shibboleth-ds/nonminimised/idpselect_languages.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac4f2551e00d8887885de9c0e4affe6e0519a0eb
--- /dev/null
+++ b/conf/etc/shibboleth-ds/nonminimised/idpselect_languages.js
@@ -0,0 +1,115 @@
+ 
+/** @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 parameter must start with https:// or http://",
+        'fatal.badReturnString' : "Return parameter is not whitelisted",
+        '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.noData' : 'Heruntergeladene Metadata waren leer',
+        '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",
+        'fatal.badReturnString' : "Return Parameter ist nicht auf Positivliste enthalten",
+        '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',
+        'autoFollow.message' : 'Auswahl merken und diesen Dialog nicht mehr anzeigen',
+        'autoFollow.never' : 'Nicht merken',
+        'autoFollow.time0' : 'Nur f\u00fcr Heute',
+        'autoFollow.time1' : 'Drei Monate lang',
+        'autoFollow.time2' : 'Neun Monate lang'
+        },
+    '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か月',
+        'fatal.badReturnString' : "戻り URL が不正です",
+        'helpText': 'ヘルプ',
+        'defaultLogoAlt' : 'ロゴ未設定'
+    },
+    '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/conf/etc/shibboleth-ds/nonminimised/json2.js b/conf/etc/shibboleth-ds/nonminimised/json2.js
new file mode 100644
index 0000000000000000000000000000000000000000..5e61c56fc67fcce0e3cf4de8595cb77b1dc73164
--- /dev/null
+++ b/conf/etc/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 '&nbsp;'),
+                        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/conf/etc/shibboleth-ds/nonminimised/typeahead.js b/conf/etc/shibboleth-ds/nonminimised/typeahead.js
new file mode 100644
index 0000000000000000000000000000000000000000..329ecd82425c3385514ccf974799ac120ea30711
--- /dev/null
+++ b/conf/etc/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/conf/etc/shibboleth/keygen.sh b/conf/etc/shibboleth/keygen.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8624b81cacd0bbbfa49e8f6cece970fa83cdce79
--- /dev/null
+++ b/conf/etc/shibboleth/keygen.sh
@@ -0,0 +1,91 @@
+#! /bin/sh
+
+while getopts n:h:u:g:o:e:y:bf c
+     do
+         case $c in
+           u)         USER=$OPTARG;;
+           g)         GROUP=$OPTARG;;
+           o)         OUT=$OPTARG;;
+           b)         BATCH=1;;
+           f)         FORCE=1;;
+           h)         FQDN=$OPTARG;;
+           e)         ENTITYID=$OPTARG;;
+           y)         YEARS=$OPTARG;;
+	   n)         PREFIX=$OPTARG;;
+	   \?)        echo "keygen [-o output directory (default .)] [-u username to own keypair] [-g owning groupname] [-h hostname for cert] [-y years to issue cert] [-e entityID to embed in cert] [-n filename prefix (default 'sp')]"
+                      exit 1;;
+         esac
+     done
+     
+if [ -z "$OUT" ] ; then
+    OUT=.
+fi
+
+if [ -z "$PREFIX" ]; then
+    PREFIX="sp"
+fi
+
+if [ -n "$FORCE" ] ; then
+    rm $OUT/${PREFIX}-key.pem $OUT/${PREFIX}-cert.pem
+fi
+
+if  [ -s $OUT/${PREFIX}-key.pem -o -s $OUT/${PREFIX}-cert.pem ] ; then
+    if [ -z "$BATCH" ] ; then  
+        echo The files $OUT/${PREFIX}-key.pem and/or $OUT/${PREFIX}-cert.pem already exist!
+        echo Use -f option to force recreation of keypair.
+        exit 2
+    fi
+    exit 0
+fi
+
+if [ -z "$FQDN" ] ; then
+    FQDN=`hostname`
+fi
+
+if [ -z "$YEARS" ] ; then
+    YEARS=10
+fi
+
+DAYS=`expr $YEARS \* 365`
+
+if [ -z "$ENTITYID" ] ; then
+    ALTNAME=DNS:$FQDN
+else
+    ALTNAME=DNS:$FQDN,URI:$ENTITYID
+fi
+
+SSLCNF=$OUT/${PREFIX}-cert.cnf
+cat >$SSLCNF <<EOF
+# OpenSSL configuration file for creating keypair
+[req]
+prompt=no
+default_bits=3072
+encrypt_key=no
+default_md=sha256
+distinguished_name=dn
+# PrintableStrings only
+string_mask=MASK:0002
+x509_extensions=ext
+[dn]
+CN=$FQDN
+[ext]
+subjectAltName=$ALTNAME
+subjectKeyIdentifier=hash
+EOF
+
+touch $OUT/${PREFIX}-key.pem
+chmod 600 $OUT/${PREFIX}-key.pem
+if [ -z "$BATCH" ] ; then
+    openssl req -config $SSLCNF -new -x509 -days $DAYS -keyout $OUT/${PREFIX}-key.pem -out $OUT/${PREFIX}-cert.pem
+else
+    openssl req -config $SSLCNF -new -x509 -days $DAYS -keyout $OUT/${PREFIX}-key.pem -out $OUT/${PREFIX}-cert.pem 2> /dev/null
+fi
+rm $SSLCNF
+
+if  [ -s $OUT/${PREFIX}-key.pem -a -n "$USER" ] ; then
+    chown $USER $OUT/${PREFIX}-key.pem $OUT/${PREFIX}-cert.pem
+fi
+
+if  [ -s $OUT/${PREFIX}-key.pem -a -n "$GROUP" ] ; then
+    chgrp $GROUP $OUT/${PREFIX}-key.pem $OUT/${PREFIX}-cert.pem
+fi
diff --git a/conf/faas-registry.cnf b/conf/faas-registry.cnf
index 302ad4bc89484f033b2fd9385e261fb1ef54e81f..b14cd145cdc67025ab66f81561d127620d8e2473 100644
--- a/conf/faas-registry.cnf
+++ b/conf/faas-registry.cnf
@@ -4,7 +4,8 @@ FAAS_REGISTRY_VERSION=1.0-SNAPSHOT
 
 FAAS_REGISTRY_PORT=9080
 
-FAAS_REGISTRY_BASE_URL=http://localhost:9080/rr3/
+FAAS_REGISTRY_HOSTNAME=localhost
+FAAS_REGISTRY_BASE_URL=http://localhost:9080
 FAAS_REGISTRY_COOKIE_SECURE=FALSE
 FAAS_REGISTRY_TIMEZONE=Europe/Warsaw
 
diff --git a/docker-compose.yml b/docker-compose.yml
index a83e0aa61cf2920ef84faab91f7f8c48729276f1..d5a5748b67adfbc2cdd47a8cb5a4e5285bfd9270 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -48,4 +48,5 @@ services:
             FAAS_REGISTRY_RR_SMTP_PORT: 25
             FAAS_REGISTRY_RR_MAIL_USER: ${FAAS_REGISTRY_RR_MAIL_USER}
             FAAS_REGISTRY_RR_MAIL_PASS: ${FAAS_REGISTRY_RR_MAIL_PASS}
-            FAAS_REGISTRY_RR_MAIL_FROM: ${FAAS_REGISTRY_RR_MAIL_FROM}
\ No newline at end of file
+            FAAS_REGISTRY_RR_MAIL_FROM: ${FAAS_REGISTRY_RR_MAIL_FROM}
+            FAAS_REGISTRY_HOSTNAME: ${FAAS_REGISTRY_HOSTNAME}