Browse Source

fffffuuuu

Peter Alcock 8 months ago
parent
commit
ff2a451843
24 changed files with 2958 additions and 0 deletions
  1. 288 0
      adfsWebCustomization/.gitignore
  2. 21 0
      adfsWebCustomization/LICENSE
  3. 31 0
      adfsWebCustomization/README.md
  4. 77 0
      adfsWebCustomization/centeredUi/README.md
  5. 855 0
      adfsWebCustomization/centeredUi/ThemeCenterBrand.css
  6. BIN
      adfsWebCustomization/centeredUi/images/empty_user.png
  7. BIN
      adfsWebCustomization/centeredUi/images/screenshot.png
  8. BIN
      adfsWebCustomization/centeredUi/images/screenshot_paginated.png
  9. 883 0
      adfsWebCustomization/centeredUi/paginatedOnload.js
  10. 28 0
      adfsWebCustomization/communityCustomizations/README.md
  11. 14 0
      adfsWebCustomization/communityCustomizations/ShowPasswordButton/OnLoad.js
  12. 19 0
      adfsWebCustomization/communityCustomizations/ShowPasswordButton/README.md
  13. BIN
      adfsWebCustomization/communityCustomizations/ShowPasswordButton/images/Customization1.png
  14. BIN
      adfsWebCustomization/communityCustomizations/ShowPasswordButton/images/Customization2.png
  15. 33 0
      adfsWebCustomization/mfaLoadingWheel/README.md
  16. BIN
      adfsWebCustomization/mfaLoadingWheel/images/screenshot_wheel.png
  17. 9 0
      adfsWebCustomization/mfaLoadingWheel/loadWheel.js
  18. 47 0
      adfsWebCustomization/pageDetectionTelemetry/InteractiveCompletionByPlatformQuery.txt
  19. 45 0
      adfsWebCustomization/pageDetectionTelemetry/InteractiveCompletionQuery.txt
  20. 54 0
      adfsWebCustomization/pageDetectionTelemetry/LoginReliabilityByPlatformQuery.txt
  21. 74 0
      adfsWebCustomization/pageDetectionTelemetry/README.md
  22. 54 0
      adfsWebCustomization/pageDetectionTelemetry/UserPromptRateByPlatformQuery.txt
  23. 50 0
      adfsWebCustomization/pageDetectionTelemetry/UserPromptRateQuery.txt
  24. 376 0
      adfsWebCustomization/pageDetectionTelemetry/onload.js

+ 288 - 0
adfsWebCustomization/.gitignore

@@ -0,0 +1,288 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs

+ 21 - 0
adfsWebCustomization/LICENSE

@@ -0,0 +1,21 @@
+    MIT License
+
+    Copyright (c) Microsoft Corporation. All rights reserved.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE

+ 31 - 0
adfsWebCustomization/README.md

@@ -0,0 +1,31 @@
+# AD FS Web Customizations 
+
+## Overview 
+
+This repository contains useful web customizations for AD FS. The following customizations are currently included: 
+
+1. __[pageDetectionTelemetry](pageDetectionTelemetry)__ - JavaScript customization to detect AD FS pages and upload telemetry 
+to your [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) datastore. 
+
+2. __[centeredUi](centeredUi)__ - CSS customization to allow your on-prem AD FS to be consistent with the look-and-feel of the
+[centered Azure AD Sign-in](https://cloudblogs.microsoft.com/enterprisemobility/2017/08/02/the-new-azure-ad-signin-experience-is-now-in-public-preview/)
+
+3. __[mfaLoadingWheel](mfaLoadingWheel)__ - JavaScript customization to add a loading wheel to the AD FS authentication options page.
+
+4. __[communityCustomizations](communityCustomizations)__ - JavaScript customizations from community members.
+
+## Contributing
+
+This project welcomes contributions and suggestions. We encourage you to fork this project, include any web customizations
+you find useful, and then do a pull request to master. If your customizations work, we'll include them so everyone can benefit. 
+
+Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, 
+grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
+a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
+provided by the bot. You will only need to do this once across all repos using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

+ 77 - 0
adfsWebCustomization/centeredUi/README.md

@@ -0,0 +1,77 @@
+# Match Azure AD Centered Login Page 
+
+## Overview
+
+This project provides an Active Directory Federation Services (AD FS) style sheet to allow your AD FS login form to be consistent with the [new Azure Active Directory centered sign-in experience](https://cloudblogs.microsoft.com/enterprisemobility/2017/08/02/the-new-azure-ad-signin-experience-is-now-in-public-preview/).
+
+Note that this customization comes in two parts. The first is a style sheet, which allows the look-and-feel of your AD FS to match the Azure AD centered UI experience. The second is a more advanced customization, using the AD FS JavaScript customization feature to create a front-end paginated sign-in experience.
+
+## Getting Started
+
+We will break the deployment of this feature into two parts. First, the style sheet to create a consistent look-and-feel. Second, the JavaScript to create a front-end paginated experience. You can choose if you wish to deploy one or both.
+
+## Getting Started - Style Sheet Deployment
+
+1. Download the ```ThemeCenterBrand.css``` file to your AD FS server, wherever you host your style sheets.
+
+    Note: It is recommended that you minify your CSS for a production environment.
+
+2. Create a custom web theme using the following command in PowerShell:
+
+    ```New-AdfsWebTheme –Name custom -SourceName default –StyleSheet @{path="c:\style\ThemeCenterBrand.css"}```
+
+3. Apply the new custom web theme using the following command in PowerShell:
+
+    ```Set-AdfsWebConfig -ActiveThemeName custom```
+
+4. Update the logo and background image. For details and image size recommendations, see [this post](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/azure-ux-web-theme-in-ad-fs).
+
+## Getting Started - JavaScript Deployment
+
+1. Download the ```paginatedOnload.js``` file to your AD FS server, wherever you host your JavaScript.
+
+    Note: It is *__highly__* recommended that you minify your ```paginatedOnload.js``` before including it in a production environment. There are many popular tools online for minifying JavaScript code. Two popular choices are [minifier.org](http://www.minifier.org/) and [JSCompress](https://jscompress.com/).
+
+2. Modify your existing custom web theme from the style sheet deployment to include the new JavaScript. In PowerShell:
+
+    ```Set-AdfsWebTheme –TargetName custom -AdditionalFileResource @{Uri="/adfs/portal/script/onload.js"; path="c:\paginatedOnload.js"}```
+
+3. Apply the modified custom web theme using the following command in PowerShell:
+
+    ```Set-AdfsWebConfig -ActiveThemeName custom```
+
+4. For more information on JavaScript customization, see [Advanced AD FS Customization](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages).
+
+## Additional JavaScript Changes
+
+The JavaScript we provide out-of-the-box does not provide two key features you may want.
+
+1. The JavaScript works for deployments in most major languages. However, if you wish to have your pages work under other languages, you will need to follow the steps below in ```Supporting Non-English Languages```.
+
+2. The new paginated sign in contains a user avatar image. By default, this image is a standard empty user avatar (shown below). To support user avatar lookup, you will need to follow the steps below in ```Supporting User Avatar Lookup```.
+
+    ![Empty User](./images/empty_user.png)
+
+## Supporting Non-English Languages
+
+In order to support non-English languages, you will need to add translated text for the new UI items that are created by the JavaScript.
+
+In the code, you should locate the translation table in the function ```GetLocalizedStringForElement```. You should add translations for the text in the translation table.
+
+Each translation should be mapped to the correct language code. For a reference on language codes, see the ```ISO 639-1 Code``` column in the table at [this resource](https://www.loc.gov/standards/iso639-2/php/code_list.php).
+
+## Supporting User Avatar Lookup
+
+The Azure AD experience includes a user avatar on the password page, on the right side of the banner. AD FS does not have support for the concept of a user avatar, so it's not possible to include this logic out-of-the-box. If you wish to support this behavior, you will have to build a web API that accepts a username, and returns an image. You will then have to update the ```paginatedOnload.js``` code to make the request for the user avatar image, and handle the response.
+
+If you are interested in working with the AD FS Open Source community to build a user avatar web app, please contact mattbo@microsoft.com.
+
+## Example
+
+![Login Screenshot](./images/screenshot_paginated.png)
+
+## Contributing (Special Note)
+
+If you find any problems with the CSS, JavaScript, or docs, please fork and send us your fix. If you don't have a fix, please open an issue, and describe what you are seeing (feel free to include screenshots).
+
+For the full Contributing details, please see __[the root README](../README.md)__.

+ 855 - 0
adfsWebCustomization/centeredUi/ThemeCenterBrand.css

@@ -0,0 +1,855 @@
+* {
+    margin: 0px;
+    padding: 0px;
+}
+
+html, body {
+    height: 100%;
+    width: 100%;
+    background-color: #ffffff;
+    color: #000000;
+    font-weight: normal;
+    font-family: "Segoe UI Webfont",-apple-system,"Helvetica Neue","Lucida Grande","Roboto","Ebrima","Nirmala UI","Gadugi","Segoe Xbox Symbol","Segoe UI Symbol","Meiryo UI","Khmer UI","Tunga","Lao UI","Raavi","Iskoola Pota","Latha","Leelawadee","Microsoft YaHei UI","Microsoft JhengHei UI","Malgun Gothic","Estrangelo Edessa","Microsoft Himalaya","Microsoft New Tai Lue","Microsoft PhagsPa","Microsoft Tai Le","Microsoft Yi Baiti","Mongolian Baiti","MV Boli","Myanmar Text","Cambria Math";
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+}
+
+body {
+    font-size: 0.9em;
+}
+
+#noScript {
+    margin: 16px;
+    color: Black;
+}
+
+:lang(en-GB) {
+    quotes: '\2018' '\2019' '\201C' '\201D';
+}
+
+:lang(zh) {
+    font-family: 微软雅黑;
+}
+
+@-ms-viewport {
+    width: device-width;
+}
+
+@-moz-viewport {
+    width: device-width;
+}
+
+@-o-viewport {
+    width: device-width;
+}
+
+@-webkit-viewport {
+    width: device-width;
+}
+
+@viewport {
+    width: device-width;
+}
+
+/* Theme layout styles */
+
+#fullPage {
+    position: absolute;
+    bottom: 28px;
+    top: 0px;
+    width: 100%;
+}
+
+#brandingWrapper {
+    background-color: #4488dd;
+    height: 100%;
+    width: 100%;
+}
+
+#branding {
+    /* A background image will be added to the #branding element at run-time once the illustration image is configured in the theme. 
+       Recommended image dimensions: 1420x1200 pixels, JPG or PNG, 200 kB average, 500 kB maximum. */
+    background-color: inherit;
+    background-repeat: no-repeat;
+    background-size: cover;
+    height: 100%;
+    width: 100%;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+}
+
+#brandingTint {
+    /* This will define a tint to be overlaid on top of the illustration background image. */
+    background-color: rgba(0,0,0,0.4);
+    height: 100%;
+    width: 100%;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+}
+
+#contentWrapper {
+    background-color: transparent;
+    height: 0px;
+    width: 100%;
+}
+
+#content {
+    /* Set content to center */
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    
+    background-color: #fff;
+
+    /* Set size margins */
+    margin-bottom: 28px;
+    margin-left: auto;
+    margin-right: auto;
+    min-height: 290px;
+    min-width: 320px;
+    max-width: 412px;
+    width: 338px; /*calc(100% - 40px); */
+    height: auto;
+    padding: 36px;
+   
+    /* Add drop shadow */
+    box-shadow: 0 2px 3px rgba(0,0,0,0.55);
+    border: 1px solid rgba(0,0,0,0.4);
+}
+
+#header {
+    font-size: 2em;
+    font-weight: lighter;
+    font-family: "Segoe UI Webfont",-apple-system,"Helvetica Neue","Lucida Grande","Roboto","Ebrima","Nirmala UI","Gadugi","Segoe Xbox Symbol","Segoe UI Symbol","Meiryo UI","Khmer UI","Tunga","Lao UI","Raavi","Iskoola Pota","Latha","Leelawadee","Microsoft YaHei UI","Microsoft JhengHei UI","Malgun Gothic","Estrangelo Edessa","Microsoft Himalaya","Microsoft New Tai Lue","Microsoft PhagsPa","Microsoft Tai Le","Microsoft Yi Baiti","Mongolian Baiti","MV Boli","Myanmar Text","Cambria Math";
+    padding: 0px 0px 0px 0px;
+    margin: 0px;
+    height: 36px;
+    width: 338px;
+    background-color: transparent;
+}
+
+    #header img {
+        /* Logo image recommended dimension: 108x24  or 338x24 (elongated), 4 kB average, 10 kB maximum. Transparent PNG strongly recommended. */
+        width: auto;
+        height: 100%;
+        position: relative;
+        top: -7px;
+    }
+
+#loginMessage {
+    box-sizing: border-box;
+    color: rgb(38, 38, 38);
+    direction: ltr;
+    display: block;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-weight: 300;
+    font-size: 1.2rem;
+    height: auto;
+    line-height: 28px;
+    margin-bottom: 16px;
+    margin-left: -2px;
+    margin-right: -2px;
+    margin-top: 16px;
+    padding-bottom: 0px;
+    padding-left: 0px;
+    padding-right: 0px;
+    padding-top: 0px;
+    text-align: left;
+    text-size-adjust: 100%;
+    width: 342px;
+    background-color: transparent;
+}
+
+#loginForm {
+    width: 338px;
+}
+
+#workArea, #header {
+    word-wrap: break-word;
+}
+
+#workArea {
+    margin-bottom: 4%;
+    margin-top: 16px;
+    background-color: transparent;
+}
+
+#footerPlaceholder {
+    height: 0px;
+}
+
+#footer {
+    background-color: rgba(0, 0, 0, 0.6);
+    bottom: 0px;
+    box-sizing: border-box;
+    clear: both;
+    color: rgb(38, 38, 38);
+    direction: ltr;
+    display: block;
+    filter: none;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-size: 15px;
+    font-weight: normal;
+    height: 28px;
+    line-height: 20px;
+    overflow-x: visible;
+    overflow-y: visible;
+    position: fixed;
+    text-align: left;
+    text-size-adjust: 100%;
+    width: 100%;
+}
+
+#footerLinks {
+    padding-left: 10px;
+}
+
+#copyright {
+    box-sizing: border-box;
+    color: rgb(255, 255, 255);
+    direction: ltr;
+    display: inline-block;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-size: 12px;
+    font-weight: normal;
+    height: 28px;
+    line-height: 28px;
+    margin-left: 8px;
+    margin-right: 8px;
+    text-align: left;
+    text-size-adjust: 100%;
+}
+
+#userNameArea, #passwordArea {
+    box-sizing: border-box;
+    color: rgb(38, 38, 38);
+    direction: ltr;
+    display: block;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-size: 15px;
+    font-weight: normal;
+    height: 52px;
+    line-height: 20px;
+    margin-left: 0px;
+    margin-right: -2px;
+    text-align: left;
+    text-size-adjust: 100%;
+    width: 342px;
+}
+
+#updatePasswordForm #submitButton, #cancelButton {
+    width: 48%;
+} 
+
+#oldPasswordArea, #newPasswordArea {
+    margin-bottom: 7px;
+}
+
+#errorMessage{
+    margin-top: 5px;
+}
+
+.pageLink {
+    padding-left: 5px;
+    padding-right: 5px;
+    box-sizing: border-box;
+    color: rgb(0, 0, 0);
+    direction: ltr;
+    display: inline-block;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-size: 12px;
+    font-weight: normal;
+    height: 28px;
+    line-height: 28px;
+    margin-left: 0px;
+    margin-right: 0px;
+    text-align: left;
+    text-size-adjust: 100%;
+    text-decoration: underline;
+}
+
+/* Common content styles */
+
+.clear {
+    clear: both;
+}
+
+.float {
+    float: left;
+}
+
+.floatReverse {
+    float: right;
+}
+
+.indent {
+    margin-left: 16px;
+}
+
+.indentNonCollapsible {
+    padding-left: 16px;
+}
+
+.hidden {
+    display: none;
+}
+
+.notHidden {
+    display: inherit;
+}
+
+.error {
+    color: #e81123;
+}
+
+.actionLink {
+    margin-top: 5px;
+    margin-bottom: 8px;
+    display: block;
+}
+
+a {
+    color: #0067b8;
+    text-decoration: none;
+    background-color: transparent;
+    word-wrap: normal;
+}
+
+ul {
+    list-style-type: disc;
+}
+
+ul, ol, dd {
+    padding: 0 0 0 16px;
+}
+
+h1, h2, h3, h4, h5, label {
+    margin-bottom: 8px;
+}
+
+.submitMargin {
+    margin-top: 18px;
+    margin-bottom: 18px;
+}
+
+.topFieldMargin {
+    margin-top: 8px;
+}
+
+.fieldMargin {
+    margin-bottom: 8px;
+}
+
+.groupMargin {
+    margin-bottom: 0px;
+}
+
+.sectionMargin {
+    margin-bottom: 64px;
+}
+
+.block {
+    display: block;
+}
+
+.autoWidth {
+    width: auto;
+}
+
+.fullWidth {
+    width: 342px;
+}
+
+.fullWidthIndent {
+    width: 326px;
+}
+
+.smallTopSpacing {
+    margin-top: 15px;
+}
+
+.mediumTopSpacing {
+    margin-top: 25px;
+}
+
+.largeTopSpacing {
+    margin-top: 35px;
+}
+
+.smallBottomSpacing {
+    margin-bottom: 5px;
+}
+
+.mediumBottomSpacing {
+    margin-bottom: 15px;
+}
+
+.largeBottomSpacing {
+    margin-bottom: 25px;
+}
+
+#openingMessage {
+    margin-bottom: 4px;
+}
+
+input {
+    max-width: 100%;
+    font-family: inherit;
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+input[type="radio"], input[type="checkbox"] {
+    vertical-align: middle;
+    margin-bottom: 0px;
+}
+
+    span.submit, input[type="submit"] {
+        align-items: flex-start;
+        background-color: rgb(0, 103, 184);
+        border-bottom-color: rgb(0, 103, 184);
+        border-bottom-style: solid;
+        border-bottom-width: 1px;
+        border-image-outset: 0px;
+        border-image-repeat: stretch;
+        border-image-slice: 100%;
+        border-image-source: none;
+        border-image-width: 1;
+        border-left-color: rgb(0, 103, 184);
+        border-left-style: solid;
+        border-left-width: 1px;
+        border-right-color: rgb(0, 103, 184);
+        border-right-style: solid;
+        border-right-width: 1px;
+        border-top-color: rgb(0, 103, 184);
+        border-top-style: solid;
+        border-top-width: 1px;
+        box-sizing: border-box;
+        color: rgb(255, 255, 255);
+        cursor: pointer;
+        direction: ltr;
+        display: inline-block;
+        font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+        font-size: 15px;
+        font-stretch: normal;
+        font-style: normal;
+        font-variant-caps: normal;
+        font-variant-ligatures: normal;
+        font-variant-numeric: normal;
+        font-weight: normal;
+        height: 36px;
+        letter-spacing: normal;
+        line-height: 25px;
+        margin-bottom: 0px;
+        margin-left: 0px;
+        margin-right: 0px;
+        margin-top: 0px;
+        max-width: 100%;
+        min-width: 165px;
+        overflow-x: hidden;
+        overflow-y: hidden;
+        padding-bottom: 4px;
+        padding-left: 12px;
+        padding-right: 12px;
+        padding-top: 4px;
+        position: relative;
+        text-align: center;
+        text-indent: 0px;
+        text-overflow: ellipsis;
+        text-rendering: auto;
+        text-shadow: none;
+        text-size-adjust: 100%;
+        touch-action: manipulation;
+        user-select: none;
+        vertical-align: middle;
+        white-space: nowrap;
+        width: 100%;
+        word-spacing: 0px;
+        writing-mode: horizontal-tb;
+        -webkit-appearance: none;
+        -webkit-rtl-ordering: logical;
+        -webkit-border-image: none;
+    }
+
+    input[type="submit"]:hover, span.submit:hover {
+        background: rgb(0, 85, 152);
+        border: 1px solid rgb(0, 85, 152);
+    }    
+
+
+input.text {
+    background-color: rgba(255, 255, 255, 0.4);
+    background-image: none;
+    border-bottom-color: rgba(0, 0, 0, 0.6);
+    border-bottom-style: solid;
+    border-bottom-width: 1px;
+    border-image-outset: 0px;
+    border-image-repeat: stretch;
+    border-image-slice: 100%;
+    border-image-source: none;
+    border-image-width: 1;
+    border-left-color: rgba(0, 0, 0, 0.6);
+    border-left-style: solid;
+    border-left-width: 1px;
+    border-right-color: rgba(0, 0, 0, 0.6);
+    border-right-style: solid;
+    border-right-width: 1px;
+    border-top-color: rgba(0, 0, 0, 0.6);
+    border-top-style: solid;
+    border-top-width: 1px;
+    box-sizing: border-box;
+    color: rgb(38, 38, 38);
+    cursor: auto;
+    direction: ltr;
+    display: block;
+    font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+    font-size: 15px;
+    font-stretch: normal;
+    font-style: normal;
+    font-variant-caps: normal;
+    font-variant-ligatures: normal;
+    font-variant-numeric: normal;
+    font-weight: normal;
+    height: 36px;
+    letter-spacing: normal;
+    line-height: 20px;
+    margin-bottom: 0px;
+    margin-left: 0px;
+    margin-right: 0px;
+    margin-top: 0px;
+    max-width: 100%;
+    outline-color: rgb(38, 38, 38);
+    outline-style: none;
+    outline-width: 0px;
+    padding-bottom: 6px;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 6px;
+    text-align: start;
+    text-indent: 0px;
+    text-rendering: auto;
+    text-shadow: none;
+    text-size-adjust: 100%;
+    text-transform: none;
+    user-select: text;
+    width: 338px;
+    word-spacing: 0px;
+    writing-mode: horizontal-tb;
+    -webkit-appearance: none;
+    -webkit-locale: "en";
+    -webkit-rtl-ordering: logical;
+    -webkit-border-image: none;
+}
+
+input.text:focus {
+    border: 1px solid #6B6B6B;
+}
+
+select {
+    height: 28px;
+    min-width: 60px;
+    max-width: 100%;
+    margin-bottom: 8px;
+    white-space: nowrap;
+    overflow: hidden;
+    box-shadow: none;
+    padding: 2px;
+    font-family: inherit;
+}
+
+h1, .giantText {
+    font-size: 2.0em;
+    font-weight: lighter;
+}
+
+h2, .bigText {
+    font-size: 1.33em;
+    font-weight: lighter;
+}
+
+h3, .normalText {
+    font-size: 1.0em;
+    font-weight: normal;
+}
+
+h4, .smallText {
+    font-size: 0.9em;
+    font-weight: normal;
+}
+
+h4 {
+    font-size: 0.7em;
+    font-weight: normal;
+}
+
+h5, .tinyText {
+    font-size: 0.8em;
+    font-weight: normal;
+}
+
+.hint {
+    color: #999999;
+}
+
+.emphasis {
+    font-weight: 700;
+    color: #2F2F2F;
+}
+
+.smallIcon {
+    height: 20px;
+    padding-right: 12px;
+    vertical-align: middle;
+}
+
+.largeIcon {
+    height: 48px;
+    /* width:48px; */
+    vertical-align: middle;
+}
+
+.largeTextNoWrap {
+    height: 48px;
+    display: table-cell; /* needed when in float*/
+    vertical-align: middle;
+    white-space: nowrap;
+    font-size: 1.2em;
+}
+
+.idp {
+    height: 48px;
+    clear: both;
+    padding: 8px;
+    overflow: hidden;
+    cursor: pointer;
+}
+
+    .idp:hover {
+        background-color: #cccccc;
+        cursor: pointer;
+    }
+
+.idpDescription {
+    width: 80%;
+    cursor: pointer;
+}
+
+.identityBanner{
+    color: black;
+    background: #F2F2F2;
+    text-align: right;
+    white-space: nowrap;
+    line-height: 28px;
+    height: 28px;
+    margin:16px -36px;
+    padding:0px 100px 0px 40px;
+    font-family: "Segoe UI Webfont",-apple-system,"Helvetica Neue","Lucida Grande","Roboto","Ebrima","Nirmala UI","Gadugi","Segoe Xbox Symbol","Segoe UI Symbol","Meiryo UI","Khmer UI","Tunga","Lao UI","Raavi","Iskoola Pota","Latha","Leelawadee","Microsoft YaHei UI","Microsoft JhengHei UI","Malgun Gothic","Estrangelo Edessa","Microsoft Himalaya","Microsoft New Tai Lue","Microsoft PhagsPa","Microsoft Tai Le","Microsoft Yi Baiti","Mongolian Baiti","MV Boli","Myanmar Text","Cambria Math";
+    font-size: 15px;
+    font-weight: 300;
+    white-space: nowrap;
+    overflow: hidden;
+    -o-text-overflow: ellipsis;
+    text-overflow: ellipsis;    
+}
+.identityBannerImage {
+ height:48px;
+ width:48px;
+ margin-top:-17px;
+ margin-right: -26px;
+ overflow:hidden;
+ position: relative;
+ float: right;
+ top: -48px;
+ left: -33px;
+}
+
+.submit.backButton{
+    color: black;
+    width: 165px;
+    float: left;
+    background: #CCCCCC;
+    border-color: #CCCCCC;
+    margin-left: -2px;
+}
+  
+    input[type="submit"].backButton:hover, span.submit.backButton:hover {
+        background: #AAA;
+        border: 1px solid #AAA;
+    }
+
+.submit.nextButton{
+    margin-left: -2px;
+}
+
+.submitMargin.submitModified {
+    margin-bottom: 60px;
+}
+
+.submit.modifiedSignIn{
+    display: block;
+    width: auto;
+    float: right;
+}
+
+#submissionArea{
+    margin-top: 8px;
+}
+
+@media (max-width: 600px), (max-height: 392px){
+    #content {
+        /* Set content to center */
+        position: relative;
+        top: 0;
+        left: 0;
+        transform: none;
+        background-color: #fff;
+        /* Set size margins */
+        margin-bottom: 28px;
+        margin-left: auto;
+        margin-right: auto;
+        min-height: 290px;
+        min-width: 320px;
+        max-width: 412px;
+        width: calc(100% - 40px);
+        height: auto;
+        padding: 23px 18px 0px 18px;
+        /* Add drop shadow */
+        box-shadow: 0 0 0 rgba(0,0,0,0);
+        border: 0px solid rgba(0,0,0,0);
+    }
+
+    #footer {
+        background-color: rgba(0, 0, 0, 0.6);
+        bottom: 0px;
+        box-sizing: border-box;
+        clear: both;
+        color: rgb(38, 38, 38);
+        direction: ltr;
+        display: block;
+        filter: none;
+        font-family: "Segoe UI Webfont", -apple-system, "Helvetica Neue", "Lucida Grande", Roboto, Ebrima, "Nirmala UI", Gadugi, "Segoe Xbox Symbol", "Segoe UI Symbol", "Meiryo UI", "Khmer UI", Tunga, "Lao UI", Raavi, "Iskoola Pota", Latha, Leelawadee, "Microsoft YaHei UI", "Microsoft JhengHei UI", "Malgun Gothic", "Estrangelo Edessa", "Microsoft Himalaya", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Tai Le", "Microsoft Yi Baiti", "Mongolian Baiti", "MV Boli", "Myanmar Text", "Cambria Math";
+        font-size: 15px;
+        font-weight: normal;
+        height: 28px;
+        line-height: 20px;
+        overflow-x: visible;
+        overflow-y: visible;
+        position: fixed;
+        text-align: left;
+        text-size-adjust: 100%;
+        width: 100%;
+    }
+
+    #brandingWrapper {
+        display: none;
+    }
+
+    input.text {
+        font-size: 16px;
+    }
+    
+    .identityBanner {
+        margin:16px 0px;
+        padding:0px 80px 0px 40px;    
+    }
+    
+    .identityBannerImage {
+        left: -50px;   
+    }
+}
+
+/* Targets displays using any of Windows’ High Contrast Mode themes: */
+@media screen and (-ms-high-contrast: active) {
+    textarea::-webkit-input-placeholder {
+        color: #00FF00;
+    }
+
+    textarea:-moz-placeholder { /* Firefox 18- */
+        color: #00FF00;
+    }
+
+    textarea::-moz-placeholder { /* Firefox 19+ */
+        color: #00FF00;
+    }
+
+    textarea:-ms-input-placeholder {
+        color: #00FF00;
+    }
+}
+
+/* Targets displays using the Windows’ "High Contrast Black" theme: */
+@media screen and (-ms-high-contrast: white-on-black) {
+    #contentWrapper {
+        background-color: #000000;
+        color: #ffffff;
+    }
+    .idp:hover {
+        background-color: #ffffff;
+        color: #000000;
+    }
+    #brandingWrapper {
+        background-color: #000000;
+        color: #ffffff;
+    }
+    html, body {
+        background-color: #000000;
+        color: #ffffff;
+    }
+
+    textarea::-webkit-input-placeholder {
+        color: #ffffff;
+    }
+
+    textarea:-moz-placeholder { /* Firefox 18- */
+        color: #ffffff;
+    }
+
+    textarea::-moz-placeholder { /* Firefox 19+ */
+        color: #ffffff;
+    }
+
+    textarea:-ms-input-placeholder {
+        color: #ffffff;
+    }
+}
+
+/* Targets displays using the Windows’ "High Contrast White" theme: */
+@media screen and (-ms-high-contrast: black-on-white) {
+    #contentWrapper {
+        background-color: #ffffff;
+        color: #000000;
+    }
+
+    .idp:hover {
+        background-color: #000000;
+        color: #ffffff;
+    }
+
+    #brandingWrapper {
+        background-color: #ffffff;
+        color: #000000;
+    }
+
+    html, body {
+        background-color: #ffffff;
+        color: #000000;
+    }
+
+    textarea::-webkit-input-placeholder {
+        color: #000000;
+    }
+
+    textarea:-moz-placeholder { /* Firefox 18- */
+        color: #000000;
+    }
+
+    textarea::-moz-placeholder { /* Firefox 19+ */
+        color: #000000;
+    }
+
+    textarea:-ms-input-placeholder {
+        color: #000000;
+    }
+}

BIN
adfsWebCustomization/centeredUi/images/empty_user.png


BIN
adfsWebCustomization/centeredUi/images/screenshot.png


BIN
adfsWebCustomization/centeredUi/images/screenshot_paginated.png


+ 883 - 0
adfsWebCustomization/centeredUi/paginatedOnload.js

@@ -0,0 +1,883 @@
+// IE doesn't support "startsWith", adding definition
+if (!String.prototype.startsWith) {
+    String.prototype.startsWith = function(searchString, position){
+        position = position || 0;
+        return this.substr(position, searchString.length) === searchString;
+    };
+}
+
+function PaginatedNext()
+{
+    // NOTE: You can add any custom navigation logic you want here. 
+
+    // Check that a username was entered correctly 
+    var u = new InputUtil();
+    var e = new LoginErrors();
+    var usernameInput = document.getElementById('userNameInput');
+    if (!usernameInput.value || !usernameInput.value.match('[@\\\\]')) {
+        u.setError(usernameInput, e.userNameFormatError);
+        return false;
+    }
+    
+    ShowPasswordPage();
+}
+
+function PaginatedBack()
+{
+    // NOTE: You can add any custom navigation logic you want here. 
+    
+    ShowUsernamePage();
+}
+
+function AdjustElementDisplay(elementList, display)
+{
+    for ( var i = 0; i < elementList.length; i++ )
+    {
+        if ( elementList[i] && elementList[i].style )
+        {
+            elementList[i].style.display = display;
+        }
+    }
+}
+
+function GetLocalizedStringForElement(element)
+{
+    // LOCALIZATION NOTE: The following table allows for the translation of the text items created 
+    //  within this JavaScript. Elements created client-side are not localized by ADFS, so we must 
+    //  localize the text ourselves. Admins can add additional translations to this table
+    var translationTable = {
+            "ms":  {
+                       "backButton":  "Ke belakang",
+                       "nextButton":  "Seterusnya",
+                       "loginMessage":  "Masukkan kata laluan"
+                   },
+            "gl":  {
+                       "backButton":  "Atrás",
+                       "nextButton":  "Seguinte",
+                       "loginMessage":  "Introducir contrasinal"
+                   },
+            "gu":  {
+                       "backButton":  "પાછળ",
+                       "nextButton":  "આગલું",
+                       "loginMessage":  "પાસવર્ડ દાખલ કરો"
+                   },
+            "km":  {
+                       "backButton":  "ថយក្រោយ",
+                       "nextButton":  "បន្ទាប់",
+                       "loginMessage":  "បញ្ចូលពាក្យសម្ងាត់"
+                   },
+            "ig":  {
+                       "backButton":  "Àzụ",
+                       "nextButton":  "Osote",
+                       "loginMessage":  "Tinye okwuntụghe"
+                   },
+            "uz":  {
+                       "backButton":  "Orqaga",
+                       "nextButton":  "Keyingisi",
+                       "loginMessage":  "Parolni kiriting"
+                   },
+            "sv":  {
+                       "backButton":  "Bakåt",
+                       "nextButton":  "Nästa",
+                       "loginMessage":  "Ange lösenord"
+                   },
+            "mi":  {
+                       "backButton":  "Hoki",
+                       "nextButton":  "Panuku",
+                       "loginMessage":  "Tāuru kupuhipa"
+                   },
+            "rw":  {
+                       "backButton":  "Gusubira inyuma",
+                       "nextButton":  "Komeza",
+                       "loginMessage":  "Andika ijambobanga"
+                   },
+            "lb":  {
+                       "backButton":  "Zréck",
+                       "nextButton":  "Nächst",
+                       "loginMessage":  "Passwuert aginn"
+                   },
+            "ku-Arab":  {
+                            "backButton":  "دواوە",
+                            "nextButton":  "داهاتوو",
+                            "loginMessage":  "لێدانی تێپەرەوشە"
+                        },
+            "yo":  {
+                       "backButton":  "Padàsẹ́yìn",
+                       "nextButton":  "Tókàn",
+                       "loginMessage":  "Ṣàtẹ̀wọlé ọ̀rọ̀ aṣínà"
+                   },
+            "am":  {
+                       "backButton":  "ወደኋላ",
+                       "nextButton":  "ቀጣይ",
+                       "loginMessage":  "የይለፍ ቃል ያስገቡ"
+                   },
+            "es-MX":  {
+                          "backButton":  "Atrás",
+                          "nextButton":  "Siguiente",
+                          "loginMessage":  "Escriba la contraseña"
+                      },
+            "ur":  {
+                       "backButton":  "واپس",
+                       "nextButton":  "اگلا",
+                       "loginMessage":  "پاس ورڈ درج کریں"
+                   },
+            "quc":  {
+                        "backButton":  "Tzalijsab\u0027al",
+                        "nextButton":  "Teren chi uloq",
+                        "loginMessage":  "Utz\u0027ib\u0027axik retokib\u0027al"
+                    },
+            "sl":  {
+                       "backButton":  "Nazaj",
+                       "nextButton":  "Naprej",
+                       "loginMessage":  "Vnesite geslo"
+                   },
+            "pa-Arab-PK":  {
+                               "backButton":  "پچھے جاؤ",
+                               "nextButton":  "آگے",
+                               "loginMessage":  "پاس ورڈ داخل کرو"
+                           },
+            "tk":  {
+                       "backButton":  "Yza",
+                       "nextButton":  "Indiki",
+                       "loginMessage":  "Parol giriz"
+                   },
+            "te":  {
+                       "backButton":  "వెనుకకు",
+                       "nextButton":  "తదుపరి",
+                       "loginMessage":  "పాస్‌వర్డ్‌ను నమోదు చేయండి"
+                   },
+            "ro":  {
+                       "backButton":  "Înapoi",
+                       "nextButton":  "Următorul",
+                       "loginMessage":  "Introduceți parola"
+                   },
+            "en":  {
+                          "backButton":  "Back",
+                          "nextButton":  "Next",
+                          "loginMessage":  "Enter password"
+                      },
+            "zh-hans":  {
+                            "backButton":  "后退",
+                            "nextButton":  "下一步",
+                            "loginMessage":  "输入密码"
+                        },
+            "ha":  {
+                       "backButton":  "Baya",
+                       "nextButton":  "Na gaba",
+                       "loginMessage":  "Shigar da kalmar sirri"
+                   },
+            "mt":  {
+                       "backButton":  "Lura",
+                       "nextButton":  "Li Jmiss",
+                       "loginMessage":  "Daħħal il-password"
+                   },
+            "tn":  {
+                       "backButton":  "Morago",
+                       "nextButton":  "Latelang",
+                       "loginMessage":  "Tsenya khunololamoraba"
+                   },
+            "mn":  {
+                       "backButton":  "Буцах",
+                       "nextButton":  "Дараах",
+                       "loginMessage":  "Нууц үг оруулах"
+                   },
+            "pa-IN":  {
+                          "backButton":  "ਪਿੱਛੇ ਜਾਓ",
+                          "nextButton":  "ਅਗਲਾ",
+                          "loginMessage":  "ਪਾਸਵਰਡ ਦਾਖ਼ਲ ਕਰੋ"
+                      },
+            "bn-IN":  {
+                          "backButton":  "ফিরে যান",
+                          "nextButton":  "পরবর্তী",
+                          "loginMessage":  "পাসওয়ার্ড লিখুন"
+                      },
+            "kok":  {
+                        "backButton":  "फाटीं व्हरचें",
+                        "nextButton":  "फुडलें",
+                        "loginMessage":  "पासवर्ड नोंद करचो"
+                    },
+            "id":  {
+                       "backButton":  "Kembali",
+                       "nextButton":  "Selanjutnya",
+                       "loginMessage":  "Masukkan sandi"
+                   },
+            "bg":  {
+                       "backButton":  "Назад",
+                       "nextButton":  "Напред",
+                       "loginMessage":  "Въведете парола"
+                   },
+            "da":  {
+                       "backButton":  "Tilbage",
+                       "nextButton":  "Næste",
+                       "loginMessage":  "Indtast adgangskode"
+                   },
+            "az":  {
+                       "backButton":  "Geri",
+                       "nextButton":  "Növbəti",
+                       "loginMessage":  "Parol daxil edin"
+                   },
+            "mk":  {
+                       "backButton":  "Назад",
+                       "nextButton":  "Следно",
+                       "loginMessage":  "Внесете ја лозинката"
+                   },
+            "mr":  {
+                       "backButton":  "मागे",
+                       "nextButton":  "पुढे",
+                       "loginMessage":  "पासवर्ड प्रविष्ठ करा"
+                   },
+            "kk":  {
+                       "backButton":  "Артқа",
+                       "nextButton":  "Келесі",
+                       "loginMessage":  "Құпия сөзді енгізіңіз"
+                   },
+            "ml":  {
+                       "backButton":  "മടങ്ങുക",
+                       "nextButton":  "അടുത്തത്",
+                       "loginMessage":  "പാസ്‌വേഡ് നൽകുക"
+                   },
+            "xh":  {
+                       "backButton":  "Emva",
+                       "nextButton":  "Okulandelayo",
+                       "loginMessage":  "Ngenisa iphaswedi"
+                   },
+            "gd":  {
+                       "backButton":  "Air ais",
+                       "nextButton":  "Air adhart",
+                       "loginMessage":  "Cuir a-steach am facal-faire"
+                   },
+            "as":  {
+                       "backButton":  "পিছলৈ যাওক",
+                       "nextButton":  "পৰৱৰ্তী",
+                       "loginMessage":  "পাছৱৰ্ড প্ৰৱিষ্ট কৰক"
+                   },
+            "tr":  {
+                       "backButton":  "Geri",
+                       "nextButton":  "İleri",
+                       "loginMessage":  "Parola girin"
+                   },
+            "is":  {
+                       "backButton":  "Til baka",
+                       "nextButton":  "Áfram",
+                       "loginMessage":  "Færa inn aðgangsorð"
+                   },
+            "fa":  {
+                       "backButton":  "برگشت",
+                       "nextButton":  "بعدی",
+                       "loginMessage":  "رمز عبور را وارد کنید"
+                   },
+            "ga":  {
+                       "backButton":  "Siar",
+                       "nextButton":  "Ar aghaidh",
+                       "loginMessage":  "Iontráil an pasfhocal"
+                   },
+            "sr-Cyrl-BA":  {
+                               "backButton":  "Назад",
+                               "nextButton":  "Даље",
+                               "loginMessage":  "Унесите лозинку"
+                           },
+            "tg":  {
+                       "backButton":  "Бозгашт",
+                       "nextButton":  "Навбатӣ",
+                       "loginMessage":  "Паролро дохил кунед"
+                   },
+            "or":  {
+                       "backButton":  "ପଶ୍ଚ୍ୟାତ୍",
+                       "nextButton":  "ପରବର୍ତ୍ତୀ",
+                       "loginMessage":  "ପାସ୍‌ୱାର୍ଡ୍‌ ଏଣ୍ଟର୍‌ କରନ୍ତୁ"
+                   },
+            "ru":  {
+                       "backButton":  "Назад",
+                       "nextButton":  "Далее",
+                       "loginMessage":  "Введите пароль"
+                   },
+            "sq":  {
+                       "backButton":  "Prapa",
+                       "nextButton":  "Tjetër",
+                       "loginMessage":  "Fut fjalëkalimin"
+                   },
+            "he":  {
+                       "backButton":  "הקודם",
+                       "nextButton":  "הבא",
+                       "loginMessage":  "הזן סיסמה"
+                   },
+            "ug":  {
+                       "backButton":  "قايتىش",
+                       "nextButton":  "كېيىنكى",
+                       "loginMessage":  "پارول كىرگۈزۈڭ"
+                   },
+            "eu":  {
+                       "backButton":  "Atzera",
+                       "nextButton":  "Hurrengoa",
+                       "loginMessage":  "Idatzi pasahitza"
+                   },
+            "wo":  {
+                       "backButton":  "Dellu",
+                       "nextButton":  "Li ci topp",
+                       "loginMessage":  "Dugalal baatu-jàll bi"
+                   },
+            "no":  {
+                       "backButton":  "Tilbake",
+                       "nextButton":  "Neste",
+                       "loginMessage":  "Skriv inn passord"
+                   },
+            "es":  {
+                       "backButton":  "Atrás",
+                       "nextButton":  "Siguiente",
+                       "loginMessage":  "Escribir contraseña "
+                   },
+            "pt-BR":  {
+                          "backButton":  "Voltar",
+                          "nextButton":  "Avançar",
+                          "loginMessage":  "Insira a senha"
+                      },
+            "bn-BD":  {
+                          "backButton":  "ফিরুন",
+                          "nextButton":  "পরবর্তী",
+                          "loginMessage":  "পাসওয়ার্ড লিখুন"
+                      },
+            "hy":  {
+                       "backButton":  "Հետ",
+                       "nextButton":  "Հաջորդը",
+                       "loginMessage":  "Մուտքագրեք գաղտնաբառը"
+                   },
+            "zh-hant":  {
+                            "backButton":  "返回",
+                            "nextButton":  "下一步",
+                            "loginMessage":  "輸入密碼"
+                        },
+            "vi":  {
+                       "backButton":  "Quay lại",
+                       "nextButton":  "Tiếp theo",
+                       "loginMessage":  "Nhập mật khẩu"
+                   },
+            "sr-cyrl-RS":  {
+                               "backButton":  "Назад",
+                               "nextButton":  "Даље",
+                               "loginMessage":  "Унесите лозинку"
+                           },
+            "sr-Latn-RS":  {
+                               "backButton":  "Nazad",
+                               "nextButton":  "Dalje",
+                               "loginMessage":  "Unesite lozinku"
+                           },
+            "nl":  {
+                       "backButton":  "Vorige",
+                       "nextButton":  "Volgende",
+                       "loginMessage":  "Wachtwoord invoeren"
+                   },
+            "th":  {
+                       "backButton":  "ย้อนกลับ",
+                       "nextButton":  "ถัดไป",
+                       "loginMessage":  "ใส่รหัสผ่าน"
+                   },
+            "lt":  {
+                       "backButton":  "Atgal",
+                       "nextButton":  "Tolyn",
+                       "loginMessage":  "Įveskite slaptažodį"
+                   },
+            "ja":  {
+                       "backButton":  "戻る",
+                       "nextButton":  "次へ",
+                       "loginMessage":  "パスワードの入力"
+                   },
+            "ko":  {
+                       "backButton":  "뒤로",
+                       "nextButton":  "다음",
+                       "loginMessage":  "암호 입력"
+                   },
+            "it":  {
+                       "backButton":  "Indietro",
+                       "nextButton":  "Avanti",
+                       "loginMessage":  "Immettere la password"
+                   },
+            "el":  {
+                       "backButton":  "Πίσω",
+                       "nextButton":  "Επόμενο",
+                       "loginMessage":  "Εισαγάγετε κωδικό πρόσβασης"
+                   },
+            "pt-PT":  {
+                          "backButton":  "Anterior",
+                          "nextButton":  "Seguinte",
+                          "loginMessage":  "Introduzir palavra-passe"
+                      },
+            "kn":  {
+                       "backButton":  "ಹಿಂದಕ್ಕೆ",
+                       "nextButton":  "ಮುಂದೆ",
+                       "loginMessage":  "ಪಾಸ್‌ವರ್ಡ್ ನಮೂದಿಸಿ"
+                   },
+            "de":  {
+                       "backButton":  "Zurück",
+                       "nextButton":  "Weiter",
+                       "loginMessage":  "Kennwort eingeben"
+                   },
+            "ne":  {
+                       "backButton":  "पछाडि जानुहोस्",
+                       "nextButton":  "अर्को",
+                       "loginMessage":  "पासवर्ड प्रविष्ट गर्नुहोस्"
+                   },
+            "sd":  {
+                       "backButton":  "واپس",
+                       "nextButton":  "اڳيون",
+                       "loginMessage":  "پاسورڊ داخل ڪريو"
+                   },
+            "ky":  {
+                       "backButton":  "Артка",
+                       "nextButton":  "Кийинки",
+                       "loginMessage":  "Сырсөз киргизүү"
+                   },
+            "ar":  {
+                       "backButton":  "الخلف",
+                       "nextButton":  "التالي",
+                       "loginMessage":  "أدخل كلمة المرور"
+                   },
+            "hi":  {
+                       "backButton":  "वापस जाएँ",
+                       "nextButton":  "अगला",
+                       "loginMessage":  "पासवर्ड दर्ज करें"
+                   },
+            "quz":  {
+                        "backButton":  "Qhipa",
+                        "nextButton":  "Qatiq",
+                        "loginMessage":  "Kichanata qillqay"
+                    },
+            "ka":  {
+                       "backButton":  "უკან",
+                       "nextButton":  "შემდეგი",
+                       "loginMessage":  "შეიყვანეთ პაროლი"
+                   },
+            "af":  {
+                       "backButton":  "Terug",
+                       "nextButton":  "Volgende",
+                       "loginMessage":  "Voer wagwoord in"
+                   },
+            "et":  {
+                       "backButton":  "Tagasi",
+                       "nextButton":  "Edasi",
+                       "loginMessage":  "Sisestage parool"
+                   },
+            "pl":  {
+                       "backButton":  "Wstecz",
+                       "nextButton":  "Dalej",
+                       "loginMessage":  "Wprowadź hasło"
+                   },
+            "ta":  {
+                       "backButton":  "பின் செல்",
+                       "nextButton":  "அடுத்து",
+                       "loginMessage":  "கடவுச்சொல்லை உள்ளிடவும்"
+                   },
+            "prs":  {
+                        "backButton":  "بازگشت",
+                        "nextButton":  "بعدی",
+                        "loginMessage":  "رمزعبور را وارد کنید"
+                    },
+            "tt":  {
+                       "backButton":  "Артка",
+                       "nextButton":  "Алга",
+                       "loginMessage":  "Серсүзне кертү"
+                   },
+            "fr":  {
+                       "backButton":  "Précédent",
+                       "nextButton":  "Suivant",
+                       "loginMessage":  "Entrez le mot de passe"
+                   },
+            "be":  {
+                       "backButton":  "Назад",
+                       "nextButton":  "Наступны",
+                       "loginMessage":  "Увядзіце пароль"
+                   },
+            "hr":  {
+                       "backButton":  "Natrag",
+                       "nextButton":  "Dalje",
+                       "loginMessage":  "Unesite lozinku"
+                   },
+            "zu":  {
+                       "backButton":  "Emuva",
+                       "nextButton":  "Okulandelayo",
+                       "loginMessage":  "Faka iphasiwedi"
+                   },
+            "sk":  {
+                       "backButton":  "Späť",
+                       "nextButton":  "Ďalej",
+                       "loginMessage":  "Zadajte heslo"
+                   },
+            "bs":  {
+                       "backButton":  "Nazad",
+                       "nextButton":  "Dalje",
+                       "loginMessage":  "Unesite lozinku"
+                   },
+            "fi":  {
+                       "backButton":  "Edellinen",
+                       "nextButton":  "Seuraava",
+                       "loginMessage":  "Anna salasana"
+                   },
+            "lo":  {
+                       "backButton":  "ກັບຄືນ",
+                       "nextButton":  "ຖັດໄປ",
+                       "loginMessage":  "ໃສ່ລະຫັດຜ່ານ"
+                   },
+            "lv":  {
+                       "backButton":  "Atpakaļ",
+                       "nextButton":  "Tālāk",
+                       "loginMessage":  "Ievadīt paroli"
+                   },
+            "fil":  {
+                        "backButton":  "Itim",
+                        "nextButton":  "Susunod",
+                        "loginMessage":  "Ipasok ang password"
+                    },
+            "ti":  {
+                       "backButton":  "ድሕሪት",
+                       "nextButton":  "ቀጻሊ",
+                       "loginMessage":  "መሕለፊ ቃል የእትው"
+                   },
+            "cy":  {
+                       "backButton":  "Yn ôl",
+                       "nextButton":  "Nesaf",
+                       "loginMessage":  "Rhowch gyfrinair"
+                   },
+            "si":  {
+                       "backButton":  "ආපසු",
+                       "nextButton":  "ඊළඟ",
+                       "loginMessage":  "මුරපදය ඇතුළු කරන්න"
+                   },
+            "sw":  {
+                       "backButton":  "Nyuma",
+                       "nextButton":  "Ifuatayo",
+                       "loginMessage":  "Ingiza nywila"
+                   },
+            "fr-CA":  {
+                          "backButton":  "Précédent",
+                          "nextButton":  "Suivant",
+                          "loginMessage":  "Entrer le mot de passe"
+                      },
+            "cs":  {
+                       "backButton":  "Zpět",
+                       "nextButton":  "Další",
+                       "loginMessage":  "Zadat heslo"
+                   },
+            "uk":  {
+                       "backButton":  "Назад",
+                       "nextButton":  "Далі",
+                       "loginMessage":  "Введіть пароль"
+                   },
+            "nn-NO":  {
+                          "backButton":  "Tilbake",
+                          "nextButton":  "Neste",
+                          "loginMessage":  "Skriv inn passord"
+                      },
+            "nso":  {
+                        "backButton":  "Morago",
+                        "nextButton":  "Latelago",
+                        "loginMessage":  "Tsenya phasewete"
+                    },
+            "hu":  {
+                       "backButton":  "Vissza",
+                       "nextButton":  "Tovább",
+                       "loginMessage":  "Jelszó megadása"
+                   },
+            "ca":  {
+                       "backButton":  "Endarrere",
+                       "nextButton":  "Següent",
+                       "loginMessage":  "Introduïu la contrasenya"
+                   }
+        };
+
+
+    var languageAndCountry = navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage; 
+    var language = "en";
+    if ( languageAndCountry && languageAndCountry.length >= 2 )
+    {
+        var languageOptions = Object.keys(translationTable);
+        // Sort the codes by length, so that we match longest first 
+        languageOptions.sort(function(a, b){
+          return b.length - a.length;
+        });
+
+        for ( i = 0; i < languageOptions.length; i++ )
+        {
+            // Prefix match the longest (most specific) langauge code we can 
+            if ( languageAndCountry.startsWith( languageOptions[i] ) )
+            {
+                language = languageOptions[i];
+                break;
+            }
+        }
+    }
+
+    if ( !element || !element.id )
+    {
+        return; 
+    }
+
+    var returnText = "";
+    returnText = translationTable["en"][element.id];
+    if ( translationTable[language] )
+    {
+        returnText = translationTable[language][element.id];
+    }
+
+    return returnText;    
+}
+
+function ShowUsernamePage(badUsernamePassword)
+{
+    var nextButton = document.getElementById('nextButton');
+    var backButton = document.getElementById('backButton');
+    var idBanner = document.getElementById('identityBanner');
+    var idBannerImage = document.getElementById('identityBannerImage');
+
+    var thingsToHide = [ passArea, submitButton, backButton, idBannerImage, idBanner ];
+    var thingsToShow = [ nextButton, username ];
+
+    // Show/Hide elements 
+    AdjustElementDisplay(thingsToHide, 'none');
+    AdjustElementDisplay(thingsToShow, 'block');
+
+    // Set the login message to what it was originally
+    if ( loginMessage )
+    {
+        loginMessage.innerHTML = originalLoginMessage;    
+    }
+
+    if ( errorText && errorText.innerHTML && !badUsernamePassword )
+    {
+        errorDisplay.style.display = 'none';
+    }
+    
+    // Create the 'next' button if we don't have it yet 
+    if ( submissionArea && !nextButton )
+    {
+        var nextButton = document.createElement("span");
+        nextButton.id = "nextButton";
+        nextButton.className = "submit";
+        nextButton.setAttribute("onclick", "PaginatedNext(); return false;");
+
+        var nextButtonText = GetLocalizedStringForElement(nextButton);
+        nextButton.innerHTML = nextButtonText;
+        nextButton.setAttribute("role", "button");
+        submissionArea.appendChild(nextButton);
+    }
+
+    if ( submissionArea )
+    {
+        submissionArea.classList.remove('submitModified');    
+    }
+
+    // Add 'enter' key listener to username textbox 
+    if ( usernameInput && !didAddListener )
+    {
+        usernameInput.addEventListener("keydown", function(event) {     
+            if (event.keyCode === 13) {
+                event.preventDefault();
+                PaginatedNext();
+                return false;
+            }
+        });
+
+        didAddListener = true;
+    }
+}
+
+function ShowPasswordPage()
+{
+    var nextButton = document.getElementById('nextButton');
+    var idBanner = document.getElementById('identityBanner');
+    var idBannerImage = document.getElementById('identityBannerImage');
+    var backButton = document.getElementById('backButton');
+
+    var thingsToHide = [ errorDisplay, nextButton, username ];
+    var thingsToShow = [ submitButton, passArea, backButton, idBanner, idBannerImage ];
+
+    // Show/Hide elements 
+    AdjustElementDisplay(thingsToHide, 'none');
+    AdjustElementDisplay(thingsToShow, 'block');
+
+    if ( loginMessage )
+    {
+        var loginMessageText = GetLocalizedStringForElement(loginMessage);
+        loginMessage.innerHTML = loginMessageText;    
+    }
+
+    if ( idBanner )
+    {
+        idBanner.innerHTML = usernameInput.value;
+    }
+
+    // Create the ID Banner if we need to 
+    if ( workArea && !idBanner)
+    {
+        // Create the ID banner
+        var idBanner = document.createElement("div");
+        idBanner.id = "identityBanner";
+        idBanner.className = "identityBanner";
+        idBanner.innerHTML = usernameInput.value;
+
+        // Create the ID banner user avatar image 
+        var image = document.createElement("img");
+        image.role = "presentation";
+        image.className = "identityBannerImage";
+        image.id = "identityBannerImage";
+
+        // NOTE: Admins should set this source to the image host server they use. Additionally, this image should be set
+        //  based on the username entered.  
+        image.src = "https://auth.gfx.ms/16.000.27564.3/images/picker_account_msa.svg";
+        
+        // Add the newly-created elements 
+        workArea.insertBefore(image, workArea.firstChild);
+        workArea.insertBefore(idBanner, workArea.firstChild);
+    }
+
+    // Create the 'Back' button if we need to 
+    if ( submissionArea && !backButton )
+    {
+        var backButton = document.createElement("span");
+        backButton.id = "backButton";
+        backButton.className = "submit";
+        backButton.classList.add('backButton');
+        backButton.setAttribute("onclick", "PaginatedBack(); return false;");
+        
+        if ( submitButton )
+        {
+            submitButton.classList.add('modifiedSignIn');    
+        }
+        
+        var backButtonText = GetLocalizedStringForElement(backButton);
+        backButton.innerHTML = backButtonText;
+        backButton.setAttribute("role", "button");
+        submissionArea.appendChild(backButton);
+    }
+
+    if ( submissionArea )
+    {
+        submissionArea.classList.add('submitModified');    
+    }
+
+    if ( passwordInput )
+    {
+        passwordInput.focus();
+    }
+}
+
+
+var usernameInput = document.getElementById("userNameInput");
+var passwordInput = document.getElementById('passwordInput');
+
+if ( usernameInput && passwordInput)
+{
+    var username = document.getElementById('userNameArea');
+    var passArea = document.getElementById('passwordArea');
+    
+    var submitButton = document.getElementById('submitButton');
+    var submissionArea = document.getElementById('submissionArea');
+    var errorText = document.getElementById('errorText');
+    var errorDisplay = document.getElementById('error');
+    var workArea = document.getElementById('workArea');
+    
+    var loginMessage = document.getElementById('loginMessage');
+    var originalLoginMessage = "";
+    var didLoadPasswordPageBefore = false;
+
+    if ( loginMessage )
+    {
+        originalLoginMessage = loginMessage.innerHTML;
+    }
+    var didAddListener = false;
+
+    var errorIsShown = false;
+    if ( errorDisplay && errorDisplay.style && errorDisplay.style.display != "none")
+    {
+        errorIsShown = true;
+    }
+
+    // Show the Username page, unless a username was already entered (login hint on the request), or we have an error
+    if ( usernameInput && usernameInput.value && !errorIsShown )
+    {
+        ShowPasswordPage();
+    }
+    else 
+    {
+        ShowUsernamePage(errorIsShown);
+    }
+}
+
+function getStyle(element, styleProp) {
+    var propStyle = null;
+
+    if (element && element.currentStyle) {
+        propStyle = element.currentStyle[styleProp];
+    }
+    else if (element && window.getComputedStyle) {
+        propStyle = document.defaultView.getComputedStyle(element, null).getPropertyValue(styleProp);
+    }
+
+    return propStyle;
+}
+
+var computeLoadIllustration = function () {
+    var branding = document.getElementById("branding");
+    var brandingDisplay = getStyle(branding, "display");
+    var brandingWrapperDisplay = getStyle(document.getElementById("brandingWrapper"), "display");
+
+    if (brandingDisplay && brandingDisplay !== "none" &&
+        brandingWrapperDisplay && brandingWrapperDisplay !== "none") {
+        var newClass = "illustrationClass";
+
+        if (branding.classList && branding.classList.add) {
+            branding.classList.add(newClass);
+        } else if (branding.className !== undefined) {
+            branding.className += " " + newClass;
+        }
+        if (window.removeEventListener) {
+            window.removeEventListener('load', computeLoadIllustration, false);
+            window.removeEventListener('resize', computeLoadIllustration, false);
+        }
+        else if (window.detachEvent) {
+            window.detachEvent('onload', computeLoadIllustration);
+            window.detachEvent('onresize', computeLoadIllustration);
+        }
+    }
+};
+
+if (window.addEventListener) {
+    window.addEventListener('resize', computeLoadIllustration, false);
+    window.addEventListener('load', computeLoadIllustration, false);
+}
+else if (window.attachEvent) {
+    window.attachEvent('onresize', computeLoadIllustration);
+    window.attachEvent('onload', computeLoadIllustration);
+}
+
+function SetIllustrationImage(imageUri) {
+    var illustrationImageClass = '.illustrationClass {background-image:url(' + imageUri + ');}';
+
+    var css = document.createElement('style');
+    css.type = 'text/css';
+
+    if (css.styleSheet) css.styleSheet.cssText = illustrationImageClass;
+    else css.appendChild(document.createTextNode(illustrationImageClass));
+
+    document.getElementsByTagName("head")[0].appendChild(css);
+}
+
+// IE doesn't support "startsWith", adding definition
+if (!String.prototype.startsWith) {
+    String.prototype.startsWith = function(searchString, position){
+        position = position || 0;
+        return this.substr(position, searchString.length) === searchString;
+    };
+}
+
+// Create new div to handle background tint overlay
+var tintDiv = document.createElement('div');
+tintDiv.id = 'brandingTint';
+tintDiv.class = 'illustrationClass';
+// Locate branding div and apply add tintDiv as child
+var brandingDiv = document.getElementById('branding');
+if (brandingDiv) {
+    brandingDiv.appendChild(tintDiv);
+}
+
+// NOTE: If you wish to support the ADFS illustration (background image), you must use the following:
+// PSH> Set-AdfsWebTheme -TargetName <activeTheme> -AdditionalFileResource @{uri='/adfs/portal/images/illustration_mine.png';path='.\illustration_mine.png'}
+// SetIllustrationImage('/adfs/portal/images/illustration_mine.png');

+ 28 - 0
adfsWebCustomization/communityCustomizations/README.md

@@ -0,0 +1,28 @@
+# Community Customizations 
+
+## Overview
+
+This project contains a number of customizations provided by community members. 
+
+## Adding Your Customization
+
+To add your customizations, please do the following: 
+
+1. Create a subfolder in this directory with a descriptive name of your customization (ex. "showPasswordButton")
+
+2. Update the list in this file with your change 
+
+3. In the subfolder with your customization, include the onload.js, any CSS changes you need, and a few screenshots of what your change does.
+    
+4. In the subfolder, include a `README.md` file, which explains your customization, and shows the screenshots 
+
+
+## Customizations
+
+1. __[ShowPasswordButton](ShowPasswordButton)__ - A customization to allow users to click "show password" when entering their password on an AD FS page
+
+## Contributing (Special Note)
+
+If you find any problems with the CSS, JavaScript, or docs, please fork and send us your fix. If you don't have a fix, please open an issue, and describe what you are seeing (feel free to include screenshots).
+
+For the full Contributing details, please see __[the root README](../README.md)__.

+ 14 - 0
adfsWebCustomization/communityCustomizations/ShowPasswordButton/OnLoad.js

@@ -0,0 +1,14 @@
+var inputArea = document.getElementById("inputArea");
+var showButton = document.getElementById("showButton");
+
+if ( inputArea && !showButton )
+{
+    var showButton = document.createElement("div");
+    showButton.id = "showButton";  
+    showButton.innerHTML = "Show password";
+    inputArea.appendChild(showButton);
+
+    var appendedButton = document.getElementById("showButton");
+    appendedButton.onmousedown = function(){ document.getElementById("password").type = "text"; };
+    appendedButton.onmouseup = function(){ document.getElementById("password").type = "password";};
+}

+ 19 - 0
adfsWebCustomization/communityCustomizations/ShowPasswordButton/README.md

@@ -0,0 +1,19 @@
+# ShowPasswordButton 
+
+## Overview
+
+This project contains an `onload.js` script for adding a "Show Password" button in the password field on AD FS logon pages.
+Works with password fields on Form based auth and with a custom provider using domain password as secondary auth. 
+
+## Applying the customization
+
+To add "Show password" button do the following: 
+
+1. Add the code from the `onload.js` to your webtheme `onload.js`
+
+2. Apply the customization according to https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages  
+
+## Examples
+
+![Button added](/communityCustomizations/ShowPasswordButton/images/Customization1.png)
+![Password shown on mouse click](/communityCustomizations/ShowPasswordButton/images/Customization2.png)

BIN
adfsWebCustomization/communityCustomizations/ShowPasswordButton/images/Customization1.png


BIN
adfsWebCustomization/communityCustomizations/ShowPasswordButton/images/Customization2.png


+ 33 - 0
adfsWebCustomization/mfaLoadingWheel/README.md

@@ -0,0 +1,33 @@
+# ADFS MFA Loading Wheel
+
+## Overview
+
+This project provides an ADFS web customization to add as part of your onload.js customizations. The waiting wheel provides UI feedback when a user chooses an MFA method. Some MFA providers perform overhead operations before navigating away from the MFA options page, which means the user may wait up to 3 seconds before page navigation occurs. 
+
+## Getting Started - JavaScript Deployment 
+
+1. Download the ```loadWheel.js``` file to your ADFS server, wherever you host your JavaScript. 
+    
+    Note: It is *__highly__* recommended that you minify your ```loadWheel.js``` before including it in a production environment. There are many popular tools online 
+    for minifying JavaScript code. Two popular choices are [minifier.org](http://www.minifier.org/) and [JSCompress](https://jscompress.com/).
+
+2. Create a custom web theme using the following command in PowerShell: 
+
+    ```New-AdfsWebTheme –Name custom -SourceName default -AdditionalFileResource @{Uri=’/adfs/portal/script/onload.js’; path="c:\loadWheel.js"}```
+
+3. Apply the new custom web theme using the following command in PowerShell:
+
+    ```Set-AdfsWebConfig -ActiveThemeName custom```
+
+4. For more information on JavaScript customization, see [Advanced ADFS Customization](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages)
+
+## Example
+
+![Screenshot](./images/screenshot_wheel.png)
+
+## Contributing (Special Note)
+
+If you find any problems with the CSS, JavaScript, or docs, please fork and send us your fix. If you don't 
+have a fix, please open an issue, and describe what you are seeing (feel free to include screenshots).
+
+For the full Contributing details, please see __[the root README](../README.md)__.

BIN
adfsWebCustomization/mfaLoadingWheel/images/screenshot_wheel.png


File diff suppressed because it is too large
+ 9 - 0
adfsWebCustomization/mfaLoadingWheel/loadWheel.js


+ 47 - 0
adfsWebCustomization/pageDetectionTelemetry/InteractiveCompletionByPlatformQuery.txt

@@ -0,0 +1,47 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+
+
+Client Interactive Completion Percentage (By Platform for 1 week): 
+
+For all requests to ADFS, what percentage of those requests, per platform (Windows vs. iOS vs. Android, etc.), end up returning or erroring out? (i.e. (#succeeded + #failed) / (#started))
+
+Important filters: 
+
+    • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 
+    • We remove all traffic for which there is no correlation ID set 
+    • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 
+
+Important Caveats: 
+
+    • Because we cannot guarantee what the end state of the request is, the numbers here are based on the likely outcome of the request 
+
+customEvents
+| where timestamp > ago(8d) and timestamp < ago(1d)
+| where isempty(operation_SyntheticSource)
+| where tostring(customDimensions.CorrelationID) != "NOTSET"
+| extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS))
+| summarize 
+    EventCount = count(),
+    DistinctEventCount = dcount(name),
+    EventNames = makelist(name) 
+    by CorrelationID = tostring(customDimensions.CorrelationID), CleanOS
+| where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35
+| extend LikelyFormsEnded = iff(EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart"), 1, 0)
+| extend LikelyAuthSelectEnded = iff((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked") and (EventNames !has "PhoneFactorWaitingStart"), 1, 0)
+| extend LikelyPFAEnded = iff(EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd", 1, 0)
+| extend LikelyErrorEnded = iff((EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart") and 
+    (EventNames !has "FormsPageStart" and 
+    EventNames !has "FormsPageEnd" and 
+    EventNames !has "AuthSelectionPageStart" and 
+    EventNames !has "AuthSelectionPageEnd" and 
+    EventNames !has "PhoneFactorWaitingStart" and 
+    EventNames !has "PhoneFactorWaitingEnd"), 1, 0)
+| extend LikelyEnded = iff(LikelyFormsEnded > 0 or LikelyAuthSelectEnded > 0 or LikelyPFAEnded > 0 or LikelyErrorEnded > 0, 1, 0) 
+| project CorrelationID, LikelyEnded, CleanOS
+| summarize 
+    TotalRequests = count(), 
+    EndedRequests = countif(LikelyEnded > 0) 
+    by CleanOS
+| extend InteractiveCompletion = (1.0 * EndedRequests) / (1.0 * TotalRequests) * 100.0
+| sort by TotalRequests desc

+ 45 - 0
adfsWebCustomization/pageDetectionTelemetry/InteractiveCompletionQuery.txt

@@ -0,0 +1,45 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+
+
+
+Client Interactive Completion Percentage (Total for 1 week): 
+
+For all requests that come to ADFS, what percentage of those requests end up returning or erroring out? (i.e. (#succeeded + #failed) / (#started))
+
+Important filters: 
+
+    • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 
+    • We remove all traffic for which there is no correlation ID set 
+    • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 
+
+Important Caveats: 
+
+    • Because we cannot guarantee what the end state of the request is, the numbers here are based on the likely outcome of the request 
+
+customEvents
+| where timestamp > ago(8d) and timestamp < ago(1d)
+| where isempty(operation_SyntheticSource)
+| where tostring(customDimensions.CorrelationID) != "NOTSET"
+| summarize 
+    EventCount = count(),
+    DistinctEventCount = dcount(name),
+    EventNames = makelist(name) 
+    by CorrelationID = tostring(customDimensions.CorrelationID)
+| where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35
+| extend LikelyFormsEnded = iff(EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart"), 1, 0)
+| extend LikelyAuthSelectEnded = iff((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked") and (EventNames !has "PhoneFactorWaitingStart"), 1, 0)
+| extend LikelyPFAEnded = iff(EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd", 1, 0)
+| extend LikelyErrorEnded = iff((EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart") and 
+    (EventNames !has "FormsPageStart" and 
+    EventNames !has "FormsPageEnd" and 
+    EventNames !has "AuthSelectionPageStart" and 
+    EventNames !has "AuthSelectionPageEnd" and 
+    EventNames !has "PhoneFactorWaitingStart" and 
+    EventNames !has "PhoneFactorWaitingEnd"), 1, 0)
+| extend LikelyEnded = iff(LikelyFormsEnded > 0 or LikelyAuthSelectEnded > 0 or LikelyPFAEnded > 0 or LikelyErrorEnded > 0, 1, 0) 
+| project CorrelationID, LikelyEnded
+| summarize 
+    TotalRequests = count(), 
+    EndedRequests = countif(LikelyEnded > 0) 
+| extend InteractiveCompletion = (1.0 * EndedRequests) / (1.0 * TotalRequests) * 100.0

+ 54 - 0
adfsWebCustomization/pageDetectionTelemetry/LoginReliabilityByPlatformQuery.txt

@@ -0,0 +1,54 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+
+
+
+Login Reliability Percentage - By Platform for 1 week: 
+
+For all requests to ADFS, what percentage of those requests, per platform (Windows vs. iOS vs. Android, etc.), end up returning? (i.e. #succeeded / (#succeeded + failed))
+
+In this query, instead of counting a request as an ADFS failure if we ever see an error page, we only count it as a failure if we end on an error page. 
+
+Important filters: 
+
+        • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 
+        • We remove all traffic for which there is no correlation ID set 
+        • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 
+
+Important Caveats: 
+
+        • Because we cannot guarantee that a request returned or did not return to EVO, the numbers here are based on the likely outcome of the request 
+        • Login reliability for ADFS includes ONLY PROMPTING requests, no silent requests are included. This leads to an expectation of having a much lower login reliability than normal
+
+customEvents
+| where timestamp > ago(8d) and timestamp < ago(1d)
+| where isempty(operation_SyntheticSource)
+| where tostring(customDimensions.CorrelationID) != "NOTSET"
+| extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS))
+| summarize 
+    EventCount = count(),
+    DistinctEventCount = dcount(name),
+    EventNames = makelist(name) 
+    by CorrelationID = tostring(customDimensions.CorrelationID), CleanOS
+| where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35
+| extend LikelyFormsNotAbandoned = 
+    iff((EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart")) 
+        or (EventNames has "FormsPageStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0)
+| extend LikelyAuthSelectNotAbandoned = iff(
+    ((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked" or EventNames has "AuthSelectionLatency") and (EventNames !has "PhoneFactorWaitingStart")) 
+    or (EventNames has "AuthSelectionPageStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0)
+| extend LikelyPFANotAbandoned = iff(
+    (EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd") or 
+    (EventNames has "PhoneFactorWaitingStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0)
+| extend LikelyNotAbandoned = iff(LikelyFormsNotAbandoned > 0 or LikelyAuthSelectNotAbandoned > 0 or LikelyPFANotAbandoned > 0, 1, 0) 
+| extend WasErrorRequest = iff(EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart", 1, 0)
+| extend WasEndErrorState = iff(EventNames[ toint(arraylength(EventNames) - 1) ] has "ErrorPageStart" or EventNames[ toint(arraylength(EventNames) - 1) ] has "ErrorDetailedPageStart", 1, 0)
+| project CorrelationID, EventNames, LikelyNotAbandoned, WasErrorRequest, CleanOS, WasEndErrorState 
+| summarize 
+    TotalRequests = count(), 
+    TotalErroredRequests = sum(WasEndErrorState),
+    ReturnedRequests = countif(LikelyNotAbandoned > 0) 
+    by CleanOS 
+| extend LoginReliabilityInteractive = (1.0 * ReturnedRequests) / (1.0 * ReturnedRequests + TotalErroredRequests) * 100.0
+| sort by TotalRequests desc
+

+ 74 - 0
adfsWebCustomization/pageDetectionTelemetry/README.md

@@ -0,0 +1,74 @@
+# Page Detection with App Insights 
+
+## Overview
+
+This project performs page detection of common, uncustomized ADFS web pages, and then uploads 
+telemetry about those pages to your [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) datastore. 
+
+Note that this customization DOES NOT send any telemetry to the Microsoft ADFS team. All telemetry is sent to your datastore only.
+
+This project also includes some useful analysis scripts you can run against your Application Insights datastore. 
+
+## Requirements
+
+This tool requires that you have an [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) subscription.
+
+Additionally, it is recommended that you have minimal web customization of the standard ADFS pages, as customization could throw off the page 
+detection. Please note that customization referres to onload.js changes, not logo changes, illustration changes, etc. 
+
+Lastly, there is some page detection logic that relies on English strings in the pages. If you are presenting pages in languages other than 
+English, you might need to make modifications to the JavaScript.
+
+## Getting Started 
+
+1. Register for an [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) subscription
+
+2. Download the ```onload.js``` in this repo locally, and update the ```instrumentationKey``` under ```GenerateAppInsightsObject``` to be your Application Insights API key
+
+     (For more details, see [Copy the instrumentation key](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-create-new-resource#copy-the-instrumentation-key))
+
+3. Replace the ```onload.js``` in your ADFS environment with the ```onload.js``` from this project. Alternatively, if you already have content in your ```onload.js```, you 
+should append our content to yours. 
+
+    Note: It is *__highly__* recommended that you minify your ```onload.js``` before including it in a production environment. There are many popular tools online 
+    for minifying JavaScript code. Two popular choices are [minifier.org](http://www.minifier.org/) and [JSCompress](https://jscompress.com/).
+
+     (For more information, see [Advanced ADFS Customization](https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages))
+
+## What Gets Tracked
+
+The following pages are detected and tracked:
+
+* Forms Page - the username and password collection page 
+* Auth Selection Page - the page served when MFA is required. This page lists MFA provider options
+* PFA Waiting Page - the page served when Phone Factor Authentication (PFA) is performed by the [MultiFactorAuthenticationAdfsAdapter](https://docs.microsoft.com/en-us/azure/multi-factor-authentication/multi-factor-authentication-get-started-adfs-w2k12)
+* Error Page - the ADFS or PFA error page 
+
+
+## Analyzing the Data
+
+To analyze your data, you will need to write analysis queries against your [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) datastore. 
+For more details, see [Analytics](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-analytics).
+
+Included in this project are a number of useful queries for tracking: 
+
+* User prompting rate served by ADFS server 
+* Login reliability of your ADFS server
+* Interactive completion rate of your ADFS server
+
+## Further Reading 
+
+The following documentation is useful for making changes to the ```onload.js``` code and the included queries.
+
+* [Application Insights Overview](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview)
+* [Data Analysis Overivew](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-analytics-tour)
+* [Custom App Insights Collection](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-api-custom-events-metrics#_flushing-data)
+* [App Insights in JavaScript](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-javascript)
+* [App Insights JavaScript GitHub](https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md)
+
+## Contributing (Special Note)
+
+If you are contributing code, please be sure that you __remove your instrumentation key__ from any code you 
+put in a pull request. This project is public, and anyone on the Internet can see it.
+
+For the full Contributing details, please see __[the root README](../README.md)__.

+ 54 - 0
adfsWebCustomization/pageDetectionTelemetry/UserPromptRateByPlatformQuery.txt

@@ -0,0 +1,54 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+
+
+
+
+Browser Sessions with Exactly 1 Unforced Prompt Per Week (By Platform): 
+
+What percentage of browser sessions that are making requests to ADFS are receiving exactly 1 unforced prompt per week, broken down by platform?
+
+Important filters: 
+
+    • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 
+    • We remove all traffic for which there is no correlation ID set 
+    • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 
+    • We remove traffic for users who only saw an error page 
+    • We remove all traffic that has a query parameter that would force a prompt ("wfresh = 0")
+
+Important Caveats: 
+
+    • Session_Id is not a good field to use for this analysis, because we are under-reporting when we use this field. For more details, see this discussion (https://docs.microsoft.com/en-us/azure/application-insights/app-insights-web-track-usage#Sessions)
+    • We count 2 or fewer prompts per session as a single user prompt, since some requests will have AuthSelect and MFA, which we count as 1 prompt 
+
+customEvents
+| where timestamp > ago(8d) and timestamp < ago(1d)
+| where isempty(operation_SyntheticSource)
+| where tostring(customDimensions.CorrelationID) != "NOTSET"
+| where tostring(customDimensions.wfresh) != "0"
+| extend CleanBrowser = replace('Internet Explorer', 'IE', replace(@'\s', '', replace('UI/WKWebView', '', replace('Mobile', '', replace('[0-9.]+', '', client_Browser)))))
+| extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS))
+| summarize 
+    EventCount = count(),
+    DistinctEventCount = dcount(name),
+    FormsPromptCount = countif(name startswith "Forms"),
+    AuthPromptCount = countif(name startswith "AuthSelection" and customDimensions.SelectionMethod !has "auto"),
+    PFAPromptCount = countif(name startswith "Phone"),
+    EventNames = makelist(name) 
+    by CorrelationID = tostring(customDimensions.CorrelationID), session_Id, CleanOS
+| where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35
+| extend WasFormsPrompted = iff(FormsPromptCount > 0, 1, 0)
+| extend WasAuthPrompted = iff(AuthPromptCount > 0, 1, 0) 
+| extend WasPFAPrompted = iff(PFAPromptCount > 0, 1, 0)
+| extend PromptsPerRequest = WasAuthPrompted + WasFormsPrompted + WasPFAPrompted
+| project CorrelationID, session_Id, PromptsPerRequest, CleanOS
+| where PromptsPerRequest > 0 // Some users are only getting an Error Page
+| summarize 
+    PromptsPerSession = sum(PromptsPerRequest)
+    by session_Id, CleanOS
+| summarize 
+    TotalSessions = count(), 
+    SessionsWith1Prompt = countif(PromptsPerSession <= 3)
+    by CleanOS
+| extend PercentWith1 = (1.0 * SessionsWith1Prompt) / (1.0 * TotalSessions) * 100.0
+| sort by TotalSessions desc

+ 50 - 0
adfsWebCustomization/pageDetectionTelemetry/UserPromptRateQuery.txt

@@ -0,0 +1,50 @@
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the MIT License.
+
+
+
+Browser Sessions with Exactly 1 Unforced Prompt Per Week: 
+
+What percentage of browser sessions that are making requests to ADFS are receiving exactly 1 unforced prompt per week?
+
+Important filters: 
+
+    • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 
+    • We remove all traffic for which there is no correlation ID set 
+    • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 
+    • We remove traffic for users who only saw an error page 
+    • We remove all traffic that has a query parameter that would force a prompt ("wfresh = 0")
+
+Important Caveats: 
+
+    • Session_Id is not a good field to use for this analysis, because we are under-reporting when we use this field. For more details, see this discussion (https://docs.microsoft.com/en-us/azure/application-insights/app-insights-web-track-usage#Sessions)
+    • We count 2 or fewer prompts per session as a single user prompt, since some requests will have AuthSelect and MFA, which we count as 1 prompt 
+
+
+customEvents
+| where timestamp > ago(8d) and timestamp < ago(1d)
+| where isempty(operation_SyntheticSource)
+| where tostring(customDimensions.CorrelationID) != "NOTSET"
+| where tostring(customDimensions.wfresh) != "0"
+| summarize 
+    EventCount = count(),
+    DistinctEventCount = dcount(name),
+    FormsPromptCount = countif(name startswith "Forms"),
+    AuthPromptCount = countif(name startswith "AuthSelection" and customDimensions.SelectionMethod !has "auto"),
+    PFAPromptCount = countif(name startswith "Phone"),
+    EventNames = makelist(name) 
+    by CorrelationID = tostring(customDimensions.CorrelationID), session_Id
+| where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35
+| extend WasFormsPrompted = iff(FormsPromptCount > 0, 1, 0)
+| extend WasAuthPrompted = iff(AuthPromptCount > 0, 1, 0) 
+| extend WasPFAPrompted = iff(PFAPromptCount > 0, 1, 0)
+| extend PromptsPerRequest = WasAuthPrompted + WasFormsPrompted + WasPFAPrompted
+| project CorrelationID, session_Id, PromptsPerRequest
+| where PromptsPerRequest > 0 // Some users are only getting an Error Page
+| summarize 
+    PromptsPerSession = sum(PromptsPerRequest)
+    by session_Id
+| summarize 
+    TotalSessions = count(), 
+    SessionsWith1Prompt = countif(PromptsPerSession <= 2)  // We allow 2 here because MFA and Auth Select together count as 1 prompt
+| extend PercentWith1 = (1.0 * SessionsWith1Prompt) / (1.0 * TotalSessions) * 100.0

+ 376 - 0
adfsWebCustomization/pageDetectionTelemetry/onload.js

@@ -0,0 +1,376 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+//
+// Telemetry Manager is the App Insights telemetry management object
+// Callers MUST call 'Initialize' before using the 'ProducePageDetectionTelemetry' method
+//
+TelemetryManager = {
+
+    /*
+     * Initialize the app state for the current page 
+     */ 
+    Initialize: function () {
+        _self = this;
+
+        // Collect some page details for later 
+        var NOT_SET_CONST = 'NOTSET';
+        _self.currentUri = window.location.href.split('?')[0];
+        _self.mswtrealm = _self.getQueryString("wtrealm") || NOT_SET_CONST;
+        _self.decodedwtrealm = decodeURIComponent(_self.mswtrealm) || NOT_SET_CONST;
+        _self.requestID = _self.getQueryString("client-request-id") || NOT_SET_CONST;
+        _self.wfresh = _self.getQueryString("wfresh") || NOT_SET_CONST;
+        _self.wauth = _self.getQueryString("wauth") || NOT_SET_CONST;
+        _self.debugging = _self.getQueryString("debug") || NOT_SET_CONST;
+        _self.wauth = decodeURIComponent(_self.wauth);
+        _self.Username = NOT_SET_CONST;
+
+        _self.startedPfaWaiting = false;
+        _self.pfaTimestamp = null;
+        _self.pfaTimestampOldBrowser = null;
+        _self.startedAuthSelectionWaiting = false;
+        _self.authSelectionTimestamp = null;
+        _self.authSelectionTimestampOldBrowser = null;
+        _self.authSelectionLinkClicked = null;
+        _self.authSelectionMethod = null;
+        _self.startedFormsPage = false;
+
+        // Create App Insights object with settings 
+        if (!window.appInsights) {
+            if(console && _self.debugging) console.log("TelemetryManager: Generating a new App Insights object");
+            var appInsights = _self.GenerateAppInsightsObject.call();
+
+            // Set App Insights object against the current window 
+            window.appInsights = appInsights;
+            if(console && _self.debugging) console.log("TelemetryManager: Set new App Insights object against the current window");
+        }
+
+        //
+        // Add unload callback to window, so we can capture telemetry 
+        //
+        if (window.addEventListener) {
+            window.addEventListener("unload", function () { _self.LeavingCurrentPageCallback(_self); }, false);  // Modern browsers
+        } else if (window.attachEvent) {
+            window.attachEvent('onunload', function () { _self.LeavingCurrentPageCallback(_self); });            // Old IE
+        }
+
+        if(console && _self.debugging) console.log("Exit: TelemetryManager.Initialize");
+    },
+
+    /*
+     * Generate an App Insights object to use when
+     *  sending telemetry. 
+     */
+    GenerateAppInsightsObject: function() {
+        return function (config) {
+            function r(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = { config: config }, u = document, e = window, o = "script", s = u.createElement(o), i, f; for (s.src = config.url || "//az416426.vo.msecnd.net/scripts/a/ai.0.js", u.getElementsByTagName(o)[0].parentNode.appendChild(s), t.cookie = u.cookie, t.queue = [], i = ["Event", "Exception", "Metric", "PageView", "Trace"]; i.length;) r("track" + i.pop()); return r("setAuthenticatedUserContext"), r("clearAuthenticatedUserContext"), config.disableExceptionTracking || (i = "onerror", r("_" + i), f = e[i], e[i] = function (config, r, u, e, o) { var s = f && f(config, r, u, e, o); return s !== !0 && t["_" + i](config, r, u, e, o), s }), t
+        }({
+            samplingPercentage: 100,
+            instrumentationKey: "YOUR-KEY-HERE"
+        });
+    },
+
+    /*
+     * Helper function to get a querystring parameter 
+     */
+    getQueryString: function(qsName) {
+        qsName = qsName.replace(/[\[\]]/g, "\\$&");
+        var regex = new RegExp("[?&]" + qsName + "(=([^&#]*)|&|#|$)"),
+            results = regex.exec(location.href);
+        if (!results) return "";
+        if (!results[2]) return "";
+        return decodeURIComponent(results[2].replace(/\+/g, " "));
+    }
+
+    /*
+     *  Produces all telemetry for the following pages: 
+     *      Forms Page 
+     *      AuthSelection Page 
+     *      Home Realm Discovery Page 
+     *      Phone Factor Authentication Page 
+     *      Phone Factor Error Page 
+     *      ADFS Error Page 
+     *      Phone Factor Authentication Options Page 
+     */
+    ProducePageDetectionTelemetry: function () {
+        
+        var _self = this;
+
+        if(console && _self.debugging) console.log("Enter: TelemetryManager.ProducePageDetectionTelemetry");
+
+        //
+        // Generic Page view tracking 
+        //
+        window.appInsights.trackPageView("Generic");
+
+        //
+        // Home Realm Discovery Page
+        //
+        var hrd = document.getElementById('hrd');
+        if (hrd) {
+            window.appInsights.trackPageView("HomeRealmDiscovery");
+            if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found HRD Page");
+            return;
+        }
+
+        //
+        // Forms Page (before creds are entered)
+        //  NOTE: This only works for pages presented in English 
+        //
+        var pageloginForm = document.getElementById('loginForm');
+        if (!hrd && pageloginForm && document.title == 'Sign In') {
+            window.appInsights.trackPageView("FormsPage");
+            window.appInsights.trackEvent("FormsPageStart",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm }
+            );
+            _self.startedFormsPage = true;
+            if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Forms Page");
+            return;
+        }
+
+        //
+        // Error Page
+        //
+        var ierrorText = document.getElementById("errorText");
+        if (ierrorText) {
+            var ierrorCurrent = ierrorText.innerHTML;
+            if (ierrorCurrent.length > 0) {
+                var pageTitle = document.title;
+
+                //
+                // Try to gather more error information from the page 
+                //
+                var erruserAccount = _self.GetUserNameFromAuthArea();
+                var erractivityId = (document.getElementById('activityId') || {innerText:''}).innerText;
+                var errcontextId = (document.getElementById('contextId')|| {innerText:''}).innerText;
+                var errtimestamp = (document.getElementById('timestamp')|| {innerText:''}).innerText;
+                
+                if(erractivityId || errcontextId || errtimestamp){
+                    window.appInsights.trackPageView("ErrorDetailedPage");
+                    window.appInsights.trackEvent("ErrorDetailedPageStart",
+                        { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, PageTitle: pageTitle, Username: erruserAccount, ActivityID: erractivityId, ContextId: errcontextId, ErrorTimestamp: errtimestamp }
+                    );
+                    if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Detailed Error Page");
+                }                
+                return;
+            }
+        }
+
+        //
+        // AuthSelection Page
+        //
+        var authOptions = document.getElementById('authOptions')
+        var progress = document.getElementById('Progress')
+        if (authOptions && !progress) {
+            
+            var foundUsername = _self.GetUserNameFromAuthArea();
+            _self.Username = foundUsername;
+
+            window.appInsights.trackPageView("AuthSelectionPage");
+            window.appInsights.trackEvent("AuthSelectionPageStart",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username }
+            );
+            if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Auth Selection Page");
+
+            //
+            // Add click callbacks to the auth selection options
+            //  NOTE: If you have other options you wish to track, add them here
+            //
+            var certOption = document.getElementById('CertificateAuthentication');
+            if (certOption) {
+                certOption.addEventListener("click", function () { _self.AuthSelectionPageSubmitCallback("cert", "manual", _self); }, false);
+            }
+            var azureOption = document.getElementById('WindowsAzureMultiFactorAuthentication');
+            if (azureOption) {
+                azureOption.addEventListener("click", function () { _self.AuthSelectionPageSubmitCallback("phonefactor", "manual", _self); }, false);
+            }
+
+            return;
+        }
+
+        //
+        // Phone Factor Waiting Page 
+        //
+        var workArea = document.getElementById('workArea');
+        var authArea = document.getElementById('authArea');
+        var progressDiv = document.getElementById('Progress');
+        var authMethod = document.getElementById('authMethod');
+        var errorDiv = document.getElementById('errorDiv');
+        if (workArea && authArea && progressDiv && authMethod && !errorDiv) {            
+
+            var phonefactorUserID = _self.GetUserNameFromAuthArea();
+            var authchildren = authArea.childNodes;
+            for (var i = 0; i < authchildren.length; i++) {
+                if (authchildren[i].className === 'fieldMargin bigText') {
+                    window.appInsights.trackPageView("PhoneFactorWaitingPage");
+                    window.appInsights.trackEvent("PhoneFactorWaitingStart",
+                        { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: phonefactorUserID }
+                    );
+                    if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found PFA Waiting Page");
+
+                    // Once we detect the pfa page, add a timer to collect the PFA latency 
+                    _self.startedPfaWaiting = true;
+                    _self.Username = phonefactorUserID;
+
+                    if (performance && performance.now()) {
+                        _self.pfaTimestamp = performance.now();
+                    }
+
+                    if (Date && Date.now()) {
+                        _self.pfaTimestampOldBrowser = Date.now();
+                    }
+
+                    return;
+                }
+            }
+        }
+
+        if(console && _self.debugging) console.log("Exit: TelemetryManager.ProducePageDetectionTelemetry");
+    },
+
+    /*
+     * Collect the username from the auth area message 
+     *  NOTE: This method only works for pages presented in English 
+     */
+    GetUserNameFromAuthArea: function () {
+        var authchildren = document.getElementById('authArea').childNodes;
+        for (var i = 0; i < authchildren.length; i++) {
+            if (authchildren[i].className === 'fieldMargin bigText') {
+                var tempuserAccount = authchildren[i].innerText;
+                return tempuserAccount.replace("Welcome ", "");
+            }
+        }
+    },
+
+    /*
+     * Callback function when the AuthSelection page is being submitted after an 
+     *  auth option was chosen.
+     */
+    AuthSelectionPageSubmitCallback: function (linkClicked, selectionMethod, _self) {
+        if(console && _self.debugging) console.log("Enter: TelemetryManager.AuthSelectionPageSubmitCallback");
+
+        // Collect telemetry
+        window.appInsights.trackEvent("AuthSelectionPicked",
+             { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wauth: _self.wauth, wfresh: _self.wfresh, Type: linkClicked, SelectionMethod: selectionMethod, wtrealm: _self.decodedwtrealm, Username: _self.Username }
+        );
+
+        if(console && _self.debugging) console.log("AuthSelectionPageSubmitCallback: Link Clicked: " + linkClicked);
+
+        // Start the auth selection timer to time from page submit to page unload 
+        _self.startedAuthSelectionWaiting = true;
+        _self.authSelectionLinkClicked = linkClicked;
+        _self.authSelectionMethod = selectionMethod;
+
+        if (performance && performance.now()) {
+            _self.authSelectionTimestamp = performance.now();
+        }
+
+        if (Date && Date.now()) {
+            _self.authSelectionTimestampOldBrowser = Date.now();
+        }
+
+        if(console && _self.debugging) console.log("Exit: TelemetryManager.AuthSelectionPageSubmitCallback");
+    },
+
+    /*
+     * Callback function when any page is being left
+     *  NOTE: Due to browser unload calls, there is no guarantee that the 
+     *   processing in this method will complete. Some of the XHR requests made for 
+     *   trackEvent calls may not succeed. This telemetry is a best-effort collection
+     */
+    LeavingCurrentPageCallback: function (_self) {
+
+        // Grab the window appInsights object for local use 
+        var localAppInsights = window.appInsights;
+        var flushMePlease = false;
+
+        if (_self.startedFormsPage) {
+            _self.Username = document.getElementById(Login.userNameInput).value;
+            localAppInsights.trackEvent("FormsPageEnd",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username }
+            );
+            _self.startedFormsPage = false;
+            flushMePlease = true;
+        }
+
+        var pfaTime = null;
+        if (_self.pfaTimestamp) {
+            pfaTime = (performance.now() - _self.pfaTimestamp) / 1000.0;
+        }
+
+        var pfaTimeOldBrowser = null;
+        if (_self.pfaTimestampOldBrowser) {
+            pfaTimeOldBrowser = (Date.now() - _self.pfaTimestampOldBrowser) / 1000.0;
+        }
+
+        if (pfaTime) {
+            localAppInsights.trackEvent("PhoneFactorLatency",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username },
+                { Latency: pfaTime }
+            );
+            flushMePlease = true;
+        } else if (pfaTimeOldBrowser) {
+            localAppInsights.trackEvent("PhoneFactorLatency",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username },
+                { OldBrowserLatency: pfaTimeOldBrowser }
+            );
+            flushMePlease = true;
+        }
+
+        if (_self.startedPfaWaiting) {
+
+            localAppInsights.trackEvent("PhoneFactorWaitingEnd",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username }
+            );
+            _self.startedPfaWaiting = false;
+            flushMePlease = true;
+        }
+
+        if (_self.startedAuthSelectionWaiting) {
+            localAppInsights.trackEvent("AuthSelectionPageEnd",
+                { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username, Type: _self.authSelectionLinkClicked, SelectionMethod: _self.authSelectionMethod }
+            );
+            _self.startedAuthSelectionWaiting = false;
+            flushMePlease = true;
+        }
+
+        var authSelectionTime = null;
+        if (_self.authSelectionTimestamp) {
+            authSelectionTime = (performance.now() - _self.authSelectionTimestamp) / 1000.0;
+        }
+
+        var authSelectionTimeOldBrowser = null;
+        if (_self.authSelectionTimestampOldBrowser) {
+            authSelectionTimeOldBrowser = (Date.now() - _self.authSelectionTimestampOldBrowser) / 1000.0;
+        }
+
+        if (authSelectionTime ) {
+            localAppInsights.trackEvent("AuthSelectionLatency",
+                { CorrelationID: _self.requestID, Username: _self.Username, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm, Type: _self.authSelectionLinkClicked, SelectionMethod: _self.authSelectionMethod },
+                { Latency: authSelectionTime }
+            );
+            flushMePlease = true;
+        } else if (authSelectionTimeOldBrowser) {
+            localAppInsights.trackEvent("AuthSelectionLatency",
+                { CorrelationID: _self.requestID, Username: _self.Username, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm, Type: _self.authSelectionLinkClicked, SelectionMethod: _self.authSelectionMethod },
+                { OldBrowserLatency: authSelectionTimeOldBrowser }
+            );
+            flushMePlease = true;
+        }
+
+        if (flushMePlease) {
+            if (localAppInsights) {
+                if (localAppInsights.flush) {
+                    localAppInsights.flush();
+                }
+            }
+        }
+    },
+};
+
+// Produce telemetry 
+if(console) console.log("TelemetryManager: Start trying to produce telemetry");
+var pageTelemetryManager = TelemetryManager;
+pageTelemetryManager.Initialize();
+pageTelemetryManager.ProducePageDetectionTelemetry();
+if(console) console.log("TelemetryManager: End trying to produce telemetry");

Some files were not shown because too many files changed in this diff