├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── centeredUi ├── README.md ├── ThemeCenterBrand.css ├── ThemeCenterBrandRTL.css ├── images │ ├── empty_user.png │ ├── screenshot.png │ ├── screenshot_paginated.png │ └── screenshot_paginated2.png └── paginatedOnload.js ├── communityCustomizations ├── CustomImagesThemeGenerator │ ├── Digitude.Adfs.CustomImagesThemeGenerator.sln │ ├── README.md │ ├── images │ │ ├── 3bbc0a40203d4ce7ba0c4d97b8b7dd93.png │ │ ├── 9544fd2a4ad07f3956b38493ab2d3453.png │ │ ├── b7f27c9fe4d4a175d9b243f6bb694a85.png │ │ └── c587fa46b0cf0b5afbac869eb426db97.png │ └── src │ │ ├── App.config │ │ ├── Digitude.Adfs.CustomImagesThemeGenerator.csproj │ │ ├── Main.Designer.cs │ │ ├── Main.cs │ │ ├── Main.resx │ │ ├── Program.cs │ │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ │ ├── Resources │ │ ├── mockup_YCw_icon.ico │ │ ├── onload.js │ │ └── style.css │ │ ├── WebThemeInfo.Designer.cs │ │ ├── WebThemeInfo.cs │ │ ├── WebThemeInfo.xsc │ │ ├── WebThemeInfo.xsd │ │ └── WebThemeInfo.xss ├── README.md ├── RenameAndReorderADCPTrust │ ├── ONLOAD.JS │ └── README.md └── ShowPasswordButton │ ├── OnLoad.js │ ├── README.md │ └── images │ ├── Customization1.png │ └── Customization2.png ├── mfaLoadingWheel ├── README.md ├── images │ └── screenshot_wheel.png └── loadWheel.js └── pageDetectionTelemetry ├── InteractiveCompletionByPlatformQuery.txt ├── InteractiveCompletionQuery.txt ├── LoginReliabilityByPlatformQuery.txt ├── README.md ├── UserPromptRateByPlatformQuery.txt ├── UserPromptRateQuery.txt └── onload.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AD FS Web Customizations 2 | 3 | ## Overview 4 | 5 | This repository contains useful web customizations for AD FS. The following customizations are currently included: 6 | 7 | 1. __[pageDetectionTelemetry](pageDetectionTelemetry)__ - JavaScript customization to detect AD FS pages and upload telemetry 8 | to your [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) datastore. 9 | 10 | 2. __[centeredUi](centeredUi)__ - CSS customization to allow your on-prem AD FS to be consistent with the look-and-feel of the 11 | [centered Azure AD Sign-in](https://cloudblogs.microsoft.com/enterprisemobility/2017/08/02/the-new-azure-ad-signin-experience-is-now-in-public-preview/) 12 | 13 | __![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Action Required__ 14 | 15 | If you use the paginated onload.js web customization to create a paginated experience on your AD FS server, please update to the latest version. If you deployed the onload.js on or before __May 29, 2018__, please update your deployment. 16 | 17 | 3. __[mfaLoadingWheel](mfaLoadingWheel)__ - JavaScript customization to add a loading wheel to the AD FS authentication options page. 18 | 19 | 4. __[communityCustomizations](communityCustomizations)__ - JavaScript customizations from community members. 20 | 21 | ## Usage 22 | 23 | If you use any of our open source tools or projects, please consider subscribing to our announcements newsletter. We use this list to provide updates on security bugs, new feature announcements, and other info directly relevant to our users. 24 | 25 | [Sign up here](http://eepurl.com/dwF5gP) 26 | 27 | ## Contributing 28 | 29 | This project welcomes contributions and suggestions. We encourage you to fork this project, include any web customizations 30 | you find useful, and then do a pull request to master. If your customizations work, we'll include them so everyone can benefit. 31 | 32 | Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, 33 | grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. 34 | 35 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 36 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 37 | provided by the bot. You will only need to do this once across all repos using our CLA. 38 | 39 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 40 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 41 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 42 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /centeredUi/README.md: -------------------------------------------------------------------------------- 1 | # Match Azure AD Centered Login Page 2 | 3 | ## Overview 4 | 5 | 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/). 6 | 7 | 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. 8 | 9 | ## ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Action Required 10 | 11 | If you use the paginated onload.js web customization to create a paginated experience on your AD FS server, please update to the latest version. If you deployed the onload.js on or before __May 29, 2018__, please update your deployment. 12 | 13 | ## Getting Started 14 | 15 | 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. 16 | 17 | ## Getting Started - Style Sheet Deployment 18 | 19 | 1. Download the ```ThemeCenterBrand.css``` file to your AD FS server, wherever you host your style sheets. 20 | 21 | Note: It is recommended that you minify your CSS for a production environment. 22 | 23 | 2. Create a custom web theme using the following command in PowerShell: 24 | 25 | ```New-AdfsWebTheme –Name custom -SourceName default –StyleSheet @{path="c:\style\ThemeCenterBrand.css"}``` 26 | 27 | 3. Apply the new custom web theme using the following command in PowerShell: 28 | 29 | ```Set-AdfsWebConfig -ActiveThemeName custom``` 30 | 31 | 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). 32 | 33 | ## Getting Started - JavaScript Deployment 34 | 35 | 1. Download the ```paginatedOnload.js``` file to your AD FS server, wherever you host your JavaScript. 36 | 37 | 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/). 38 | 39 | 2. Modify your existing custom web theme from the style sheet deployment to include the new JavaScript. In PowerShell: 40 | 41 | ```Set-AdfsWebTheme –TargetName custom -AdditionalFileResource @{Uri="/adfs/portal/script/onload.js"; path="c:\paginatedOnload.js"}``` 42 | 43 | 3. Apply the modified custom web theme using the following command in PowerShell: 44 | 45 | ```Set-AdfsWebConfig -ActiveThemeName custom``` 46 | 47 | 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). 48 | 49 | ## Additional JavaScript Changes 50 | 51 | The JavaScript we provide out-of-the-box does not provide two key features you may want. 52 | 53 | 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```. 54 | 55 | ## Supporting Non-English Languages 56 | 57 | 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. 58 | 59 | In the code, you should locate the translation table in the function ```GetLocalizedStringForElement```. You should add translations for the text in the translation table. 60 | 61 | 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). 62 | 63 | ## Example 64 | 65 | ![Login Screenshot](./images/screenshot_paginated2.png) 66 | 67 | ## Contributing (Special Note) 68 | 69 | 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). 70 | 71 | For the full Contributing details, please see __[the root README](../README.md)__. 72 | -------------------------------------------------------------------------------- /centeredUi/ThemeCenterBrand.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0px; 3 | padding: 0px; 4 | } 5 | 6 | html, body { 7 | height: 100%; 8 | width: 100%; 9 | background-color: #ffffff; 10 | color: #000000; 11 | font-weight: normal; 12 | 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"; 13 | -ms-overflow-style: -ms-autohiding-scrollbar; 14 | } 15 | 16 | body { 17 | font-size: 0.9em; 18 | } 19 | 20 | #noScript { 21 | margin: 16px; 22 | color: Black; 23 | } 24 | 25 | :lang(en-GB) { 26 | quotes: '\2018' '\2019' '\201C' '\201D'; 27 | } 28 | 29 | :lang(zh) { 30 | font-family: 微软雅黑; 31 | } 32 | 33 | @-ms-viewport { 34 | width: device-width; 35 | } 36 | 37 | @-moz-viewport { 38 | width: device-width; 39 | } 40 | 41 | @-o-viewport { 42 | width: device-width; 43 | } 44 | 45 | @-webkit-viewport { 46 | width: device-width; 47 | } 48 | 49 | @viewport { 50 | width: device-width; 51 | } 52 | 53 | /* Theme layout styles */ 54 | 55 | #fullPage { 56 | position: absolute; 57 | bottom: 28px; 58 | top: 0px; 59 | width: 100%; 60 | } 61 | 62 | #brandingWrapper { 63 | background-color: #4488dd; 64 | height: 100%; 65 | width: 100%; 66 | } 67 | 68 | #branding { 69 | /* A background image will be added to the #branding element at run-time once the illustration image is configured in the theme. 70 | Recommended image dimensions: 1420x1200 pixels, JPG or PNG, 200 kB average, 500 kB maximum. */ 71 | background-color: inherit; 72 | background-repeat: no-repeat; 73 | background-size: cover; 74 | height: 100%; 75 | width: 100%; 76 | -webkit-background-size: cover; 77 | -moz-background-size: cover; 78 | -o-background-size: cover; 79 | } 80 | 81 | #brandingTint { 82 | /* This will define a tint to be overlaid on top of the illustration background image. */ 83 | background-color: rgba(0,0,0,0.4); 84 | height: 100%; 85 | width: 100%; 86 | -webkit-background-size: cover; 87 | -moz-background-size: cover; 88 | -o-background-size: cover; 89 | } 90 | 91 | #contentWrapper { 92 | background-color: transparent; 93 | height: 0px; 94 | width: 100%; 95 | } 96 | 97 | #content { 98 | /* Set content to center */ 99 | position: fixed; 100 | top: 50%; 101 | left: 50%; 102 | transform: translate(-50%, -50%); 103 | 104 | background-color: #fff; 105 | 106 | /* Set size margins */ 107 | margin-bottom: 28px; 108 | margin-left: auto; 109 | margin-right: auto; 110 | min-height: 235px; 111 | min-width: 320px; 112 | max-width: 412px; 113 | width: 338px; /*calc(100% - 40px); */ 114 | height: auto; 115 | padding: 36px; 116 | 117 | /* Add drop shadow */ 118 | box-shadow: 0 2px 3px rgba(0,0,0,0.55); 119 | border: 1px solid rgba(0,0,0,0.4); 120 | 121 | /* Allow Scrolling */ 122 | overflow-y: auto; 123 | max-height: 80%; 124 | } 125 | 126 | #header { 127 | font-size: 2em; 128 | font-weight: lighter; 129 | 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"; 130 | padding: 0px 0px 0px 0px; 131 | margin: 0px; 132 | height: 36px; 133 | width: 338px; 134 | background-color: transparent; 135 | } 136 | 137 | #header img { 138 | /* Logo image recommended dimension: 108x24 or 338x24 (elongated), 4 kB average, 10 kB maximum. Transparent PNG strongly recommended. */ 139 | width: auto; 140 | height: 100%; 141 | position: relative; 142 | top: -7px; 143 | } 144 | 145 | #loginMessage { 146 | box-sizing: border-box; 147 | color: rgb(38, 38, 38); 148 | direction: ltr; 149 | display: block; 150 | 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"; 151 | font-weight: 300; 152 | font-size: 1.2rem; 153 | height: auto; 154 | line-height: 28px; 155 | margin-bottom: 16px; 156 | margin-left: -2px; 157 | margin-right: -2px; 158 | margin-top: 16px; 159 | padding-bottom: 0px; 160 | padding-left: 0px; 161 | padding-right: 0px; 162 | padding-top: 0px; 163 | text-align: left; 164 | text-size-adjust: 100%; 165 | width: 342px; 166 | background-color: transparent; 167 | } 168 | 169 | #loginForm { 170 | width: 338px; 171 | } 172 | 173 | #workArea, #header { 174 | word-wrap: break-word; 175 | } 176 | 177 | #workArea { 178 | margin-bottom: 4%; 179 | margin-top: 16px; 180 | background-color: transparent; 181 | } 182 | 183 | #footerPlaceholder { 184 | height: 0px; 185 | } 186 | 187 | #footer { 188 | background-color: rgba(0, 0, 0, 0.6); 189 | bottom: 0px; 190 | box-sizing: border-box; 191 | clear: both; 192 | color: rgb(38, 38, 38); 193 | direction: ltr; 194 | display: block; 195 | filter: none; 196 | 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"; 197 | font-size: 15px; 198 | font-weight: normal; 199 | height: 28px; 200 | line-height: 20px; 201 | overflow-x: visible; 202 | overflow-y: visible; 203 | position: fixed; 204 | text-align: left; 205 | text-size-adjust: 100%; 206 | width: 100%; 207 | } 208 | 209 | #footerLinks { 210 | padding-left: 10px; 211 | } 212 | 213 | #copyright { 214 | box-sizing: border-box; 215 | color: rgb(255, 255, 255); 216 | direction: ltr; 217 | display: inline-block; 218 | 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"; 219 | font-size: 12px; 220 | font-weight: normal; 221 | height: 28px; 222 | line-height: 28px; 223 | margin-left: 8px; 224 | margin-right: 8px; 225 | text-align: left; 226 | text-size-adjust: 100%; 227 | } 228 | 229 | #userNameArea, #passwordArea { 230 | box-sizing: border-box; 231 | color: rgb(38, 38, 38); 232 | direction: ltr; 233 | display: block; 234 | 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"; 235 | font-size: 15px; 236 | font-weight: normal; 237 | height: 52px; 238 | line-height: 20px; 239 | margin-left: 0px; 240 | margin-right: -2px; 241 | text-align: left; 242 | text-size-adjust: 100%; 243 | width: 342px; 244 | } 245 | 246 | #updatePasswordForm #submitButton, #cancelButton { 247 | width: 48%; 248 | } 249 | 250 | #oldPasswordArea, #newPasswordArea { 251 | margin-bottom: 7px; 252 | } 253 | 254 | #errorMessage{ 255 | margin-top: 5px; 256 | } 257 | 258 | .pageLink { 259 | padding-left: 5px; 260 | padding-right: 5px; 261 | box-sizing: border-box; 262 | color: rgb(0, 0, 0); 263 | direction: ltr; 264 | display: inline-block; 265 | 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"; 266 | font-size: 12px; 267 | font-weight: normal; 268 | height: 28px; 269 | line-height: 28px; 270 | margin-left: 0px; 271 | margin-right: 0px; 272 | text-align: left; 273 | text-size-adjust: 100%; 274 | text-decoration: underline; 275 | } 276 | 277 | /* Common content styles */ 278 | 279 | .clear { 280 | clear: both; 281 | } 282 | 283 | .float { 284 | float: left; 285 | } 286 | 287 | .floatReverse { 288 | float: right; 289 | } 290 | 291 | .indent { 292 | margin-left: 16px; 293 | } 294 | 295 | .indentNonCollapsible { 296 | padding-left: 16px; 297 | } 298 | 299 | .hidden { 300 | display: none; 301 | } 302 | 303 | .notHidden { 304 | display: inherit; 305 | } 306 | 307 | .error { 308 | color: #e81123; 309 | } 310 | 311 | .actionLink { 312 | margin-top: 5px; 313 | margin-bottom: 8px; 314 | display: block; 315 | } 316 | 317 | a { 318 | color: #0067b8; 319 | text-decoration: none; 320 | background-color: transparent; 321 | word-wrap: normal; 322 | } 323 | 324 | ul { 325 | list-style-type: disc; 326 | } 327 | 328 | ul, ol, dd { 329 | padding: 0 0 0 16px; 330 | } 331 | 332 | h1, h2, h3, h4, h5, label { 333 | margin-bottom: 8px; 334 | } 335 | 336 | .submitMargin { 337 | margin-top: 18px; 338 | margin-bottom: 18px; 339 | } 340 | 341 | .topFieldMargin { 342 | margin-top: 8px; 343 | } 344 | 345 | .fieldMargin { 346 | margin-bottom: 8px; 347 | } 348 | 349 | .groupMargin { 350 | margin-bottom: 0px; 351 | } 352 | 353 | .sectionMargin { 354 | margin-bottom: 64px; 355 | } 356 | 357 | .block { 358 | display: block; 359 | } 360 | 361 | .autoWidth { 362 | width: auto; 363 | } 364 | 365 | .fullWidth { 366 | width: 342px; 367 | } 368 | 369 | .fullWidthIndent { 370 | width: 326px; 371 | } 372 | 373 | .smallTopSpacing { 374 | margin-top: 15px; 375 | } 376 | 377 | .mediumTopSpacing { 378 | margin-top: 25px; 379 | } 380 | 381 | .largeTopSpacing { 382 | margin-top: 35px; 383 | } 384 | 385 | .smallBottomSpacing { 386 | margin-bottom: 5px; 387 | } 388 | 389 | .mediumBottomSpacing { 390 | margin-bottom: 15px; 391 | } 392 | 393 | .largeBottomSpacing { 394 | margin-bottom: 25px; 395 | } 396 | 397 | #openingMessage { 398 | margin-bottom: 4px; 399 | } 400 | 401 | input { 402 | max-width: 100%; 403 | font-family: inherit; 404 | margin-top: 0px; 405 | margin-bottom: 0px; 406 | } 407 | 408 | input[type="radio"], input[type="checkbox"] { 409 | vertical-align: middle; 410 | margin-bottom: 0px; 411 | } 412 | 413 | span.submit, input[type="submit"] { 414 | align-items: flex-start; 415 | background-color: rgb(0, 103, 184); 416 | border-bottom-color: rgb(0, 103, 184); 417 | border-bottom-style: solid; 418 | border-bottom-width: 1px; 419 | border-image-outset: 0px; 420 | border-image-repeat: stretch; 421 | border-image-slice: 100%; 422 | border-image-source: none; 423 | border-image-width: 1; 424 | border-left-color: rgb(0, 103, 184); 425 | border-left-style: solid; 426 | border-left-width: 1px; 427 | border-right-color: rgb(0, 103, 184); 428 | border-right-style: solid; 429 | border-right-width: 1px; 430 | border-top-color: rgb(0, 103, 184); 431 | border-top-style: solid; 432 | border-top-width: 1px; 433 | box-sizing: border-box; 434 | color: rgb(255, 255, 255); 435 | cursor: pointer; 436 | direction: ltr; 437 | display: inline-block; 438 | 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"; 439 | font-size: 15px; 440 | font-stretch: normal; 441 | font-style: normal; 442 | font-variant-caps: normal; 443 | font-variant-ligatures: normal; 444 | font-variant-numeric: normal; 445 | font-weight: normal; 446 | height: 36px; 447 | letter-spacing: normal; 448 | line-height: 25px; 449 | margin-bottom: 0px; 450 | margin-left: 0px; 451 | margin-right: 0px; 452 | margin-top: 0px; 453 | max-width: 100%; 454 | overflow-x: hidden; 455 | overflow-y: hidden; 456 | padding-bottom: 4px; 457 | padding-left: 12px; 458 | padding-right: 12px; 459 | padding-top: 3px; 460 | position: relative; 461 | text-align: center; 462 | text-indent: 0px; 463 | text-overflow: ellipsis; 464 | text-rendering: auto; 465 | text-shadow: none; 466 | text-size-adjust: 100%; 467 | touch-action: manipulation; 468 | user-select: none; 469 | vertical-align: middle; 470 | white-space: nowrap; 471 | width: 100%; 472 | word-spacing: 0px; 473 | writing-mode: horizontal-tb; 474 | -webkit-appearance: none; 475 | -webkit-rtl-ordering: logical; 476 | -webkit-border-image: none; 477 | } 478 | 479 | input[type="submit"]:hover, span.submit:hover { 480 | background: rgb(0, 85, 152); 481 | border: 1px solid rgb(0, 85, 152); 482 | } 483 | 484 | 485 | input.text { 486 | background-color: rgba(255, 255, 255, 0.4); 487 | background-image: none; 488 | border-bottom-color: rgba(0, 0, 0, 0.6); 489 | border-bottom-style: solid; 490 | border-bottom-width: 1px; 491 | border-image-outset: 0px; 492 | border-image-repeat: stretch; 493 | border-image-slice: 100%; 494 | border-image-source: none; 495 | border-image-width: 1; 496 | border-top: none; 497 | border-left: none; 498 | border-right: none; 499 | box-sizing: border-box; 500 | color: rgb(38, 38, 38); 501 | cursor: auto; 502 | direction: ltr; 503 | display: block; 504 | 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"; 505 | font-size: 15px; 506 | font-stretch: normal; 507 | font-style: normal; 508 | font-variant-caps: normal; 509 | font-variant-ligatures: normal; 510 | font-variant-numeric: normal; 511 | font-weight: normal; 512 | height: 36px; 513 | letter-spacing: normal; 514 | line-height: 20px; 515 | margin-bottom: 0px; 516 | margin-left: 0px; 517 | margin-right: 0px; 518 | margin-top: 0px; 519 | max-width: 100%; 520 | outline-color: rgb(38, 38, 38); 521 | outline-style: none; 522 | outline-width: 0px; 523 | padding-bottom: 6px; 524 | padding-left: 0px; 525 | padding-right: 10px; 526 | padding-top: 6px; 527 | text-align: start; 528 | text-indent: 0px; 529 | text-rendering: auto; 530 | text-shadow: none; 531 | text-size-adjust: 100%; 532 | text-transform: none; 533 | user-select: text; 534 | width: 338px; 535 | word-spacing: 0px; 536 | writing-mode: horizontal-tb; 537 | -webkit-appearance: none; 538 | -webkit-locale: "en"; 539 | -webkit-rtl-ordering: logical; 540 | -webkit-border-image: none; 541 | } 542 | 543 | input.text:focus { 544 | border: 1px solid #0067B8; 545 | border-top: none; 546 | border-left: none; 547 | border-right: none; 548 | } 549 | 550 | select { 551 | height: 28px; 552 | min-width: 60px; 553 | max-width: 100%; 554 | margin-bottom: 8px; 555 | white-space: nowrap; 556 | overflow: hidden; 557 | box-shadow: none; 558 | padding: 2px; 559 | font-family: inherit; 560 | } 561 | 562 | h1, .giantText { 563 | font-size: 2.0em; 564 | font-weight: lighter; 565 | } 566 | 567 | h2, .bigText { 568 | font-size: 1.33em; 569 | font-weight: lighter; 570 | } 571 | 572 | h3, .normalText { 573 | font-size: 1.0em; 574 | font-weight: normal; 575 | } 576 | 577 | h4, .smallText { 578 | font-size: 0.9em; 579 | font-weight: normal; 580 | } 581 | 582 | h4 { 583 | font-size: 0.7em; 584 | font-weight: normal; 585 | } 586 | 587 | h5, .tinyText { 588 | font-size: 0.8em; 589 | font-weight: normal; 590 | } 591 | 592 | .hint { 593 | color: #999999; 594 | } 595 | 596 | .emphasis { 597 | font-weight: 700; 598 | color: #2F2F2F; 599 | } 600 | 601 | .smallIcon { 602 | height: 20px; 603 | padding-right: 12px; 604 | vertical-align: middle; 605 | } 606 | 607 | .largeIcon { 608 | height: 48px; 609 | /* width:48px; */ 610 | vertical-align: middle; 611 | } 612 | 613 | .largeTextNoWrap { 614 | height: 48px; 615 | display: table-cell; /* needed when in float*/ 616 | vertical-align: middle; 617 | white-space: nowrap; 618 | font-size: 1.2em; 619 | } 620 | 621 | .idp { 622 | height: 48px; 623 | clear: both; 624 | padding: 8px; 625 | overflow: hidden; 626 | cursor: pointer; 627 | } 628 | 629 | .idp:hover { 630 | background-color: #cccccc; 631 | cursor: pointer; 632 | } 633 | 634 | .idpDescription { 635 | width: 80%; 636 | cursor: pointer; 637 | } 638 | 639 | .identityBanner{ 640 | color: black; 641 | text-align: left; 642 | white-space: nowrap; 643 | line-height: 28px; 644 | height: 28px; 645 | 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"; 646 | font-size: 15px; 647 | font-weight: 300; 648 | white-space: nowrap; 649 | overflow: hidden; 650 | -o-text-overflow: ellipsis; 651 | text-overflow: ellipsis; 652 | } 653 | 654 | .submit.backButton{ 655 | color: black; 656 | width: 108px; 657 | float: left; 658 | background: #CCCCCC; 659 | border-color: #CCCCCC; 660 | margin-left: -2px; 661 | height: 32px; 662 | position: relative; 663 | left:110px; 664 | bottom:17px; 665 | } 666 | 667 | input[type="submit"].backButton:hover, span.submit.backButton:hover { 668 | background: #AAA; 669 | border: 1px solid #AAA; 670 | } 671 | 672 | .submit.nextButton{ 673 | margin-left: -2px; 674 | left:229px; 675 | bottom:-40px; 676 | height: 32px; 677 | width: 108px; 678 | } 679 | 680 | .submit.modifiedSignIn{ 681 | display: block; 682 | width: auto; 683 | position: relative; 684 | height: 32px; 685 | width: 108px; 686 | left:229px; 687 | bottom:-15px; 688 | } 689 | 690 | #submissionArea{ 691 | margin-top: 8px; 692 | } 693 | 694 | @media (max-width: 600px), (max-height: 392px){ 695 | #content { 696 | /* Set content to center */ 697 | position: relative; 698 | top: 0; 699 | left: 0; 700 | transform: none; 701 | background-color: #fff; 702 | /* Set size margins */ 703 | margin-bottom: 28px; 704 | margin-left: auto; 705 | margin-right: auto; 706 | min-height: 290px; 707 | min-width: 320px; 708 | max-width: 412px; 709 | width: calc(100% - 40px); 710 | height: auto; 711 | padding: 23px 18px 0px 18px; 712 | /* Add drop shadow */ 713 | box-shadow: 0 0 0 rgba(0,0,0,0); 714 | border: 0px solid rgba(0,0,0,0); 715 | 716 | /* Allow Scrolling */ 717 | overflow-y: initial; 718 | max-height: initial; 719 | } 720 | 721 | #footer { 722 | background-color: rgba(0, 0, 0, 0.6); 723 | bottom: 0px; 724 | box-sizing: border-box; 725 | clear: both; 726 | color: rgb(38, 38, 38); 727 | direction: ltr; 728 | display: block; 729 | filter: none; 730 | 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"; 731 | font-size: 15px; 732 | font-weight: normal; 733 | height: 28px; 734 | line-height: 20px; 735 | overflow-x: visible; 736 | overflow-y: visible; 737 | position: fixed; 738 | text-align: left; 739 | text-size-adjust: 100%; 740 | width: 100%; 741 | } 742 | 743 | #brandingWrapper { 744 | display: none; 745 | } 746 | 747 | input.text { 748 | font-size: 16px; 749 | } 750 | 751 | .identityBanner { 752 | margin:16px 0px; 753 | padding:0px 80px 0px 40px; 754 | } 755 | 756 | .identityBannerImage { 757 | left: -50px; 758 | } 759 | } 760 | 761 | /* Targets displays using any of Windows’ High Contrast Mode themes: */ 762 | @media screen and (-ms-high-contrast: active) { 763 | textarea::-webkit-input-placeholder { 764 | color: #00FF00; 765 | } 766 | 767 | textarea:-moz-placeholder { /* Firefox 18- */ 768 | color: #00FF00; 769 | } 770 | 771 | textarea::-moz-placeholder { /* Firefox 19+ */ 772 | color: #00FF00; 773 | } 774 | 775 | textarea:-ms-input-placeholder { 776 | color: #00FF00; 777 | } 778 | } 779 | 780 | /* Targets displays using the Windows’ "High Contrast Black" theme: */ 781 | @media screen and (-ms-high-contrast: white-on-black) { 782 | #contentWrapper { 783 | background-color: #000000; 784 | color: #ffffff; 785 | } 786 | .idp:hover { 787 | background-color: #ffffff; 788 | color: #000000; 789 | } 790 | #brandingWrapper { 791 | background-color: #000000; 792 | color: #ffffff; 793 | } 794 | html, body { 795 | background-color: #000000; 796 | color: #ffffff; 797 | } 798 | 799 | textarea::-webkit-input-placeholder { 800 | color: #ffffff; 801 | } 802 | 803 | textarea:-moz-placeholder { /* Firefox 18- */ 804 | color: #ffffff; 805 | } 806 | 807 | textarea::-moz-placeholder { /* Firefox 19+ */ 808 | color: #ffffff; 809 | } 810 | 811 | textarea:-ms-input-placeholder { 812 | color: #ffffff; 813 | } 814 | } 815 | 816 | /* Targets displays using the Windows’ "High Contrast White" theme: */ 817 | @media screen and (-ms-high-contrast: black-on-white) { 818 | #contentWrapper { 819 | background-color: #ffffff; 820 | color: #000000; 821 | } 822 | 823 | .idp:hover { 824 | background-color: #000000; 825 | color: #ffffff; 826 | } 827 | 828 | #brandingWrapper { 829 | background-color: #ffffff; 830 | color: #000000; 831 | } 832 | 833 | html, body { 834 | background-color: #ffffff; 835 | color: #000000; 836 | } 837 | 838 | textarea::-webkit-input-placeholder { 839 | color: #000000; 840 | } 841 | 842 | textarea:-moz-placeholder { /* Firefox 18- */ 843 | color: #000000; 844 | } 845 | 846 | textarea::-moz-placeholder { /* Firefox 19+ */ 847 | color: #000000; 848 | } 849 | 850 | textarea:-ms-input-placeholder { 851 | color: #000000; 852 | } 853 | } 854 | -------------------------------------------------------------------------------- /centeredUi/ThemeCenterBrandRTL.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0px; 3 | padding: 0px; 4 | } 5 | 6 | html, body { 7 | height: 100%; 8 | width: 100%; 9 | background-color: #ffffff; 10 | color: #000000; 11 | font-weight: normal; 12 | 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"; 13 | -ms-overflow-style: -ms-autohiding-scrollbar; 14 | } 15 | 16 | body { 17 | font-size: 0.9em; 18 | } 19 | 20 | #noScript { 21 | margin: 16px; 22 | color: Black; 23 | } 24 | 25 | :lang(en-GB) { 26 | quotes: '\2018' '\2019' '\201C' '\201D'; 27 | } 28 | 29 | :lang(zh) { 30 | font-family: 微软雅黑; 31 | } 32 | 33 | @-ms-viewport { 34 | width: device-width; 35 | } 36 | 37 | @-moz-viewport { 38 | width: device-width; 39 | } 40 | 41 | @-o-viewport { 42 | width: device-width; 43 | } 44 | 45 | @-webkit-viewport { 46 | width: device-width; 47 | } 48 | 49 | @viewport { 50 | width: device-width; 51 | } 52 | 53 | /* Theme layout styles */ 54 | 55 | #fullPage { 56 | position: absolute; 57 | bottom: 28px; 58 | top: 0px; 59 | width: 100%; 60 | } 61 | 62 | #brandingWrapper { 63 | background-color: #4488dd; 64 | height: 100%; 65 | width: 100%; 66 | } 67 | 68 | #branding { 69 | /* A background image will be added to the #branding element at run-time once the illustration image is configured in the theme. 70 | Recommended image dimensions: 1420x1200 pixels, JPG or PNG, 200 kB average, 500 kB maximum. */ 71 | background-color: inherit; 72 | background-repeat: no-repeat; 73 | background-size: cover; 74 | height: 100%; 75 | width: 100%; 76 | -webkit-background-size: cover; 77 | -moz-background-size: cover; 78 | -o-background-size: cover; 79 | } 80 | 81 | #contentWrapper { 82 | background-color: transparent; 83 | height: 0px; 84 | width: 100%; 85 | } 86 | 87 | #content { 88 | /* Set content to center */ 89 | position: fixed; 90 | top: 50%; 91 | right: 50%; 92 | transform: translate(50%, -50%); 93 | 94 | background-color: #fff; 95 | 96 | /* Set size margins */ 97 | margin-bottom: 28px; 98 | margin-right: auto; 99 | margin-left: auto; 100 | min-height: 235px; 101 | min-width: 320px; 102 | max-width: 412px; 103 | width: 338px; /*calc(100% - 40px); */ 104 | height: auto; 105 | padding: 36px; 106 | 107 | /* Add drop shadow */ 108 | box-shadow: 0 2px 3px rgba(0,0,0,0.55); 109 | border: 1px solid rgba(0,0,0,0.4); 110 | 111 | /* Allow Scrolling */ 112 | overflow-y: auto; 113 | max-height: 80%; 114 | } 115 | 116 | #header { 117 | font-size: 2em; 118 | font-weight: lighter; 119 | 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"; 120 | padding: 0px 0px 0px 0px; 121 | margin: 0px; 122 | height: 36px; 123 | width: 338px; 124 | background-color: transparent; 125 | } 126 | 127 | #header img { 128 | /* Logo image recommended dimension: 108x24 or 338x24 (elongated), 4 kB average, 10 kB maximum. Transparent PNG strongly recommended. */ 129 | width: auto; 130 | height: 100%; 131 | position: relative; 132 | top: -7px; 133 | } 134 | 135 | #loginMessage { 136 | box-sizing: border-box; 137 | color: rgb(38, 38, 38); 138 | direction: rtl; 139 | display: block; 140 | 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"; 141 | font-weight: 300; 142 | font-size: 1.2rem; 143 | height: auto; 144 | line-height: 28px; 145 | margin-bottom: 16px; 146 | margin-right: -2px; 147 | margin-left: -2px; 148 | margin-top: 16px; 149 | padding-bottom: 0px; 150 | padding-right: 0px; 151 | padding-left: 0px; 152 | padding-top: 0px; 153 | text-align: right; 154 | text-size-adjust: 100%; 155 | width: 342px; 156 | background-color: transparent; 157 | } 158 | 159 | #loginForm { 160 | width: 338px; 161 | } 162 | 163 | #workArea, #header { 164 | word-wrap: break-word; 165 | } 166 | 167 | #workArea { 168 | margin-bottom: 4%; 169 | margin-top: 16px; 170 | background-color: transparent; 171 | } 172 | 173 | #footerPlaceholder { 174 | height: 0px; 175 | } 176 | 177 | #footer { 178 | background-color: rgba(0, 0, 0, 0.6); 179 | bottom: 0px; 180 | box-sizing: border-box; 181 | clear: both; 182 | color: rgb(38, 38, 38); 183 | direction: rtl; 184 | display: block; 185 | filter: none; 186 | 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"; 187 | font-size: 15px; 188 | font-weight: normal; 189 | height: 28px; 190 | line-height: 20px; 191 | overflow-x: visible; 192 | overflow-y: visible; 193 | position: fixed; 194 | text-align: right; 195 | text-size-adjust: 100%; 196 | width: 100%; 197 | } 198 | 199 | #footerLinks { 200 | padding-right: 10px; 201 | } 202 | 203 | #copyright { 204 | box-sizing: border-box; 205 | color: rgb(255, 255, 255); 206 | direction: rtl; 207 | display: inline-block; 208 | 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"; 209 | font-size: 12px; 210 | font-weight: normal; 211 | height: 28px; 212 | line-height: 28px; 213 | margin-right: 8px; 214 | margin-left: 8px; 215 | text-align: right; 216 | text-size-adjust: 100%; 217 | } 218 | 219 | #userNameArea, #passwordArea { 220 | box-sizing: border-box; 221 | color: rgb(38, 38, 38); 222 | direction: rtl; 223 | display: block; 224 | 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"; 225 | font-size: 15px; 226 | font-weight: normal; 227 | height: 52px; 228 | line-height: 20px; 229 | margin-right: 0px; 230 | margin-left: -2px; 231 | text-align: right; 232 | text-size-adjust: 100%; 233 | width: 342px; 234 | } 235 | 236 | #updatePasswordForm #submitButton, #cancelButton { 237 | width: 48%; 238 | } 239 | 240 | #oldPasswordArea, #newPasswordArea { 241 | margin-bottom: 7px; 242 | } 243 | 244 | #errorMessage{ 245 | margin-top: 5px; 246 | } 247 | 248 | .pageLink { 249 | padding-right: 5px; 250 | padding-left: 5px; 251 | box-sizing: border-box; 252 | color: rgb(0, 0, 0); 253 | direction: rtl; 254 | display: inline-block; 255 | 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"; 256 | font-size: 12px; 257 | font-weight: normal; 258 | height: 28px; 259 | line-height: 28px; 260 | margin-right: 0px; 261 | margin-left: 0px; 262 | text-align: right; 263 | text-size-adjust: 100%; 264 | text-decoration: underline; 265 | } 266 | 267 | /* Common content styles */ 268 | 269 | .clear { 270 | clear: both; 271 | } 272 | 273 | .float { 274 | float: right; 275 | } 276 | 277 | .floatReverse { 278 | float: left; 279 | } 280 | 281 | .indent { 282 | margin-right: 16px; 283 | } 284 | 285 | .indentNonCollapsible { 286 | padding-right: 16px; 287 | } 288 | 289 | .hidden { 290 | display: none; 291 | } 292 | 293 | .notHidden { 294 | display: inherit; 295 | } 296 | 297 | .error { 298 | color: #e81123; 299 | } 300 | 301 | .actionLink { 302 | margin-top: 5px; 303 | margin-bottom: 8px; 304 | display: block; 305 | } 306 | 307 | a { 308 | color: #0067b8; 309 | text-decoration: none; 310 | background-color: transparent; 311 | word-wrap: normal; 312 | } 313 | 314 | ul { 315 | list-style-type: disc; 316 | } 317 | 318 | ul, ol, dd { 319 | padding: 0 16px 0 0; 320 | } 321 | 322 | h1, h2, h3, h4, h5, label { 323 | margin-bottom: 8px; 324 | } 325 | 326 | .submitMargin { 327 | margin-top: 18px; 328 | margin-bottom: 18px; 329 | } 330 | 331 | .topFieldMargin { 332 | margin-top: 8px; 333 | } 334 | 335 | .fieldMargin { 336 | margin-bottom: 8px; 337 | } 338 | 339 | .groupMargin { 340 | margin-bottom: 0px; 341 | } 342 | 343 | .sectionMargin { 344 | margin-bottom: 64px; 345 | } 346 | 347 | .block { 348 | display: block; 349 | } 350 | 351 | .autoWidth { 352 | width: auto; 353 | } 354 | 355 | .fullWidth { 356 | width: 342px; 357 | } 358 | 359 | .fullWidthIndent { 360 | width: 326px; 361 | } 362 | 363 | .smallTopSpacing { 364 | margin-top: 15px; 365 | } 366 | 367 | .mediumTopSpacing { 368 | margin-top: 25px; 369 | } 370 | 371 | .largeTopSpacing { 372 | margin-top: 35px; 373 | } 374 | 375 | .smallBottomSpacing { 376 | margin-bottom: 5px; 377 | } 378 | 379 | .mediumBottomSpacing { 380 | margin-bottom: 15px; 381 | } 382 | 383 | .largeBottomSpacing { 384 | margin-bottom: 25px; 385 | } 386 | 387 | #openingMessage { 388 | margin-bottom: 4px; 389 | } 390 | 391 | input { 392 | max-width: 100%; 393 | font-family: inherit; 394 | margin-top: 0px; 395 | margin-bottom: 0px; 396 | } 397 | 398 | input[type="radio"], input[type="checkbox"] { 399 | vertical-align: middle; 400 | margin-bottom: 0px; 401 | } 402 | 403 | span.submit, input[type="submit"] { 404 | align-items: flex-start; 405 | background-color: rgb(0, 103, 184); 406 | border-bottom-color: rgb(0, 103, 184); 407 | border-bottom-style: solid; 408 | border-bottom-width: 1px; 409 | border-image-outset: 0px; 410 | border-image-repeat: stretch; 411 | border-image-slice: 100%; 412 | border-image-source: none; 413 | border-image-width: 1; 414 | border-right-color: rgb(0, 103, 184); 415 | border-right-style: solid; 416 | border-right-width: 1px; 417 | border-left-color: rgb(0, 103, 184); 418 | border-left-style: solid; 419 | border-left-width: 1px; 420 | border-top-color: rgb(0, 103, 184); 421 | border-top-style: solid; 422 | border-top-width: 1px; 423 | box-sizing: border-box; 424 | color: rgb(255, 255, 255); 425 | cursor: pointer; 426 | direction: rtl; 427 | display: inline-block; 428 | 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"; 429 | font-size: 15px; 430 | font-stretch: normal; 431 | font-style: normal; 432 | font-variant-caps: normal; 433 | font-variant-ligatures: normal; 434 | font-variant-numeric: normal; 435 | font-weight: normal; 436 | height: 36px; 437 | letter-spacing: normal; 438 | line-height: 25px; 439 | margin-bottom: 0px; 440 | margin-right: 0px; 441 | margin-left: 0px; 442 | margin-top: 0px; 443 | max-width: 100%; 444 | overflow-x: hidden; 445 | overflow-y: hidden; 446 | padding-bottom: 4px; 447 | padding-right: 12px; 448 | padding-left: 12px; 449 | padding-top: 3px; 450 | position: relative; 451 | text-align: center; 452 | text-indent: 0px; 453 | text-overflow: ellipsis; 454 | text-rendering: auto; 455 | text-shadow: none; 456 | text-size-adjust: 100%; 457 | touch-action: manipulation; 458 | user-select: none; 459 | vertical-align: middle; 460 | white-space: nowrap; 461 | width: 100%; 462 | word-spacing: 0px; 463 | writing-mode: horizontal-tb; 464 | -webkit-appearance: none; 465 | -webkit-rtl-ordering: logical; 466 | -webkit-border-image: none; 467 | } 468 | 469 | input[type="submit"]:hover, span.submit:hover { 470 | background: rgb(0, 85, 152); 471 | border: 1px solid rgb(0, 85, 152); 472 | } 473 | 474 | input.text { 475 | background-color: rgba(255, 255, 255, 0.4); 476 | background-image: none; 477 | border-bottom-color: rgba(0, 0, 0, 0.6); 478 | border-bottom-style: solid; 479 | border-bottom-width: 1px; 480 | border-image-outset: 0px; 481 | border-image-repeat: stretch; 482 | border-image-slice: 100%; 483 | border-image-source: none; 484 | border-image-width: 1; 485 | border-top: none; 486 | border-left: none 487 | border-right: none; 488 | box-sizing: border-box; 489 | color: rgb(38, 38, 38); 490 | cursor: auto; 491 | direction: rtl; 492 | display: block; 493 | 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"; 494 | font-size: 15px; 495 | font-stretch: normal; 496 | font-style: normal; 497 | font-variant-caps: normal; 498 | font-variant-ligatures: normal; 499 | font-variant-numeric: normal; 500 | font-weight: normal; 501 | height: 36px; 502 | letter-spacing: normal; 503 | line-height: 20px; 504 | margin-bottom: 0px; 505 | margin-right: 0px; 506 | margin-left: 0px; 507 | margin-top: 0px; 508 | max-width: 100%; 509 | outline-color: rgb(38, 38, 38); 510 | outline-style: none; 511 | outline-width: 0px; 512 | padding-bottom: 6px; 513 | padding-right: 10px; 514 | padding-left: 10px; 515 | padding-top: 6px; 516 | text-align: start; 517 | text-indent: 0px; 518 | text-rendering: auto; 519 | text-shadow: none; 520 | text-size-adjust: 100%; 521 | text-transform: none; 522 | user-select: text; 523 | width: 338px; 524 | word-spacing: 0px; 525 | writing-mode: horizontal-tb; 526 | -webkit-appearance: none; 527 | -webkit-locale: "en"; 528 | -webkit-rtl-ordering: logical; 529 | -webkit-border-image: none; 530 | } 531 | 532 | input.text:focus { 533 | border: 1px solid #0067B8; 534 | border-top: none; 535 | border-left: none; 536 | border-right: none; 537 | } 538 | 539 | select { 540 | height: 28px; 541 | min-width: 60px; 542 | max-width: 100%; 543 | margin-bottom: 8px; 544 | white-space: nowrap; 545 | overflow: hidden; 546 | box-shadow: none; 547 | padding: 2px; 548 | font-family: inherit; 549 | } 550 | 551 | h1, .giantText { 552 | font-size: 2.0em; 553 | font-weight: lighter; 554 | } 555 | 556 | h2, .bigText { 557 | font-size: 1.33em; 558 | font-weight: lighter; 559 | } 560 | 561 | h3, .normalText { 562 | font-size: 1.0em; 563 | font-weight: normal; 564 | } 565 | 566 | h4, .smallText { 567 | font-size: 0.9em; 568 | font-weight: normal; 569 | } 570 | 571 | h4 { 572 | font-size: 0.7em; 573 | font-weight: normal; 574 | } 575 | 576 | h5, .tinyText { 577 | font-size: 0.8em; 578 | font-weight: normal; 579 | } 580 | 581 | .hint { 582 | color: #999999; 583 | } 584 | 585 | .emphasis { 586 | font-weight: 700; 587 | color: #2F2F2F; 588 | } 589 | 590 | .smallIcon { 591 | height: 20px; 592 | padding-left: 12px; 593 | vertical-align: middle; 594 | } 595 | 596 | .largeIcon { 597 | height: 48px; 598 | /* width:48px; */ 599 | vertical-align: middle; 600 | } 601 | 602 | .largeTextNoWrap { 603 | height: 48px; 604 | display: table-cell; /* needed when in float*/ 605 | vertical-align: middle; 606 | white-space: nowrap; 607 | font-size: 1.2em; 608 | } 609 | 610 | .idp { 611 | height: 48px; 612 | clear: both; 613 | padding: 8px; 614 | overflow: hidden; 615 | cursor: pointer; 616 | } 617 | 618 | .idp:hover { 619 | background-color: #cccccc; 620 | cursor: pointer; 621 | } 622 | 623 | .idpDescription { 624 | width: 80%; 625 | cursor: pointer; 626 | } 627 | 628 | .identityBanner{ 629 | color: black; 630 | text-align: left; 631 | white-space: nowrap; 632 | line-height: 28px; 633 | height: 28px; 634 | 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"; 635 | font-size: 15px; 636 | font-weight: 300; 637 | white-space: nowrap; 638 | overflow: hidden; 639 | -o-text-overflow: ellipsis; 640 | text-overflow: ellipsis; 641 | padding-left:20px; 642 | } 643 | 644 | .submit.backButton{ 645 | color: black; 646 | width: 108px; 647 | float: left; 648 | background: #CCCCCC; 649 | border-color: #CCCCCC; 650 | margin-left: -2px; 651 | height: 32px; 652 | position: relative; 653 | left:110px; 654 | bottom:17px; 655 | } 656 | 657 | input[type="submit"].backButton:hover, span.submit.backButton:hover { 658 | background: #AAA; 659 | border: 1px solid #AAA; 660 | } 661 | 662 | .submit.nextButton{ 663 | margin-left: -2px; 664 | left:229px; 665 | bottom:-40px; 666 | height: 32px; 667 | width: 108px; 668 | } 669 | 670 | .submit.modifiedSignIn{ 671 | display: block; 672 | width: auto; 673 | position: relative; 674 | height: 32px; 675 | width: 108px; 676 | left:229px; 677 | bottom:-15px; 678 | } 679 | 680 | #submissionArea{ 681 | margin-top: 8px; 682 | } 683 | 684 | @media (max-width: 600px), (max-height: 392px){ 685 | #content { 686 | /* Set content to center */ 687 | position: relative; 688 | top: 0; 689 | right: 0; 690 | transform: none; 691 | background-color: #fff; 692 | /* Set size margins */ 693 | margin-bottom: 28px; 694 | margin-right: auto; 695 | margin-left: auto; 696 | min-height: 290px; 697 | min-width: 320px; 698 | max-width: 412px; 699 | width: calc(100% - 40px); 700 | height: auto; 701 | padding: 23px 18px 0px 18px; 702 | /* Add drop shadow */ 703 | box-shadow: 0 0 0 rgba(0,0,0,0); 704 | border: 0px solid rgba(0,0,0,0); 705 | 706 | /* Allow Scrolling */ 707 | overflow-y: initial; 708 | max-height: initial; 709 | } 710 | 711 | #footer { 712 | background-color: rgba(0, 0, 0, 0.6); 713 | bottom: 0px; 714 | box-sizing: border-box; 715 | clear: both; 716 | color: rgb(38, 38, 38); 717 | direction: rtl; 718 | display: block; 719 | filter: none; 720 | 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"; 721 | font-size: 15px; 722 | font-weight: normal; 723 | height: 28px; 724 | line-height: 20px; 725 | overflow-x: visible; 726 | overflow-y: visible; 727 | position: fixed; 728 | text-align: right; 729 | text-size-adjust: 100%; 730 | width: 100%; 731 | } 732 | 733 | #brandingWrapper { 734 | display: none; 735 | } 736 | } 737 | 738 | /* Targets displays using any of Windows’ High Contrast Mode themes: */ 739 | @media screen and (-ms-high-contrast: active) { 740 | textarea::-webkit-input-placeholder { 741 | color: #00FF00; 742 | } 743 | 744 | textarea:-moz-placeholder { /* Firefox 18- */ 745 | color: #00FF00; 746 | } 747 | 748 | textarea::-moz-placeholder { /* Firefox 19+ */ 749 | color: #00FF00; 750 | } 751 | 752 | textarea:-ms-input-placeholder { 753 | color: #00FF00; 754 | } 755 | } 756 | 757 | /* Targets displays using the Windows’ "High Contrast Black" theme: */ 758 | @media screen and (-ms-high-contrast: white-on-black) { 759 | #contentWrapper { 760 | background-color: #000000; 761 | color: #ffffff; 762 | } 763 | .idp:hover { 764 | background-color: #ffffff; 765 | color: #000000; 766 | } 767 | #brandingWrapper { 768 | background-color: #000000; 769 | color: #ffffff; 770 | } 771 | html, body { 772 | background-color: #000000; 773 | color: #ffffff; 774 | } 775 | 776 | textarea::-webkit-input-placeholder { 777 | color: #ffffff; 778 | } 779 | 780 | textarea:-moz-placeholder { /* Firefox 18- */ 781 | color: #ffffff; 782 | } 783 | 784 | textarea::-moz-placeholder { /* Firefox 19+ */ 785 | color: #ffffff; 786 | } 787 | 788 | textarea:-ms-input-placeholder { 789 | color: #ffffff; 790 | } 791 | } 792 | 793 | /* Targets displays using the Windows’ "High Contrast White" theme: */ 794 | @media screen and (-ms-high-contrast: black-on-white) { 795 | #contentWrapper { 796 | background-color: #ffffff; 797 | color: #000000; 798 | } 799 | 800 | .idp:hover { 801 | background-color: #000000; 802 | color: #ffffff; 803 | } 804 | 805 | #brandingWrapper { 806 | background-color: #ffffff; 807 | color: #000000; 808 | } 809 | 810 | html, body { 811 | background-color: #ffffff; 812 | color: #000000; 813 | } 814 | 815 | textarea::-webkit-input-placeholder { 816 | color: #000000; 817 | } 818 | 819 | textarea:-moz-placeholder { /* Firefox 18- */ 820 | color: #000000; 821 | } 822 | 823 | textarea::-moz-placeholder { /* Firefox 19+ */ 824 | color: #000000; 825 | } 826 | 827 | textarea:-ms-input-placeholder { 828 | color: #000000; 829 | } 830 | } -------------------------------------------------------------------------------- /centeredUi/images/empty_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/centeredUi/images/empty_user.png -------------------------------------------------------------------------------- /centeredUi/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/centeredUi/images/screenshot.png -------------------------------------------------------------------------------- /centeredUi/images/screenshot_paginated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/centeredUi/images/screenshot_paginated.png -------------------------------------------------------------------------------- /centeredUi/images/screenshot_paginated2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/centeredUi/images/screenshot_paginated2.png -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/Digitude.Adfs.CustomImagesThemeGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2018 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digitude.Adfs.CustomImagesThemeGenerator", "src\Digitude.Adfs.CustomImagesThemeGenerator.csproj", "{F645A08A-3C1F-4260-BD12-823681D07E3E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F645A08A-3C1F-4260-BD12-823681D07E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F645A08A-3C1F-4260-BD12-823681D07E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F645A08A-3C1F-4260-BD12-823681D07E3E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F645A08A-3C1F-4260-BD12-823681D07E3E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {EF0428E3-0C32-4E7F-A055-AA3E93EDDB72} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/README.md: -------------------------------------------------------------------------------- 1 | Update ADFS webtheme in order to display different IDP images 2 | ============================================================== 3 | 4 | Concept 5 | ------- 6 | 7 | To modify the layout of the ADFS home realm detection page, ADFS provides the concept of a web theme. 8 | 9 | A web theme is a collection of following web artifacts: 10 | 11 | \+---css 12 | ¦       style.css 13 | ¦       style.rtl.css 14 | ¦ 15 | +---illustration 16 | ¦       illustration.png 17 | ¦ 18 | +---images 19 | ¦   +---idp 20 | ¦           idp.png 21 | ¦           localsts.png 22 | ¦           otherorganizations.png 23 | ¦ 24 | +---script 25 |         onload.js 26 | 27 | This tree structure holds the artifacts we will update in order to be able to show for each IDP (identity provider) a different image. 28 | 29 | ADFS also provides a couple of PowerShell cmdlets to work with the concept of web theme. 30 | 31 | The cmdlets we will use are: 32 | 33 | - **Get-AdfsWebTheme -Name WebThemeName** 34 | 35 | - Retrieve a reference to an existing web theme or null if it doesn’t exist. 36 | 37 | 38 | - **New-AdfsWebTheme –Name WebThemeName –SourceName default** 39 | 40 | - Create a new webtheme based on an existing webtheme. 41 | 42 | 43 | - **Set-AdfsWebTheme -TargetName WebThemeName -AdditionalFileResource …** 44 | 45 | - Associate additional artifacts with an existing webtheme (images, customized onload script, custom stylesheet, …) 46 | 47 | 48 | - **Set-AdfsWebConfig -ActiveThemeName WebThemeName** 49 | 50 | - Activate an existing webtheme via its name. 51 | 52 | 53 | Tooling 54 | ------- 55 | 56 | Although the concept of performing these kind of updates is not difficult, the mere fact that the ADFS webtheme cmdlets have to be invoked using references to external resources makes it an repetitive and error prone task. To avoid this I have created a very small RAD utility that can speed up dramatically this task, even more if there are a lot of identity providers on the home realm detection page. 57 | 58 | ![](images/9544fd2a4ad07f3956b38493ab2d3453.png) 59 | 60 | You can populate the list of display names manually. These names have to correspond with the display names they have been given in the ADFS management 61 | console. You can also export the list of claim provider trust display names to a text file as follows: 62 | 63 | **Get-AdfsClaimsProviderTrust \| select \@{Name="CPTName";Expression={\$_.Name}} \> [outputFile].txt** 64 | 65 | You can then import the list of display names in the utility by clicking on the button ![](images/3bbc0a40203d4ce7ba0c4d97b8b7dd93.png). 66 | 67 | Once the list of IDP display names is imported you can browse for a corresponding image. Optionally you can also change the translation of the display name (the utility provides translation to Dutch and French, but this is easy to change for other languages). 68 | You have the option of saving the definition file (extension \*.awi) and reloading a definition file. 69 | 70 | If you already have customized the onload.js or style.css, you can browse for these artifacts. The modifications for this utility will then be applied to the selected artifacts in stead of the default ones (the plain vanilla ADFS 2016 version). 71 | 72 | If all the display names have an associated image, you click on the button ‘Generate’. The tool will ask for a name to save the file as, and will create a zip archive with the selected name. 73 | 74 | The zip file contains the following artifacts: 75 | 76 | ![](images/c587fa46b0cf0b5afbac869eb426db97.png) 77 | 78 | Archive contents (example with the four IDP's)
79 | -------------------- 80 | 81 | ### CreateOrUpdateAdfsWebTheme.ps1
82 | 83 | This file contains the collection of ADFS PowerShell cmdlets that perform the webtheme update: 84 | 85 | ``` 86 | 87 | $webThemeName = 'Test' 88 | 89 | $webTheme = Get-AdfsWebTheme -Name \$webThemeName 90 | 91 | if (!\$webTheme) 92 | { 93 | New-AdfsWebTheme -Name Test -SourceName default 94 | } 95 | 96 | Set-AdfsWebTheme -TargetName Test -AdditionalFileResource \@{Uri="/adfs/portal/images/idp/IDP1.png";path="IDP1.png"} 97 | 98 | Set-AdfsWebTheme -TargetName Test -AdditionalFileResource \@{Uri="/adfs/portal/images/idp/IDP2.png";path="IDP2.png"} 99 | 100 | Set-AdfsWebTheme -TargetName Test -AdditionalFileResource \@{Uri="/adfs/portal/images/idp/IDP3.png";path="IDP3.png"} 101 | 102 | Set-AdfsWebTheme -TargetName Test -AdditionalFileResource \@{Uri="/adfs/portal/images/idp/IDP4.png";path="IDP4.png"} 103 | 104 | Set-AdfsWebTheme -TargetName Test -AdditionalFileResource \@{Uri="/adfs/portal/script/onload.js";path="customOnload.js"} 105 | 106 | Set-AdfsWebTheme -TargetName Test -StyleSheet \@{Path="customstyle.css"} 107 | 108 | Set-AdfsWebConfig -ActiveThemeName Test 109 | 110 | ``` 111 | 112 | ### Customonload.js 113 | 114 | This file contains the javascript file that will perform the display of the images at runtime. It is based on the default ADFS onload.js (or a specific javascript file that already contains modifications) and adds only a small portion of code: 115 | 116 | ``` 117 | 118 | // Added by the ADFS web theme generator (custom idp images). 119 | 120 | var language = document.documentElement.lang; 121 | var languageKey = 'en'; 122 | 123 | if (language.lastIndexOf('nl', 0) === 0) 124 | { 125 | languageKey = 'nl'; 126 | } 127 | else if (language.lastIndexOf('fr', 0) === 0) 128 | { 129 | languageKey = 'fr'; 130 | } 131 | 132 | function renameLabels(oldDisplayName, newDisplayNameFR, newDisplayNameNL) 133 | { 134 | var listAllSpanForIdp = document.getElementsByClassName("idpDescription float"); 135 | var inc; 136 | 137 | for (inc = 0; inc \< listAllSpanForIdp.length; inc++) 138 | { 139 | if (listAllSpanForIdp[inc].innerHTML.indexOf(oldDisplayName) !== -1) 140 | { 141 | switch (languageKey) 142 | { 143 | case 'fr': 144 | if (newDisplayNameFR !== '') 145 | { 146 | listAllSpanForIdp[inc].innerHTML = listAllSpanForIdp[inc].innerHTML.replace(oldDisplayName, newDisplayNameFR); 147 | } 148 | break; 149 | case 'nl': 150 | if (newDisplayNameNL !== '') 151 | { 152 | listAllSpanForIdp[inc].innerHTML = listAllSpanForIdp[inc].innerHTML.replace(oldDisplayName, newDisplayNameNL); 153 | } 154 | break; 155 | default: 156 | break; 157 | } 158 | } 159 | } 160 | } 161 | 162 | function mapIdpImages() 163 | { 164 | var listAllIdpImg = document.getElementsByTagName('img'); 165 | var listAllIdpImg = document.getElementsByTagName('img'); 166 | var inc; 167 | 168 | for (inc = 0; inc \< listAllIdpImg.length; inc++) 169 | { 170 | switch ( listAllIdpImg[inc].getAttribute('alt') ) 171 | { 172 | case 'IDP1': 173 | listAllIdpImg[inc].src = '/adfs/portal/images/idp/IDP1.png'; 174 | break 175 | case 'IDP2': 176 | listAllIdpImg[inc].src = '/adfs/portal/images/idp/IDP2.png'; 177 | break 178 | case 'IDP3': 179 | listAllIdpImg[inc].src = '/adfs/portal/images/idp/IDP3.png'; 180 | break 181 | case 'Active Directory': 182 | listAllIdpImg[inc].src = '/adfs/portal/images/idp/IDP4.png'; 183 | break 184 | } 185 | } 186 | } 187 | 188 | if (typeof HRD != 'undefined') 189 | { 190 | mapIdpImages(); 191 | renameLabels('IDP3', 'IDP French translation', 'IDP Dutch translation'); 192 | } 193 | 194 | ``` 195 | 196 | ### Customstyle.css 197 | 198 | This file holds the CSS styles used by the webtheme, the only change applied is that the pointer is changed from an arrow to a hand. 199 | 200 | ### Image files (\*.png with transparency) 201 | 202 | The archive file contains all the images that will be added to the webtheme. 203 | 204 | How to apply the web theme 205 | -------------------------- 206 | 207 | The zip archive contains all the required artifacts. 208 | 209 | 1. Copy the zip archive to an ADFS server. 210 | 211 | 2. Extract the archive to a folder. 212 | 213 | 3. Launch a PowerShell console with elevated privileges 214 | 215 | 4. Navigate to the folder with the artifacts 216 | 217 | 5. Invoke the script ‘**CreateOrUpdateAdfsWebTheme.ps1**’ 218 | 219 | The script will create a new ADFS web theme with the configured name and 220 | activate it. 221 | 222 | Remark: In case issues arise or you are not satisfied with the quality of the image(s) (wrong logo, problem with transparency, …) you can activate the default ADFS web theme again as follows: 223 | 224 | **Set-AdfsWebConfig -ActiveThemeName default** 225 | 226 | Taking the sample data used in this document result would something like this (Browser language is Dutch) : 227 | 228 | ![](images/b7f27c9fe4d4a175d9b243f6bb694a85.png) 229 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/images/3bbc0a40203d4ce7ba0c4d97b8b7dd93.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/CustomImagesThemeGenerator/images/3bbc0a40203d4ce7ba0c4d97b8b7dd93.png -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/images/9544fd2a4ad07f3956b38493ab2d3453.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/CustomImagesThemeGenerator/images/9544fd2a4ad07f3956b38493ab2d3453.png -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/images/b7f27c9fe4d4a175d9b243f6bb694a85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/CustomImagesThemeGenerator/images/b7f27c9fe4d4a175d9b243f6bb694a85.png -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/images/c587fa46b0cf0b5afbac869eb426db97.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/CustomImagesThemeGenerator/images/c587fa46b0cf0b5afbac869eb426db97.png -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Digitude.Adfs.CustomImagesThemeGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F645A08A-3C1F-4260-BD12-823681D07E3E} 8 | WinExe 9 | Digitude.Adfs.CustomImagesThemeGenerator 10 | CustomImagesThemeGenerator 11 | v4.6 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Form 56 | 57 | 58 | Main.cs 59 | 60 | 61 | 62 | 63 | WebThemeInfo.xsd 64 | 65 | 66 | True 67 | True 68 | WebThemeInfo.xsd 69 | 70 | 71 | Main.cs 72 | 73 | 74 | ResXFileCodeGenerator 75 | Resources.Designer.cs 76 | Designer 77 | 78 | 79 | True 80 | Resources.resx 81 | True 82 | 83 | 84 | SettingsSingleFileGenerator 85 | Settings.Designer.cs 86 | 87 | 88 | True 89 | Settings.settings 90 | True 91 | 92 | 93 | WebThemeInfo.xsd 94 | 95 | 96 | Designer 97 | MSDataSetGenerator 98 | WebThemeInfo.Designer.cs 99 | 100 | 101 | WebThemeInfo.xsd 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Main.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Digitude.Adfs.CustomImagesThemeGenerator 2 | { 3 | partial class Main 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); 33 | this.dataGridView = new System.Windows.Forms.DataGridView(); 34 | this.displayNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); 35 | this.DisplayNameFR = new System.Windows.Forms.DataGridViewTextBoxColumn(); 36 | this.DisplayNameNL = new System.Windows.Forms.DataGridViewTextBoxColumn(); 37 | this.imageLocationDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); 38 | this.identityProviderInfoBindingSource = new System.Windows.Forms.BindingSource(this.components); 39 | this.webThemeInfoBindingSource = new System.Windows.Forms.BindingSource(this.components); 40 | this.webThemeInfo = new Digitude.Adfs.CustomImagesThemeGenerator.WebThemeInfo(); 41 | this.textBoxWebThemeName = new System.Windows.Forms.TextBox(); 42 | this.label1 = new System.Windows.Forms.Label(); 43 | this.label2 = new System.Windows.Forms.Label(); 44 | this.buttonGenerate = new System.Windows.Forms.Button(); 45 | this.toolStrip1 = new System.Windows.Forms.ToolStrip(); 46 | this.newToolStripButton = new System.Windows.Forms.ToolStripButton(); 47 | this.openToolStripButton = new System.Windows.Forms.ToolStripButton(); 48 | this.saveToolStripButton = new System.Windows.Forms.ToolStripButton(); 49 | this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); 50 | this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); 51 | this.openFileDialog = new System.Windows.Forms.OpenFileDialog(); 52 | this.saveFileDialog = new System.Windows.Forms.SaveFileDialog(); 53 | this.browseImageDialog = new System.Windows.Forms.OpenFileDialog(); 54 | this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components); 55 | this.saveZipDialog = new System.Windows.Forms.SaveFileDialog(); 56 | this.label3 = new System.Windows.Forms.Label(); 57 | this.textBoxWebThemeScriptFile = new System.Windows.Forms.TextBox(); 58 | this.buttonBrowseJavascript = new System.Windows.Forms.Button(); 59 | this.buttonBrowsStyleFile = new System.Windows.Forms.Button(); 60 | this.label4 = new System.Windows.Forms.Label(); 61 | this.textBoxWebThemeStyleFile = new System.Windows.Forms.TextBox(); 62 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit(); 63 | ((System.ComponentModel.ISupportInitialize)(this.identityProviderInfoBindingSource)).BeginInit(); 64 | ((System.ComponentModel.ISupportInitialize)(this.webThemeInfoBindingSource)).BeginInit(); 65 | ((System.ComponentModel.ISupportInitialize)(this.webThemeInfo)).BeginInit(); 66 | this.toolStrip1.SuspendLayout(); 67 | ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).BeginInit(); 68 | this.SuspendLayout(); 69 | // 70 | // dataGridView 71 | // 72 | this.dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 73 | | System.Windows.Forms.AnchorStyles.Left) 74 | | System.Windows.Forms.AnchorStyles.Right))); 75 | this.dataGridView.AutoGenerateColumns = false; 76 | this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 77 | this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 78 | this.displayNameDataGridViewTextBoxColumn, 79 | this.DisplayNameFR, 80 | this.DisplayNameNL, 81 | this.imageLocationDataGridViewTextBoxColumn}); 82 | this.dataGridView.DataSource = this.identityProviderInfoBindingSource; 83 | this.dataGridView.Location = new System.Drawing.Point(11, 138); 84 | this.dataGridView.Name = "dataGridView"; 85 | this.dataGridView.Size = new System.Drawing.Size(1056, 253); 86 | this.dataGridView.TabIndex = 10; 87 | this.dataGridView.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView_CellContentClick); 88 | // 89 | // displayNameDataGridViewTextBoxColumn 90 | // 91 | this.displayNameDataGridViewTextBoxColumn.DataPropertyName = "DisplayName"; 92 | this.displayNameDataGridViewTextBoxColumn.HeaderText = "DisplayName"; 93 | this.displayNameDataGridViewTextBoxColumn.Name = "displayNameDataGridViewTextBoxColumn"; 94 | // 95 | // DisplayNameFR 96 | // 97 | this.DisplayNameFR.DataPropertyName = "DisplayNameFR"; 98 | this.DisplayNameFR.HeaderText = "DisplayNameFR"; 99 | this.DisplayNameFR.Name = "DisplayNameFR"; 100 | this.DisplayNameFR.Width = 200; 101 | // 102 | // DisplayNameNL 103 | // 104 | this.DisplayNameNL.DataPropertyName = "DisplayNameNL"; 105 | this.DisplayNameNL.HeaderText = "DisplayNameNL"; 106 | this.DisplayNameNL.Name = "DisplayNameNL"; 107 | this.DisplayNameNL.Width = 200; 108 | // 109 | // imageLocationDataGridViewTextBoxColumn 110 | // 111 | this.imageLocationDataGridViewTextBoxColumn.DataPropertyName = "ImageLocation"; 112 | this.imageLocationDataGridViewTextBoxColumn.HeaderText = "ImageLocation"; 113 | this.imageLocationDataGridViewTextBoxColumn.Name = "imageLocationDataGridViewTextBoxColumn"; 114 | this.imageLocationDataGridViewTextBoxColumn.Width = 200; 115 | // 116 | // identityProviderInfoBindingSource 117 | // 118 | this.identityProviderInfoBindingSource.DataMember = "IdentityProviderInfo"; 119 | this.identityProviderInfoBindingSource.DataSource = this.webThemeInfoBindingSource; 120 | // 121 | // webThemeInfoBindingSource 122 | // 123 | this.webThemeInfoBindingSource.DataSource = this.webThemeInfo; 124 | this.webThemeInfoBindingSource.Position = 0; 125 | // 126 | // webThemeInfo 127 | // 128 | this.webThemeInfo.DataSetName = "WebThemeInfo"; 129 | this.webThemeInfo.SchemaSerializationMode = System.Data.SchemaSerializationMode.IncludeSchema; 130 | // 131 | // textBoxWebThemeName 132 | // 133 | this.textBoxWebThemeName.Location = new System.Drawing.Point(151, 28); 134 | this.textBoxWebThemeName.Name = "textBoxWebThemeName"; 135 | this.textBoxWebThemeName.Size = new System.Drawing.Size(151, 20); 136 | this.textBoxWebThemeName.TabIndex = 2; 137 | this.textBoxWebThemeName.TextChanged += new System.EventHandler(this.textBoxWebThemeName_TextChanged); 138 | // 139 | // label1 140 | // 141 | this.label1.AutoSize = true; 142 | this.label1.Location = new System.Drawing.Point(8, 31); 143 | this.label1.Name = "label1"; 144 | this.label1.Size = new System.Drawing.Size(122, 13); 145 | this.label1.TabIndex = 1; 146 | this.label1.Text = "ADFS web theme name:"; 147 | // 148 | // label2 149 | // 150 | this.label2.AutoSize = true; 151 | this.label2.Location = new System.Drawing.Point(8, 112); 152 | this.label2.Name = "label2"; 153 | this.label2.Size = new System.Drawing.Size(169, 13); 154 | this.label2.TabIndex = 9; 155 | this.label2.Text = "ADFS Identity Provider Information"; 156 | // 157 | // buttonGenerate 158 | // 159 | this.buttonGenerate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 160 | this.buttonGenerate.Location = new System.Drawing.Point(992, 397); 161 | this.buttonGenerate.Name = "buttonGenerate"; 162 | this.buttonGenerate.Size = new System.Drawing.Size(75, 23); 163 | this.buttonGenerate.TabIndex = 11; 164 | this.buttonGenerate.Text = "Generate"; 165 | this.buttonGenerate.UseVisualStyleBackColor = true; 166 | this.buttonGenerate.Click += new System.EventHandler(this.buttonGenerate_Click); 167 | // 168 | // toolStrip1 169 | // 170 | this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 171 | this.newToolStripButton, 172 | this.openToolStripButton, 173 | this.saveToolStripButton, 174 | this.toolStripSeparator1, 175 | this.toolStripButton1}); 176 | this.toolStrip1.Location = new System.Drawing.Point(0, 0); 177 | this.toolStrip1.Name = "toolStrip1"; 178 | this.toolStrip1.Size = new System.Drawing.Size(1079, 25); 179 | this.toolStrip1.TabIndex = 0; 180 | this.toolStrip1.Text = "toolStrip1"; 181 | // 182 | // newToolStripButton 183 | // 184 | this.newToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 185 | this.newToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("newToolStripButton.Image"))); 186 | this.newToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 187 | this.newToolStripButton.Name = "newToolStripButton"; 188 | this.newToolStripButton.Size = new System.Drawing.Size(23, 22); 189 | this.newToolStripButton.Text = "&New"; 190 | this.newToolStripButton.Click += new System.EventHandler(this.newToolStripButton_Click); 191 | // 192 | // openToolStripButton 193 | // 194 | this.openToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 195 | this.openToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("openToolStripButton.Image"))); 196 | this.openToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 197 | this.openToolStripButton.Name = "openToolStripButton"; 198 | this.openToolStripButton.Size = new System.Drawing.Size(23, 22); 199 | this.openToolStripButton.Text = "&Open"; 200 | this.openToolStripButton.Click += new System.EventHandler(this.openToolStripButton_Click); 201 | // 202 | // saveToolStripButton 203 | // 204 | this.saveToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 205 | this.saveToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripButton.Image"))); 206 | this.saveToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 207 | this.saveToolStripButton.Name = "saveToolStripButton"; 208 | this.saveToolStripButton.Size = new System.Drawing.Size(23, 22); 209 | this.saveToolStripButton.Text = "&Save"; 210 | this.saveToolStripButton.Click += new System.EventHandler(this.saveToolStripButton_Click); 211 | // 212 | // toolStripSeparator1 213 | // 214 | this.toolStripSeparator1.Name = "toolStripSeparator1"; 215 | this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); 216 | // 217 | // toolStripButton1 218 | // 219 | this.toolStripButton1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 220 | this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image"))); 221 | this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta; 222 | this.toolStripButton1.Name = "toolStripButton1"; 223 | this.toolStripButton1.Size = new System.Drawing.Size(23, 22); 224 | this.toolStripButton1.Text = "&Import list of IDP names"; 225 | this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click); 226 | // 227 | // saveFileDialog 228 | // 229 | this.saveFileDialog.Filter = "ADFS WebTheme Info|*.awi"; 230 | // 231 | // browseImageDialog 232 | // 233 | this.browseImageDialog.Filter = "Png files|*.png|Jpg files|*.jpg"; 234 | // 235 | // errorProvider 236 | // 237 | this.errorProvider.ContainerControl = this; 238 | // 239 | // saveZipDialog 240 | // 241 | this.saveZipDialog.Filter = "Zip files|*.zip"; 242 | // 243 | // label3 244 | // 245 | this.label3.AutoSize = true; 246 | this.label3.Location = new System.Drawing.Point(8, 58); 247 | this.label3.Name = "label3"; 248 | this.label3.Size = new System.Drawing.Size(137, 13); 249 | this.label3.TabIndex = 3; 250 | this.label3.Text = "ADFS web theme script file:"; 251 | // 252 | // textBoxWebThemeScriptFile 253 | // 254 | this.textBoxWebThemeScriptFile.Location = new System.Drawing.Point(151, 55); 255 | this.textBoxWebThemeScriptFile.Name = "textBoxWebThemeScriptFile"; 256 | this.textBoxWebThemeScriptFile.Size = new System.Drawing.Size(607, 20); 257 | this.textBoxWebThemeScriptFile.TabIndex = 4; 258 | // 259 | // buttonBrowseJavascript 260 | // 261 | this.buttonBrowseJavascript.Location = new System.Drawing.Point(760, 53); 262 | this.buttonBrowseJavascript.Name = "buttonBrowseJavascript"; 263 | this.buttonBrowseJavascript.Size = new System.Drawing.Size(28, 22); 264 | this.buttonBrowseJavascript.TabIndex = 5; 265 | this.buttonBrowseJavascript.Text = "..."; 266 | this.buttonBrowseJavascript.UseVisualStyleBackColor = true; 267 | this.buttonBrowseJavascript.Click += new System.EventHandler(this.buttonBrowseJavascript_Click); 268 | // 269 | // buttonBrowsStyleFile 270 | // 271 | this.buttonBrowsStyleFile.Location = new System.Drawing.Point(760, 81); 272 | this.buttonBrowsStyleFile.Name = "buttonBrowsStyleFile"; 273 | this.buttonBrowsStyleFile.Size = new System.Drawing.Size(28, 22); 274 | this.buttonBrowsStyleFile.TabIndex = 8; 275 | this.buttonBrowsStyleFile.Text = "..."; 276 | this.buttonBrowsStyleFile.UseVisualStyleBackColor = true; 277 | this.buttonBrowsStyleFile.Click += new System.EventHandler(this.buttonBrowsStyleFile_Click); 278 | // 279 | // label4 280 | // 281 | this.label4.AutoSize = true; 282 | this.label4.Location = new System.Drawing.Point(8, 85); 283 | this.label4.Name = "label4"; 284 | this.label4.Size = new System.Drawing.Size(133, 13); 285 | this.label4.TabIndex = 6; 286 | this.label4.Text = "ADFS web theme style file:"; 287 | // 288 | // textBoxWebThemeStyleFile 289 | // 290 | this.textBoxWebThemeStyleFile.Location = new System.Drawing.Point(151, 82); 291 | this.textBoxWebThemeStyleFile.Name = "textBoxWebThemeStyleFile"; 292 | this.textBoxWebThemeStyleFile.Size = new System.Drawing.Size(607, 20); 293 | this.textBoxWebThemeStyleFile.TabIndex = 7; 294 | // 295 | // Main 296 | // 297 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 298 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 299 | this.ClientSize = new System.Drawing.Size(1079, 432); 300 | this.Controls.Add(this.buttonBrowsStyleFile); 301 | this.Controls.Add(this.label4); 302 | this.Controls.Add(this.textBoxWebThemeStyleFile); 303 | this.Controls.Add(this.buttonBrowseJavascript); 304 | this.Controls.Add(this.label3); 305 | this.Controls.Add(this.textBoxWebThemeScriptFile); 306 | this.Controls.Add(this.toolStrip1); 307 | this.Controls.Add(this.buttonGenerate); 308 | this.Controls.Add(this.label2); 309 | this.Controls.Add(this.label1); 310 | this.Controls.Add(this.textBoxWebThemeName); 311 | this.Controls.Add(this.dataGridView); 312 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 313 | this.Name = "Main"; 314 | this.Text = "ADFS Generate theme script (IDP\'s with different images)"; 315 | this.Load += new System.EventHandler(this.Form1_Load); 316 | ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit(); 317 | ((System.ComponentModel.ISupportInitialize)(this.identityProviderInfoBindingSource)).EndInit(); 318 | ((System.ComponentModel.ISupportInitialize)(this.webThemeInfoBindingSource)).EndInit(); 319 | ((System.ComponentModel.ISupportInitialize)(this.webThemeInfo)).EndInit(); 320 | this.toolStrip1.ResumeLayout(false); 321 | this.toolStrip1.PerformLayout(); 322 | ((System.ComponentModel.ISupportInitialize)(this.errorProvider)).EndInit(); 323 | this.ResumeLayout(false); 324 | this.PerformLayout(); 325 | 326 | } 327 | 328 | #endregion 329 | 330 | private System.Windows.Forms.DataGridView dataGridView; 331 | private WebThemeInfo webThemeInfo; 332 | private System.Windows.Forms.BindingSource identityProviderInfoBindingSource; 333 | private System.Windows.Forms.BindingSource webThemeInfoBindingSource; 334 | private System.Windows.Forms.TextBox textBoxWebThemeName; 335 | private System.Windows.Forms.Label label1; 336 | private System.Windows.Forms.Label label2; 337 | private System.Windows.Forms.Button buttonGenerate; 338 | private System.Windows.Forms.ToolStrip toolStrip1; 339 | private System.Windows.Forms.ToolStripButton newToolStripButton; 340 | private System.Windows.Forms.ToolStripButton openToolStripButton; 341 | private System.Windows.Forms.ToolStripButton saveToolStripButton; 342 | private System.Windows.Forms.OpenFileDialog openFileDialog; 343 | private System.Windows.Forms.SaveFileDialog saveFileDialog; 344 | private System.Windows.Forms.OpenFileDialog browseImageDialog; 345 | private System.Windows.Forms.ErrorProvider errorProvider; 346 | private System.Windows.Forms.SaveFileDialog saveZipDialog; 347 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; 348 | private System.Windows.Forms.ToolStripButton toolStripButton1; 349 | private System.Windows.Forms.Button buttonBrowsStyleFile; 350 | private System.Windows.Forms.Label label4; 351 | private System.Windows.Forms.TextBox textBoxWebThemeStyleFile; 352 | private System.Windows.Forms.Button buttonBrowseJavascript; 353 | private System.Windows.Forms.Label label3; 354 | private System.Windows.Forms.TextBox textBoxWebThemeScriptFile; 355 | private System.Windows.Forms.DataGridViewTextBoxColumn displayNameDataGridViewTextBoxColumn; 356 | private System.Windows.Forms.DataGridViewTextBoxColumn DisplayNameFR; 357 | private System.Windows.Forms.DataGridViewTextBoxColumn DisplayNameNL; 358 | private System.Windows.Forms.DataGridViewTextBoxColumn imageLocationDataGridViewTextBoxColumn; 359 | } 360 | } 361 | 362 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.IO; 5 | using System.IO.Compression; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Windows.Forms; 10 | 11 | namespace Digitude.Adfs.CustomImagesThemeGenerator 12 | { 13 | public partial class Main : Form 14 | { 15 | public Main() 16 | { 17 | InitializeComponent(); 18 | } 19 | 20 | private void Form1_Load(object sender, EventArgs e) 21 | { 22 | this.Text += $" [v{Assembly.GetExecutingAssembly().GetName().Version}]"; 23 | dataGridView.Columns[0].Width = 100; 24 | dataGridView.Columns[1].Width = 200; 25 | dataGridView.Columns[2].Width = 200; 26 | dataGridView.Columns[3].Width = 450; 27 | dataGridView.Columns[3].ReadOnly = true; 28 | var browseImageButtonColumn = new DataGridViewButtonColumn 29 | { 30 | Name = "BrowseImage", 31 | HeaderText = "...", 32 | Width = 30, 33 | Text = "...", 34 | UseColumnTextForButtonValue = true 35 | }; 36 | browseImageButtonColumn.DefaultCellStyle.Padding = new Padding(2, 1, 2, 1); 37 | dataGridView.Columns.Add(browseImageButtonColumn); 38 | } 39 | 40 | private void saveToolStripButton_Click(object sender, EventArgs e) 41 | { 42 | try 43 | { 44 | if (string.IsNullOrEmpty(textBoxWebThemeName.Text)) 45 | { 46 | errorProvider.SetError(textBoxWebThemeName, "Please provide a name for the web theme."); 47 | return; 48 | } 49 | else 50 | { 51 | errorProvider.Clear(); 52 | } 53 | 54 | if (webThemeInfo.IdentityProviderInfo.Rows.Count == 0) 55 | { 56 | MessageBox.Show("Please add theme info."); 57 | return; 58 | } 59 | 60 | if (saveFileDialog.ShowDialog() == DialogResult.OK) 61 | { 62 | if (webThemeInfo.ThemeInfo.Rows.Count == 0) 63 | { 64 | var row = webThemeInfo.ThemeInfo.NewThemeInfoRow(); 65 | row.Name = textBoxWebThemeName.Text; 66 | webThemeInfo.ThemeInfo.Rows.Add(row); 67 | } 68 | else 69 | { 70 | WebThemeInfo.ThemeInfoRow row = webThemeInfo.ThemeInfo.Rows[0] as WebThemeInfo.ThemeInfoRow; 71 | row.Name = textBoxWebThemeName.Text; 72 | } 73 | 74 | webThemeInfo.WriteXml(saveFileDialog.FileName); 75 | } 76 | } 77 | catch (Exception exception) 78 | { 79 | MessageBox.Show(exception.Message); 80 | } 81 | } 82 | 83 | private void openToolStripButton_Click(object sender, EventArgs e) 84 | { 85 | try 86 | { 87 | openFileDialog.Filter = "ADFS WebTheme Info | *.awi"; 88 | 89 | if (openFileDialog.ShowDialog() == DialogResult.OK) 90 | { 91 | webThemeInfo.Clear(); 92 | webThemeInfo.ReadXml(openFileDialog.FileName); 93 | webThemeInfoBindingSource.ResetBindings(false); 94 | identityProviderInfoBindingSource.ResetBindings(false); 95 | 96 | if (webThemeInfo.ThemeInfo.Rows.Count > 0) 97 | { 98 | WebThemeInfo.ThemeInfoRow row = webThemeInfo.ThemeInfo.Rows[0] as WebThemeInfo.ThemeInfoRow; 99 | textBoxWebThemeName.Text = row.Name; 100 | } 101 | } 102 | } 103 | catch (Exception exception) 104 | { 105 | MessageBox.Show(exception.Message); 106 | } 107 | } 108 | 109 | private void dataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) 110 | { 111 | var senderGrid = (DataGridView)sender; 112 | 113 | if (senderGrid.Columns[e.ColumnIndex] is DataGridViewButtonColumn && e.ColumnIndex == 4 && e.RowIndex >= 0) 114 | { 115 | if (senderGrid.Rows[e.RowIndex].IsNewRow == false) 116 | { 117 | if (browseImageDialog.ShowDialog() == DialogResult.OK) 118 | { 119 | senderGrid.Rows[e.RowIndex].Cells[3].Value = browseImageDialog.FileName; 120 | } 121 | } 122 | } 123 | } 124 | 125 | private void buttonGenerate_Click(object sender, EventArgs e) 126 | { 127 | try 128 | { 129 | if (string.IsNullOrEmpty(textBoxWebThemeName.Text)) 130 | { 131 | errorProvider.SetError(textBoxWebThemeName, "Please provide a name for the web theme."); 132 | return; 133 | } 134 | else 135 | { 136 | errorProvider.Clear(); 137 | } 138 | 139 | var javascriptBuffer = new StringBuilder(); 140 | var powerShellScriptBuffer = new StringBuilder(); 141 | 142 | var customThemeFunctions = 143 | @" 144 | // Added by the ADFS web theme generator (custom idp images). 145 | 146 | var language = document.documentElement.lang; 147 | var languageKey = 'en'; 148 | 149 | if (language.lastIndexOf('nl', 0) === 0) 150 | { 151 | languageKey = 'nl'; 152 | } 153 | else if (language.lastIndexOf('fr', 0) === 0) 154 | { 155 | languageKey = 'fr'; 156 | } 157 | 158 | function renameLabels(oldDisplayName, newDisplayNameFR, newDisplayNameNL) { 159 | var listAllSpanForIdp = document.getElementsByClassName('idpDescription float'); 160 | var inc; 161 | for (inc = 0; inc < listAllSpanForIdp.length; inc++) 162 | { 163 | if (listAllSpanForIdp[inc].innerHTML.indexOf(oldDisplayName) !== -1) 164 | { 165 | switch (languageKey) 166 | { 167 | case 'fr': 168 | if (newDisplayNameFR !== '') 169 | { 170 | listAllSpanForIdp[inc].innerHTML = listAllSpanForIdp[inc].innerHTML.replace(oldDisplayName, newDisplayNameFR); 171 | } 172 | break; 173 | case 'nl': 174 | if (newDisplayNameNL !== '') 175 | { 176 | listAllSpanForIdp[inc].innerHTML = listAllSpanForIdp[inc].innerHTML.replace(oldDisplayName, newDisplayNameNL); 177 | } 178 | break; 179 | default: 180 | break; 181 | } 182 | } 183 | } 184 | } 185 | 186 | function mapIdpImages() 187 | { 188 | var listAllIdpImg = document.getElementsByTagName('img'); 189 | var listAllIdpImg = document.getElementsByTagName('img'); 190 | var inc; 191 | for (inc = 0; inc < listAllIdpImg.length; inc++) 192 | { 193 | switch ( listAllIdpImg[inc].getAttribute('alt') ) 194 | { 195 | [SwitchStatements] 196 | } 197 | } 198 | } 199 | 200 | if (typeof HRD != 'undefined') 201 | { 202 | mapIdpImages(); 203 | [RenameLabels] 204 | }"; 205 | 206 | powerShellScriptBuffer.AppendLine($"$webThemeName = '{textBoxWebThemeName.Text}'"); 207 | powerShellScriptBuffer.AppendLine("$webTheme = Get-AdfsWebTheme -Name $webThemeName"); 208 | powerShellScriptBuffer.AppendLine("if (!$webTheme)"); 209 | powerShellScriptBuffer.AppendLine("{"); 210 | powerShellScriptBuffer.AppendLine($"New-AdfsWebTheme -Name {textBoxWebThemeName.Text} -SourceName default"); 211 | powerShellScriptBuffer.AppendLine("}"); 212 | 213 | var switchStatements = new StringBuilder(); 214 | var renameLabelStatements = new StringBuilder(); 215 | 216 | foreach (WebThemeInfo.IdentityProviderInfoRow providerInfoRow in webThemeInfo.Tables["IdentityProviderInfo"].Rows) 217 | { 218 | var fileName = new FileInfo(providerInfoRow.ImageLocation).Name; 219 | 220 | switchStatements.AppendLine($"\tcase '{providerInfoRow.DisplayName}':"); 221 | switchStatements.AppendLine($"\t\tlistAllIdpImg[inc].src = '/adfs/portal/images/idp/{fileName}';"); 222 | switchStatements.AppendLine("\t\tbreak"); 223 | 224 | if(!string.IsNullOrEmpty(providerInfoRow.DisplayNameFR) && !string.IsNullOrEmpty(providerInfoRow.DisplayNameNL)) 225 | { 226 | renameLabelStatements.AppendLine($"\trenameLabels('{providerInfoRow.DisplayName}', '{providerInfoRow.DisplayNameFR}', '{providerInfoRow.DisplayNameNL}');"); 227 | } 228 | 229 | powerShellScriptBuffer.AppendLine($"Set-AdfsWebTheme -TargetName {textBoxWebThemeName.Text} -AdditionalFileResource @{{Uri =\"/adfs/portal/images/idp/{fileName}\";path=\"{fileName}\"}}"); 230 | } 231 | 232 | customThemeFunctions = customThemeFunctions.Replace("[SwitchStatements]", switchStatements.ToString()); 233 | customThemeFunctions = customThemeFunctions.Replace("[RenameLabels]", renameLabelStatements.ToString()); 234 | 235 | javascriptBuffer.Append(this.GetJavascriptFile()); 236 | javascriptBuffer.AppendLine(); 237 | javascriptBuffer.AppendLine(); 238 | javascriptBuffer.AppendLine(customThemeFunctions); 239 | 240 | powerShellScriptBuffer.AppendLine(); 241 | powerShellScriptBuffer.AppendLine($"Set-AdfsWebTheme -TargetName {textBoxWebThemeName.Text} -AdditionalFileResource @{{Uri =\"/adfs/portal/script/onload.js\";path=\"customOnload.js\"}}"); 242 | powerShellScriptBuffer.AppendLine(); 243 | powerShellScriptBuffer.AppendLine($"Set-AdfsWebTheme -TargetName {textBoxWebThemeName.Text} -StyleSheet @{{Path=\"customstyle.css\"}}"); 244 | powerShellScriptBuffer.AppendLine(); 245 | powerShellScriptBuffer.AppendLine($"Set-AdfsWebConfig -ActiveThemeName {textBoxWebThemeName.Text}"); 246 | 247 | using (var memoryStream = new MemoryStream()) 248 | { 249 | using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) 250 | { 251 | var powershellScriptFile = archive.CreateEntry("CreateOrUpdateAdfsWebTheme.ps1"); 252 | 253 | using (var entryStream = powershellScriptFile.Open()) 254 | using (var streamWriter = new StreamWriter(entryStream)) 255 | { 256 | streamWriter.Write(powerShellScriptBuffer.ToString()); 257 | } 258 | 259 | var javaScriptFile = archive.CreateEntry("customonload.js"); 260 | 261 | using (var entryStream = javaScriptFile.Open()) 262 | using (var streamWriter = new StreamWriter(entryStream)) 263 | { 264 | streamWriter.Write(javascriptBuffer.ToString()); 265 | } 266 | 267 | var styleFile = archive.CreateEntry("customstyle.css"); 268 | 269 | using (var entryStream = styleFile.Open()) 270 | using (var streamWriter = new StreamWriter(entryStream)) 271 | { 272 | streamWriter.Write(this.GetStyleFile()); 273 | } 274 | 275 | foreach (WebThemeInfo.IdentityProviderInfoRow providerInfoRow in webThemeInfo.Tables["IdentityProviderInfo"].Rows) 276 | { 277 | var fileName = new FileInfo(providerInfoRow.ImageLocation).Name; 278 | 279 | archive.CreateEntryFromFile(providerInfoRow.ImageLocation, fileName); 280 | } 281 | } 282 | 283 | if (saveZipDialog.ShowDialog() == DialogResult.OK) 284 | { 285 | using (var fileStream = new FileStream(saveZipDialog.FileName, FileMode.Create)) 286 | { 287 | memoryStream.Seek(0, SeekOrigin.Begin); 288 | memoryStream.CopyTo(fileStream); 289 | } 290 | 291 | MessageBox.Show("Zip archive has been created."); 292 | } 293 | } 294 | } 295 | catch (Exception exception) 296 | { 297 | MessageBox.Show(exception.Message); 298 | } 299 | } 300 | 301 | private void toolStripButton1_Click(object sender, EventArgs e) 302 | { 303 | try 304 | { 305 | const string headerName = "CPT Name"; 306 | 307 | Application.UseWaitCursor = true; 308 | 309 | openFileDialog.Filter = "Text files | *.txt"; 310 | 311 | if (openFileDialog.ShowDialog() == DialogResult.OK) 312 | { 313 | var fileContent = new List(File.ReadLines(openFileDialog.FileName)); 314 | 315 | IEnumerable claimProviderNames = fileContent.Select(s => s.Trim()); 316 | 317 | if (!claimProviderNames.Contains(headerName)) 318 | { 319 | MessageBox.Show("Please use only files generated by using the PowerShell command: \nGet-AdfsClaimsProviderTrust | select @{Name=\"CPT Name\";Expression={$_.Name}} > [outputFile].txt"); 320 | } 321 | else 322 | { 323 | webThemeInfo.IdentityProviderInfo.Clear(); 324 | 325 | foreach (var claimProviderName in claimProviderNames) 326 | { 327 | if (claimProviderName != headerName && !claimProviderName.Contains("------") && !string.IsNullOrEmpty(claimProviderName)) 328 | { 329 | WebThemeInfo.IdentityProviderInfoRow row = webThemeInfo.IdentityProviderInfo.NewIdentityProviderInfoRow(); 330 | row.DisplayName = claimProviderName; 331 | webThemeInfo.IdentityProviderInfo.AddIdentityProviderInfoRow(row); 332 | } 333 | } 334 | } 335 | } 336 | } 337 | catch (Exception exception) 338 | { 339 | MessageBox.Show(exception.Message); 340 | } 341 | finally 342 | { 343 | Application.UseWaitCursor = false; 344 | } 345 | } 346 | 347 | private void newToolStripButton_Click(object sender, EventArgs e) 348 | { 349 | webThemeInfo.ThemeInfo.Clear(); 350 | webThemeInfo.IdentityProviderInfo.Clear(); 351 | webThemeInfoBindingSource.ResetBindings(false); 352 | identityProviderInfoBindingSource.ResetBindings(false); 353 | 354 | textBoxWebThemeName.Text = string.Empty; 355 | } 356 | 357 | private void textBoxWebThemeName_TextChanged(object sender, EventArgs e) 358 | { 359 | if (string.IsNullOrEmpty(textBoxWebThemeName.Text)) 360 | { 361 | errorProvider.SetError(textBoxWebThemeName, "Please provide a name for the web theme."); 362 | } 363 | else 364 | { 365 | errorProvider.Clear(); 366 | } 367 | } 368 | 369 | private string GetJavascriptFile() 370 | { 371 | if(string.IsNullOrEmpty(textBoxWebThemeScriptFile.Text)) 372 | { 373 | return Properties.Resources.onload; 374 | } 375 | else 376 | { 377 | return File.ReadAllText(textBoxWebThemeScriptFile.Text); 378 | } 379 | } 380 | 381 | private string GetStyleFile() 382 | { 383 | if (string.IsNullOrEmpty(textBoxWebThemeStyleFile.Text)) 384 | { 385 | return Properties.Resources.customstyle; 386 | } 387 | else 388 | { 389 | return File.ReadAllText(textBoxWebThemeStyleFile.Text); 390 | } 391 | } 392 | 393 | private void buttonBrowseJavascript_Click(object sender, EventArgs e) 394 | { 395 | try 396 | { 397 | openFileDialog.Filter = "ADFS WebTheme onload| *.js"; 398 | 399 | if (openFileDialog.ShowDialog() == DialogResult.OK) 400 | { 401 | textBoxWebThemeScriptFile.Text = openFileDialog.FileName; 402 | } 403 | } 404 | catch (Exception exception) 405 | { 406 | MessageBox.Show(exception.Message); 407 | } 408 | } 409 | 410 | private void buttonBrowsStyleFile_Click(object sender, EventArgs e) 411 | { 412 | try 413 | { 414 | openFileDialog.Filter = "ADFS WebTheme style sheet| *.css"; 415 | 416 | if (openFileDialog.ShowDialog() == DialogResult.OK) 417 | { 418 | textBoxWebThemeStyleFile.Text = openFileDialog.FileName; 419 | } 420 | } 421 | catch (Exception exception) 422 | { 423 | MessageBox.Show(exception.Message); 424 | } 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace Digitude.Adfs.CustomImagesThemeGenerator 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new Main()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AdfsThemeGenerator")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AdfsThemeGenerator")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f645a08a-3c1f-4260-bd12-823681d07e3e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Digitude.Adfs.CustomImagesThemeGenerator.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Digitude.Adfs.CustomImagesThemeGenerator.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to * { 65 | /// margin:0px; 66 | /// padding:0px; 67 | ///} 68 | ///html, body 69 | ///{ 70 | /// height:100%; 71 | /// width:100%; 72 | /// background-color:#ffffff; 73 | /// color:#000000; 74 | /// font-weight:normal; 75 | /// font-family:"Segoe UI" , "Segoe" , "SegoeUI-Regular-final", Tahoma, Helvetica, Arial, sans-serif; 76 | /// min-width:500px; 77 | /// -ms-overflow-style:-ms-autohiding-scrollbar; 78 | ///} 79 | /// 80 | ///body 81 | ///{ 82 | /// font-size:0.9em; 83 | ///} 84 | /// 85 | ///#noScript { margin:16px; color:Black; } 86 | /// 87 | ///:lang(en-GB){quotes:'\2018' '\2019' '\201C' '\201D';} 88 | ///:lang(zh){font-family:微软雅黑;} 89 | /// 90 | ///@-m [rest of string was truncated]";. 91 | /// 92 | internal static string customstyle { 93 | get { 94 | return ResourceManager.GetString("customstyle", resourceCulture); 95 | } 96 | } 97 | 98 | /// 99 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 100 | /// 101 | internal static System.Drawing.Icon mockup_YCw_icon { 102 | get { 103 | object obj = ResourceManager.GetObject("mockup_YCw_icon", resourceCulture); 104 | return ((System.Drawing.Icon)(obj)); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to // Copyright (c) Microsoft Corporation. All rights reserved. 110 | /// 111 | ///// This file contains several workarounds on inconsistent browser behaviors that administrators may customize. 112 | ///"use strict"; 113 | /// 114 | ///var language = document.documentElement.lang; 115 | ///var languageKey = 'en'; 116 | /// 117 | ///if (language.lastIndexOf('nl', 0) === 0) { 118 | /// languageKey = 'nl'; 119 | ///} 120 | ///else if (language.lastIndexOf('fr', 0) === 0) { 121 | /// languageKey = 'fr'; 122 | ///} 123 | /// 124 | ///// iPhone email friendly keyboard does not include "\" key, use regular keyboard instead. 125 | ///// [rest of string was truncated]";. 126 | /// 127 | internal static string onload { 128 | get { 129 | return ResourceManager.GetString("onload", resourceCulture); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\style.css;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | 125 | ..\Resources\mockup_YCw_icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | ..\Resources\onload.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 129 | 130 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Digitude.Adfs.CustomImagesThemeGenerator.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Resources/mockup_YCw_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/CustomImagesThemeGenerator/src/Resources/mockup_YCw_icon.ico -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Resources/onload.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | 3 | // This file contains several workarounds on inconsistent browser behaviors that administrators may customize. 4 | "use strict"; 5 | 6 | // iPhone email friendly keyboard does not include "\" key, use regular keyboard instead. 7 | // Note change input type does not work on all versions of all browsers. 8 | if (navigator.userAgent.match(/iPhone/i) != null) { 9 | var emails = document.querySelectorAll("input[type='email']"); 10 | if (emails) { 11 | for (var i = 0; i < emails.length; i++) { 12 | emails[i].type = 'text'; 13 | } 14 | } 15 | } 16 | 17 | // In the CSS file we set the ms-viewport to be consistent with the device dimensions, 18 | // which is necessary for correct functionality of immersive IE. 19 | // However, for Windows 8 phone we need to reset the ms-viewport's dimension to its original 20 | // values (auto), otherwise the viewport dimensions will be wrong for Windows 8 phone. 21 | // Windows 8 phone has agent string 'IEMobile 10.0' 22 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 23 | var msViewportStyle = document.createElement("style"); 24 | msViewportStyle.appendChild( 25 | document.createTextNode( 26 | "@-ms-viewport{width:auto!important}" 27 | ) 28 | ); 29 | msViewportStyle.appendChild( 30 | document.createTextNode( 31 | "@-ms-viewport{height:auto!important}" 32 | ) 33 | ); 34 | document.getElementsByTagName("head")[0].appendChild(msViewportStyle); 35 | } 36 | 37 | // If the innerWidth is defined, use it as the viewport width. 38 | if (window.innerWidth && window.outerWidth && window.innerWidth !== window.outerWidth) { 39 | var viewport = document.querySelector("meta[name=viewport]"); 40 | viewport.setAttribute('content', 'width=' + window.innerWidth + ', initial-scale=1.0, user-scalable=1'); 41 | } 42 | 43 | // Gets the current style of a specific property for a specific element. 44 | function getStyle(element, styleProp) { 45 | var propStyle = null; 46 | 47 | if (element && element.currentStyle) { 48 | propStyle = element.currentStyle[styleProp]; 49 | } 50 | else if (element && window.getComputedStyle) { 51 | propStyle = document.defaultView.getComputedStyle(element, null).getPropertyValue(styleProp); 52 | } 53 | 54 | return propStyle; 55 | } 56 | 57 | // The script below is used for downloading the illustration image 58 | // only when the branding is displaying. This script work together 59 | // with the code in PageBase.cs that sets the html inline style 60 | // containing the class 'illustrationClass' with the background image. 61 | var computeLoadIllustration = function () { 62 | var branding = document.getElementById("branding"); 63 | var brandingDisplay = getStyle(branding, "display"); 64 | var brandingWrapperDisplay = getStyle(document.getElementById("brandingWrapper"), "display"); 65 | 66 | if (brandingDisplay && brandingDisplay !== "none" && 67 | brandingWrapperDisplay && brandingWrapperDisplay !== "none") { 68 | var newClass = "illustrationClass"; 69 | 70 | if (branding.classList && branding.classList.add) { 71 | branding.classList.add(newClass); 72 | } else if (branding.className !== undefined) { 73 | branding.className += " " + newClass; 74 | } 75 | if (window.removeEventListener) { 76 | window.removeEventListener('load', computeLoadIllustration, false); 77 | window.removeEventListener('resize', computeLoadIllustration, false); 78 | } 79 | else if (window.detachEvent) { 80 | window.detachEvent('onload', computeLoadIllustration); 81 | window.detachEvent('onresize', computeLoadIllustration); 82 | } 83 | } 84 | }; 85 | 86 | if (window.addEventListener) { 87 | window.addEventListener('resize', computeLoadIllustration, false); 88 | window.addEventListener('load', computeLoadIllustration, false); 89 | } 90 | else if (window.attachEvent) { 91 | window.attachEvent('onresize', computeLoadIllustration); 92 | window.attachEvent('onload', computeLoadIllustration); 93 | } 94 | 95 | // Function to change illustration image. Usage example below. 96 | function SetIllustrationImage(imageUri) { 97 | var illustrationImageClass = '.illustrationClass {background-image:url(' + imageUri + ');}'; 98 | 99 | var css = document.createElement('style'); 100 | css.type = 'text/css'; 101 | 102 | if (css.styleSheet) css.styleSheet.cssText = illustrationImageClass; 103 | else css.appendChild(document.createTextNode(illustrationImageClass)); 104 | 105 | document.getElementsByTagName("head")[0].appendChild(css); 106 | } 107 | 108 | // Example to change illustration image on HRD page after adding the image to active theme: 109 | // PSH> Set-AdfsWebTheme -TargetName -AdditionalFileResource @{uri='/adfs/portal/images/hrd.jpg';path='.\hrd.jpg'} 110 | // 111 | //if (typeof HRD != 'undefined') { 112 | // SetIllustrationImage('/adfs/portal/images/hrd.jpg'); 113 | //} -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/Resources/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin:0px; 3 | padding:0px; 4 | } 5 | html, body 6 | { 7 | height:100%; 8 | width:100%; 9 | background-color:#ffffff; 10 | color:#000000; 11 | font-weight:normal; 12 | font-family:"Segoe UI" , "Segoe" , "SegoeUI-Regular-final", Tahoma, Helvetica, Arial, sans-serif; 13 | min-width:500px; 14 | -ms-overflow-style:-ms-autohiding-scrollbar; 15 | } 16 | 17 | body 18 | { 19 | font-size:0.9em; 20 | } 21 | 22 | #noScript { margin:16px; color:Black; } 23 | 24 | :lang(en-GB){quotes:'\2018' '\2019' '\201C' '\201D';} 25 | :lang(zh){font-family:微软雅黑;} 26 | 27 | @-ms-viewport { width: device-width; } 28 | @-moz-viewport { width: device-width; } 29 | @-o-viewport { width: device-width; } 30 | @-webkit-viewport { width: device-width; } 31 | @viewport { width: device-width; } 32 | 33 | /* Theme layout styles */ 34 | 35 | #fullPage, #brandingWrapper 36 | { 37 | width:100%; 38 | height:100%; 39 | background-color:inherit; 40 | } 41 | #brandingWrapper 42 | { 43 | background-color:#4488dd; 44 | } 45 | #branding 46 | { 47 | /* A background image will be added to the #branding element at run-time once the illustration image is configured in the theme. 48 | Recommended image dimensions: 1420x1200 pixels, JPG or PNG, 200 kB average, 500 kB maximum. */ 49 | height:100%; 50 | margin-right:500px; margin-left:0px; 51 | background-color:inherit; 52 | background-repeat: no-repeat; 53 | background-size:cover; 54 | -webkit-background-size:cover; 55 | -moz-background-size:cover; 56 | -o-background-size:cover; 57 | } 58 | #contentWrapper 59 | { 60 | position:relative; 61 | width:500px; 62 | height:100%; 63 | overflow:auto; 64 | background-color:#ffffff; /* for IE7 */ 65 | margin-left:-500px; margin-right:0px; 66 | } 67 | #content 68 | { 69 | min-height:100%; 70 | height: auto !important; 71 | margin:0 auto -55px auto; 72 | padding:0px 150px 0px 50px; 73 | } 74 | #header 75 | { 76 | font-size:2em; 77 | font-weight:lighter; 78 | font-family:"Segoe UI Light" , "Segoe" , "SegoeUI-Light-final", Tahoma, Helvetica, Arial, sans-serif; 79 | padding-top: 90px; 80 | margin-bottom:60px; 81 | min-height:100px; 82 | overflow:hidden; 83 | } 84 | #header img 85 | { 86 | /* Logo image recommended dimension: 60x60 (square) or 350X35 (elongated), 4 kB average, 10 kB maximum. Transparent PNG strongly recommended. */ 87 | width:auto; 88 | height:auto; 89 | max-width:100%; 90 | } 91 | #workArea, #header 92 | { 93 | word-wrap:break-word; 94 | width:350px; 95 | } 96 | #workArea 97 | { 98 | margin-bottom:90px; 99 | } 100 | #footerPlaceholder 101 | { 102 | height:40px; 103 | } 104 | #footer 105 | { 106 | height:40px; 107 | padding:10px 50px 0px 50px; 108 | position:relative; 109 | color:#666666; 110 | font-size:0.78em; 111 | } 112 | #footerLinks 113 | { 114 | float:none; 115 | padding-top:10px; 116 | } 117 | #copyright {color:#696969;} 118 | .pageLink { color:#000000; padding-left:16px; } 119 | 120 | /* Common content styles */ 121 | 122 | .clear {clear:both;} 123 | .float { float:left; } 124 | .floatReverse { float:right; } 125 | .indent { margin-left:16px; } 126 | .indentNonCollapsible { padding-left:16px; } 127 | .hidden {display:none;} 128 | .notHidden {display:inherit;} 129 | .error { color:#c85305; } 130 | .actionLink { margin-bottom:8px; display:block; } 131 | a 132 | { 133 | color:#2672ec; 134 | text-decoration:none; 135 | background-color:transparent; 136 | } 137 | ul { list-style-type: disc; } 138 | ul,ol,dd { padding: 0 0 0 16px; } 139 | h1,h2,h3,h4,h5,label { margin-bottom: 8px; } 140 | .submitMargin { margin-top:38px; margin-bottom:30px; } 141 | .topFieldMargin { margin-top:8px; } 142 | .fieldMargin { margin-bottom:8px; } 143 | .groupMargin { margin-bottom:30px; } 144 | .sectionMargin { margin-bottom:64px; } 145 | .block { display: block; } 146 | .autoWidth { width:auto; } 147 | .fullWidth { width:342px; } 148 | .fullWidthIndent { width:326px; } 149 | .smallTopSpacing { margin-top:15px; } 150 | .mediumTopSpacing { margin-top:25px; } 151 | .largeTopSpacing { margin-top:35px; } 152 | .smallBottomSpacing { margin-bottom:5px; } 153 | .mediumBottomSpacing { margin-bottom:15px; } 154 | .largeBottomSpacing { margin-bottom:25px; } 155 | input 156 | { 157 | max-width:100%; 158 | font-family:inherit; 159 | margin-bottom:8px; 160 | } 161 | input[type="radio"], input[type="checkbox"] { 162 | vertical-align:middle; 163 | margin-bottom: 0px; 164 | } 165 | span.submit, input[type="submit"] 166 | { 167 | border:none; 168 | background-color:rgb(38, 114, 236); 169 | min-width:80px; 170 | width:auto; 171 | height:30px; 172 | padding:4px 20px 6px 20px; 173 | border-style:solid; 174 | border-width:1px; 175 | transition:background 0s; 176 | color:rgb(255, 255, 255); 177 | cursor:pointer; 178 | margin-bottom:8px; 179 | 180 | -ms-user-select:none; 181 | -moz-transition:background 0s; 182 | -webkit-transition:background 0s; 183 | -o-transition:background 0s; 184 | -webkit-touch-callout:none; 185 | -webkit-user-select:none; 186 | -khtml-user-select:none; 187 | -moz-user-select: none; 188 | -o-user-select: none; 189 | user-select:none; 190 | } 191 | input[type="submit"]:hover,span.submit:hover 192 | { 193 | background: rgb(212, 227, 251); 194 | } 195 | input.text{ 196 | height:28px; 197 | padding:0px 3px 0px 3px ; 198 | border:solid 1px #BABABA; 199 | font-size:1.0em; 200 | } 201 | input.text:focus 202 | { 203 | border: 1px solid #6B6B6B; 204 | } 205 | select 206 | { 207 | height:28px; 208 | min-width:60px; 209 | max-width:100%; 210 | margin-bottom:8px; 211 | 212 | white-space:nowrap; 213 | overflow:hidden; 214 | box-shadow:none; 215 | padding:2px; 216 | font-family:inherit; 217 | } 218 | h1, .giantText 219 | { 220 | font-size:2.0em; 221 | font-weight:lighter; 222 | } 223 | h2, .bigText 224 | { 225 | font-size:1.33em; 226 | font-weight:lighter; 227 | } 228 | h3, .normalText 229 | { 230 | font-size:1.0em; 231 | font-weight:normal; 232 | } 233 | h4, .smallText 234 | { 235 | font-size:0.9em; 236 | font-weight:normal; 237 | } 238 | h5, .tinyText 239 | { 240 | font-size:0.8em; 241 | font-weight:normal; 242 | } 243 | .hint 244 | { 245 | color:#999999; 246 | } 247 | .emphasis 248 | { 249 | font-weight:700; 250 | color:#2F2F2F; 251 | } 252 | .smallIcon 253 | { 254 | height:20px; 255 | padding-right:12px; 256 | vertical-align:middle; 257 | } 258 | .largeIcon 259 | { 260 | height:48px; 261 | /* width:48px; */ 262 | vertical-align:middle; 263 | } 264 | .largeTextNoWrap 265 | { 266 | height:48px; 267 | display:table-cell; /* needed when in float*/ 268 | vertical-align:middle; 269 | white-space:nowrap; 270 | font-size:1.2em; 271 | } 272 | .idp 273 | { 274 | height:48px; 275 | clear:both; 276 | padding:8px; 277 | overflow:hidden; 278 | cursor:pointer; 279 | } 280 | .idp:hover 281 | { 282 | background-color:#cccccc; 283 | } 284 | .idpDescription 285 | { 286 | width:80%; 287 | } 288 | 289 | /* Form factor: intermediate height layout. Reduce space of the header. */ 290 | @media only screen and (max-height: 820px) { 291 | #header { 292 | padding-top: 40px; 293 | min-height:0px; 294 | overflow: hidden; 295 | } 296 | 297 | #workArea 298 | { 299 | margin-bottom:60px; 300 | } 301 | } 302 | 303 | /* Form factor: intermediate height layout. Reduce space of the header. */ 304 | @media only screen and (max-height: 500px) { 305 | #header { 306 | padding-top: 30px; 307 | margin-bottom: 30px; 308 | } 309 | #workArea{ 310 | margin-bottom:40px; 311 | } 312 | } 313 | 314 | /* Form factor: intermediate layout (WAB in non-snapped view falls in here) */ 315 | @media only screen and (max-width: 600px) { 316 | html, body { 317 | min-width: 260px; 318 | } 319 | 320 | #brandingWrapper { 321 | display: none; 322 | } 323 | 324 | #contentWrapper { 325 | float: none; 326 | width: 100%; 327 | margin: 0px auto; 328 | } 329 | 330 | #content, #footer, #header { 331 | width: 400px; 332 | padding-left: 0px; 333 | padding-right: 0px; 334 | margin-left: auto; 335 | margin-right: auto; 336 | } 337 | 338 | #workArea { 339 | width: 100%; 340 | } 341 | 342 | .fullWidth { 343 | width: 392px; 344 | } 345 | 346 | .fullWidthIndent { 347 | width: 376px; 348 | } 349 | } 350 | 351 | @media only screen and (max-width: 450px) { 352 | body { 353 | font-size: 0.8em; 354 | } 355 | 356 | #content, #footer { 357 | width:auto; 358 | margin-right:33px; 359 | margin-left:25px; 360 | } 361 | 362 | #header { 363 | width: auto; 364 | } 365 | 366 | span.submit, input[type="submit"] { 367 | font-size: 0.9em; 368 | } 369 | 370 | .fullWidth 371 | { 372 | width:100%; 373 | margin-left:auto; 374 | margin-right:auto; 375 | } 376 | 377 | .fullWidthIndent { 378 | width: 85%; 379 | } 380 | 381 | .idpDescription 382 | { 383 | width:70%; 384 | } 385 | } 386 | 387 | /* Form factor: snapped WAB (for WAB to work in snapped view, the content wrapper width has to be set to 260px) */ 388 | @media only screen and (max-width:280px) 389 | { 390 | #contentWrapper 391 | { 392 | width:260px; 393 | } 394 | .idpDescription 395 | { 396 | max-width:160px; 397 | min-width:100px; 398 | } 399 | } -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/WebThemeInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Digitude.Adfs.CustomImagesThemeGenerator 2 | { 3 | } 4 | 5 | namespace Digitude.Adfs.CustomImagesThemeGenerator 6 | { 7 | 8 | 9 | public partial class WebThemeInfo 10 | { 11 | } 12 | } 13 | namespace Digitude.Adfs.CustomImagesThemeGenerator { 14 | 15 | 16 | public partial class WebThemeInfo { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/WebThemeInfo.xsc: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/WebThemeInfo.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /communityCustomizations/CustomImagesThemeGenerator/src/WebThemeInfo.xss: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /communityCustomizations/README.md: -------------------------------------------------------------------------------- 1 | # Community Customizations 2 | 3 | ## Overview 4 | 5 | This project contains a number of customizations provided by community members. 6 | 7 | ## Adding Your Customization 8 | 9 | To add your customizations, please do the following: 10 | 11 | 1. Create a subfolder in this directory with a descriptive name of your customization (ex. "showPasswordButton") 12 | 13 | 2. Update the list in this file with your change 14 | 15 | 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. 16 | 17 | 4. In the subfolder, include a `README.md` file, which explains your customization, and shows the screenshots 18 | 19 | 20 | ## Customizations 21 | 22 | 1. __[ShowPasswordButton](ShowPasswordButton)__ - A customization to allow users to click "show password" when entering their password on an AD FS page 23 | 2. __[CustomImagesThemeGenerator](CustomImagesThemeGenerator)__ - A GUI to build a list of custom images for the claim provider trusts on the AD FS home realm selection page. 24 | 25 | ## Contributing (Special Note) 26 | 27 | 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). 28 | 29 | For the full Contributing details, please see __[the root README](../README.md)__. 30 | -------------------------------------------------------------------------------- /communityCustomizations/RenameAndReorderADCPTrust/ONLOAD.JS: -------------------------------------------------------------------------------- 1 | // Adjust The Display Name Of The Active Directory CP Trust And Reorder To Put Active Directory CP Trust Back At The Top (ADFS 2016) 2 | // Works with IE, Edge, Chrome, Firefox, Safari 3 | if (document.getElementById("hrdArea")) { 4 | if (document.getElementById("hrd")) { 5 | // THE NEXT LINE SHOULD BE UPDATED TO SPECIFY THE NAME DISPLAYED ON THE ADFS HED PAGE 6 | var strCPTrustADDisplayName = ""; 7 | // THE LINE ABOVE SHOULD BE UPDATED TO SPECIFY THE NAME DISPLAYED ON THE ADFS HED PAGE 8 | var idp = document.getElementsByClassName("idp"); 9 | var totalIdPElements = idp.length; 10 | var listAllSpanForIdpIcon = document.getElementsByClassName("largeIcon float"); 11 | var listAllSpanForIdpDescription = document.getElementsByClassName("idpDescription float"); 12 | var adAuthorityElementIsPresent = false; 13 | 14 | var i; 15 | for (i = 0; i < listAllSpanForIdpDescription.length; i++) { 16 | // IN THE LINE BELOW, IF NEEDED, SPECIFY THE LOCALIZED NAME OF AD. TO BE CERTAIN CHECK THE NAME OF THE AD CP TRUST 17 | var languageBasedADname = "Active Directory"; 18 | // IN THE LINE ABOVE, IF NEEDED, SPECIFY THE LOCALIZED NAME OF AD. TO BE CERTAIN CHECK THE NAME OF THE AD CP TRUST 19 | if (listAllSpanForIdpDescription[i].innerText == languageBasedADname) { 20 | listAllSpanForIdpIcon[i].alt = strCPTrustADDisplayName; 21 | listAllSpanForIdpDescription[i].innerHTML = "" + strCPTrustADDisplayName + ""; 22 | adAuthorityElementIsPresent = true; 23 | var adAuthorityElementIDnr = i; 24 | } 25 | } 26 | 27 | if ((totalIdPElements > 1) && (adAuthorityElementIsPresent)) { 28 | idp[adAuthorityElementIDnr].parentNode.insertBefore(idp[adAuthorityElementIDnr], idp[0]); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /communityCustomizations/RenameAndReorderADCPTrust/README.md: -------------------------------------------------------------------------------- 1 | # Rename & Reorder AD CP Trust In ADFS2016 Farm Level & Up 2 | 3 | ## Overview 4 | 5 | This project contains an `ONLOAD.JS` script for renaming and reordering the Active Directory (AD) CP Trust. After raising the farm level to at least ADFS 2016, the order of the CP Trust list is updated whereas the AD CP trust is moved to the bottom and it does not inherit the display name of the federation service. It just shows "Active Directory". This piece of code, fixes the order of the CP trust list and it allows to to specify a custom display name for the AD CP trust. 6 | 7 | ## Applying The Customization 8 | 9 | To change the name of the AD CP trust on the HRD page and put it back at the top again when the farm level is at least ADFS 2016, do the following: 10 | 11 | 1. Add the code from the `ONLOAD.JS` to the `ONLOAD.JS` of your own ADFS farm 12 | 13 | 2. For more information and examples, please see: https://jorgequestforknowledge.wordpress.com/2018/10/09/changing-ad-cp-trust-display-name-and-order-in-adfs-2016/ 14 | -------------------------------------------------------------------------------- /communityCustomizations/ShowPasswordButton/OnLoad.js: -------------------------------------------------------------------------------- 1 | var inputArea = document.getElementById("inputArea"); 2 | var showButton = document.getElementById("showButton"); 3 | 4 | if ( inputArea && !showButton ) 5 | { 6 | var showButton = document.createElement("div"); 7 | showButton.id = "showButton"; 8 | showButton.innerHTML = "Show password"; 9 | inputArea.appendChild(showButton); 10 | 11 | var appendedButton = document.getElementById("showButton"); 12 | appendedButton.onmousedown = function(){ document.getElementById("password").type = "text"; }; 13 | appendedButton.onmouseup = function(){ document.getElementById("password").type = "password";}; 14 | } -------------------------------------------------------------------------------- /communityCustomizations/ShowPasswordButton/README.md: -------------------------------------------------------------------------------- 1 | # ShowPasswordButton 2 | 3 | ## Overview 4 | 5 | This project contains an `onload.js` script for adding a "Show Password" button in the password field on AD FS logon pages. 6 | Works with password fields on Form based auth and with a custom provider using domain password as secondary auth. 7 | 8 | ## Applying the customization 9 | 10 | To add "Show password" button do the following: 11 | 12 | 1. Add the code from the `onload.js` to your webtheme `onload.js` 13 | 14 | 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 15 | 16 | ## Examples 17 | 18 | ![Button added](/communityCustomizations/ShowPasswordButton/images/Customization1.png) 19 | ![Password shown on mouse click](/communityCustomizations/ShowPasswordButton/images/Customization2.png) -------------------------------------------------------------------------------- /communityCustomizations/ShowPasswordButton/images/Customization1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/ShowPasswordButton/images/Customization1.png -------------------------------------------------------------------------------- /communityCustomizations/ShowPasswordButton/images/Customization2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/communityCustomizations/ShowPasswordButton/images/Customization2.png -------------------------------------------------------------------------------- /mfaLoadingWheel/README.md: -------------------------------------------------------------------------------- 1 | # ADFS MFA Loading Wheel 2 | 3 | ## Overview 4 | 5 | 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. 6 | 7 | ## Getting Started - JavaScript Deployment 8 | 9 | 1. Download the ```loadWheel.js``` file to your ADFS server, wherever you host your JavaScript. 10 | 11 | Note: It is *__highly__* recommended that you minify your ```loadWheel.js``` before including it in a production environment. There are many popular tools online 12 | for minifying JavaScript code. Two popular choices are [minifier.org](http://www.minifier.org/) and [JSCompress](https://jscompress.com/). 13 | 14 | 2. Create a custom web theme using the following command in PowerShell: 15 | 16 | ```New-AdfsWebTheme –Name custom -SourceName default -AdditionalFileResource @{Uri=’/adfs/portal/script/onload.js’; path="c:\loadWheel.js"}``` 17 | 18 | 3. Apply the new custom web theme using the following command in PowerShell: 19 | 20 | ```Set-AdfsWebConfig -ActiveThemeName custom``` 21 | 22 | 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) 23 | 24 | ## Example 25 | 26 | ![Screenshot](./images/screenshot_wheel.png) 27 | 28 | ## Contributing (Special Note) 29 | 30 | If you find any problems with the CSS, JavaScript, or docs, please fork and send us your fix. If you don't 31 | have a fix, please open an issue, and describe what you are seeing (feel free to include screenshots). 32 | 33 | For the full Contributing details, please see __[the root README](../README.md)__. -------------------------------------------------------------------------------- /mfaLoadingWheel/images/screenshot_wheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/adfsWebCustomization/cbf1c942ad622998bfa87f182b02c0d5dbe6b75a/mfaLoadingWheel/images/screenshot_wheel.png -------------------------------------------------------------------------------- /mfaLoadingWheel/loadWheel.js: -------------------------------------------------------------------------------- 1 | function AuthSelectionPageSubmitCallback() 2 | { 3 | 4 | if (!document.getElementById("loadWheel")) 5 | { 6 | var divToAppendTo = document.getElementById("authArea"); 7 | 8 | // Create the feedback loader 9 | var loader = document.createElement("loader"); 10 | loader.innerHTML = "
" 11 | loader.id = "loadWheel"; 12 | 13 | // Add the loader to the HTML page 14 | if ( divToAppendTo && loader ) 15 | { 16 | divToAppendTo.appendChild(loader); 17 | } 18 | } 19 | } 20 | 21 | var authOptions = document.getElementById('authOptions') 22 | var progress = document.getElementById('Progress') 23 | if (authOptions && !progress) { 24 | 25 | var azureOption = document.getElementById('WindowsAzureMultiFactorAuthentication'); 26 | // var azureOption = document.getElementById('submitButton'); // use this option for MFA adapter, instead of AzureMFA 27 | 28 | if (azureOption) { 29 | azureOption.addEventListener("click", function() { AuthSelectionPageSubmitCallback(); }, false); 30 | } 31 | } 32 | 33 | // NOTE: If you wish to support the ADFS illustration (background image), you must use the following: 34 | /* 35 | function getStyle(element, styleProp) { 36 | var propStyle = null; 37 | 38 | if (element && element.currentStyle) { 39 | propStyle = element.currentStyle[styleProp]; 40 | } 41 | else if (element && window.getComputedStyle) { 42 | propStyle = document.defaultView.getComputedStyle(element, null).getPropertyValue(styleProp); 43 | } 44 | 45 | return propStyle; 46 | } 47 | 48 | var computeLoadIllustration = function () { 49 | var branding = document.getElementById("branding"); 50 | var brandingDisplay = getStyle(branding, "display"); 51 | var brandingWrapperDisplay = getStyle(document.getElementById("brandingWrapper"), "display"); 52 | 53 | if (brandingDisplay && brandingDisplay !== "none" && 54 | brandingWrapperDisplay && brandingWrapperDisplay !== "none") { 55 | var newClass = "illustrationClass"; 56 | 57 | if (branding.classList && branding.classList.add) { 58 | branding.classList.add(newClass); 59 | } else if (branding.className !== undefined) { 60 | branding.className += " " + newClass; 61 | } 62 | if (window.removeEventListener) { 63 | window.removeEventListener('load', computeLoadIllustration, false); 64 | window.removeEventListener('resize', computeLoadIllustration, false); 65 | } 66 | else if (window.detachEvent) { 67 | window.detachEvent('onload', computeLoadIllustration); 68 | window.detachEvent('onresize', computeLoadIllustration); 69 | } 70 | } 71 | }; 72 | 73 | if (window.addEventListener) { 74 | window.addEventListener('resize', computeLoadIllustration, false); 75 | window.addEventListener('load', computeLoadIllustration, false); 76 | } 77 | else if (window.attachEvent) { 78 | window.attachEvent('onresize', computeLoadIllustration); 79 | window.attachEvent('onload', computeLoadIllustration); 80 | } 81 | 82 | function SetIllustrationImage(imageUri) { 83 | var illustrationImageClass = '.illustrationClass {background-image:url(' + imageUri + ');}'; 84 | 85 | var css = document.createElement('style'); 86 | css.type = 'text/css'; 87 | 88 | if (css.styleSheet) css.styleSheet.cssText = illustrationImageClass; 89 | else css.appendChild(document.createTextNode(illustrationImageClass)); 90 | 91 | document.getElementsByTagName("head")[0].appendChild(css); 92 | } 93 | */ 94 | // NOTE: If you wish to support the ADFS illustration (background image), you must use the following: 95 | // PSH> Set-AdfsWebTheme -TargetName -AdditionalFileResource @{uri='/adfs/portal/images/illustration_mine.png';path='.\illustration_mine.jpg'} 96 | // SetIllustrationImage('/adfs/portal/images/illustration_mine.png'); -------------------------------------------------------------------------------- /pageDetectionTelemetry/InteractiveCompletionByPlatformQuery.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. 3 | 4 | 5 | Client Interactive Completion Percentage (By Platform for 1 week): 6 | 7 | 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)) 8 | 9 | Important filters: 10 | 11 | • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 12 | • We remove all traffic for which there is no correlation ID set 13 | • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 14 | 15 | Important Caveats: 16 | 17 | • Because we cannot guarantee what the end state of the request is, the numbers here are based on the likely outcome of the request 18 | 19 | customEvents 20 | | where timestamp > ago(8d) and timestamp < ago(1d) 21 | | where isempty(operation_SyntheticSource) 22 | | where tostring(customDimensions.CorrelationID) != "NOTSET" 23 | | extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS)) 24 | | summarize 25 | EventCount = count(), 26 | DistinctEventCount = dcount(name), 27 | EventNames = makelist(name) 28 | by CorrelationID = tostring(customDimensions.CorrelationID), CleanOS 29 | | where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35 30 | | extend LikelyFormsEnded = iff(EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart"), 1, 0) 31 | | extend LikelyAuthSelectEnded = iff((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked") and (EventNames !has "PhoneFactorWaitingStart"), 1, 0) 32 | | extend LikelyPFAEnded = iff(EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd", 1, 0) 33 | | extend LikelyErrorEnded = iff((EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart") and 34 | (EventNames !has "FormsPageStart" and 35 | EventNames !has "FormsPageEnd" and 36 | EventNames !has "AuthSelectionPageStart" and 37 | EventNames !has "AuthSelectionPageEnd" and 38 | EventNames !has "PhoneFactorWaitingStart" and 39 | EventNames !has "PhoneFactorWaitingEnd"), 1, 0) 40 | | extend LikelyEnded = iff(LikelyFormsEnded > 0 or LikelyAuthSelectEnded > 0 or LikelyPFAEnded > 0 or LikelyErrorEnded > 0, 1, 0) 41 | | project CorrelationID, LikelyEnded, CleanOS 42 | | summarize 43 | TotalRequests = count(), 44 | EndedRequests = countif(LikelyEnded > 0) 45 | by CleanOS 46 | | extend InteractiveCompletion = (1.0 * EndedRequests) / (1.0 * TotalRequests) * 100.0 47 | | sort by TotalRequests desc 48 | -------------------------------------------------------------------------------- /pageDetectionTelemetry/InteractiveCompletionQuery.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. 3 | 4 | 5 | 6 | Client Interactive Completion Percentage (Total for 1 week): 7 | 8 | For all requests that come to ADFS, what percentage of those requests end up returning or erroring out? (i.e. (#succeeded + #failed) / (#started)) 9 | 10 | Important filters: 11 | 12 | • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 13 | • We remove all traffic for which there is no correlation ID set 14 | • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 15 | 16 | Important Caveats: 17 | 18 | • Because we cannot guarantee what the end state of the request is, the numbers here are based on the likely outcome of the request 19 | 20 | customEvents 21 | | where timestamp > ago(8d) and timestamp < ago(1d) 22 | | where isempty(operation_SyntheticSource) 23 | | where tostring(customDimensions.CorrelationID) != "NOTSET" 24 | | summarize 25 | EventCount = count(), 26 | DistinctEventCount = dcount(name), 27 | EventNames = makelist(name) 28 | by CorrelationID = tostring(customDimensions.CorrelationID) 29 | | where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35 30 | | extend LikelyFormsEnded = iff(EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart"), 1, 0) 31 | | extend LikelyAuthSelectEnded = iff((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked") and (EventNames !has "PhoneFactorWaitingStart"), 1, 0) 32 | | extend LikelyPFAEnded = iff(EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd", 1, 0) 33 | | extend LikelyErrorEnded = iff((EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart") and 34 | (EventNames !has "FormsPageStart" and 35 | EventNames !has "FormsPageEnd" and 36 | EventNames !has "AuthSelectionPageStart" and 37 | EventNames !has "AuthSelectionPageEnd" and 38 | EventNames !has "PhoneFactorWaitingStart" and 39 | EventNames !has "PhoneFactorWaitingEnd"), 1, 0) 40 | | extend LikelyEnded = iff(LikelyFormsEnded > 0 or LikelyAuthSelectEnded > 0 or LikelyPFAEnded > 0 or LikelyErrorEnded > 0, 1, 0) 41 | | project CorrelationID, LikelyEnded 42 | | summarize 43 | TotalRequests = count(), 44 | EndedRequests = countif(LikelyEnded > 0) 45 | | extend InteractiveCompletion = (1.0 * EndedRequests) / (1.0 * TotalRequests) * 100.0 46 | -------------------------------------------------------------------------------- /pageDetectionTelemetry/LoginReliabilityByPlatformQuery.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. 3 | 4 | 5 | 6 | Login Reliability Percentage - By Platform for 1 week: 7 | 8 | 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)) 9 | 10 | 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. 11 | 12 | Important filters: 13 | 14 | • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 15 | • We remove all traffic for which there is no correlation ID set 16 | • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 17 | 18 | Important Caveats: 19 | 20 | • 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 21 | • 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 22 | 23 | customEvents 24 | | where timestamp > ago(8d) and timestamp < ago(1d) 25 | | where isempty(operation_SyntheticSource) 26 | | where tostring(customDimensions.CorrelationID) != "NOTSET" 27 | | extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS)) 28 | | summarize 29 | EventCount = count(), 30 | DistinctEventCount = dcount(name), 31 | EventNames = makelist(name) 32 | by CorrelationID = tostring(customDimensions.CorrelationID), CleanOS 33 | | where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35 34 | | extend LikelyFormsNotAbandoned = 35 | iff((EventNames has "FormsPageEnd" and (EventNames !has "AuthSelectionPageStart" and EventNames !has "PhoneFactorWaitingStart")) 36 | or (EventNames has "FormsPageStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0) 37 | | extend LikelyAuthSelectNotAbandoned = iff( 38 | ((EventNames has "AuthSelectionPageEnd" or EventNames has "AuthSelectionPicked" or EventNames has "AuthSelectionLatency") and (EventNames !has "PhoneFactorWaitingStart")) 39 | or (EventNames has "AuthSelectionPageStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0) 40 | | extend LikelyPFANotAbandoned = iff( 41 | (EventNames has "PhoneFactorLatency" or EventNames has "PhoneFactorWaitingEnd") or 42 | (EventNames has "PhoneFactorWaitingStart" and (EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart")), 1, 0) 43 | | extend LikelyNotAbandoned = iff(LikelyFormsNotAbandoned > 0 or LikelyAuthSelectNotAbandoned > 0 or LikelyPFANotAbandoned > 0, 1, 0) 44 | | extend WasErrorRequest = iff(EventNames has "ErrorPageStart" or EventNames has "ErrorDetailedPageStart", 1, 0) 45 | | extend WasEndErrorState = iff(EventNames[ toint(arraylength(EventNames) - 1) ] has "ErrorPageStart" or EventNames[ toint(arraylength(EventNames) - 1) ] has "ErrorDetailedPageStart", 1, 0) 46 | | project CorrelationID, EventNames, LikelyNotAbandoned, WasErrorRequest, CleanOS, WasEndErrorState 47 | | summarize 48 | TotalRequests = count(), 49 | TotalErroredRequests = sum(WasEndErrorState), 50 | ReturnedRequests = countif(LikelyNotAbandoned > 0) 51 | by CleanOS 52 | | extend LoginReliabilityInteractive = (1.0 * ReturnedRequests) / (1.0 * ReturnedRequests + TotalErroredRequests) * 100.0 53 | | sort by TotalRequests desc 54 | 55 | -------------------------------------------------------------------------------- /pageDetectionTelemetry/README.md: -------------------------------------------------------------------------------- 1 | # Page Detection with App Insights 2 | 3 | ## Overview 4 | 5 | This project performs page detection of common, uncustomized ADFS web pages, and then uploads 6 | telemetry about those pages to your [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) datastore. 7 | 8 | Note that this customization DOES NOT send any telemetry to the Microsoft ADFS team. All telemetry is sent to your datastore only. 9 | 10 | This project also includes some useful analysis scripts you can run against your Application Insights datastore. 11 | 12 | ## Requirements 13 | 14 | This tool requires that you have an [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) subscription. 15 | 16 | Additionally, it is recommended that you have minimal web customization of the standard ADFS pages, as customization could throw off the page 17 | detection. Please note that customization referres to onload.js changes, not logo changes, illustration changes, etc. 18 | 19 | Lastly, there is some page detection logic that relies on English strings in the pages. If you are presenting pages in languages other than 20 | English, you might need to make modifications to the JavaScript. 21 | 22 | ## Getting Started 23 | 24 | 1. Register for an [Azure Application Insights](https://azure.microsoft.com/en-us/services/application-insights/) subscription 25 | 26 | 2. Download the ```onload.js``` in this repo locally, and update the ```instrumentationKey``` under ```GenerateAppInsightsObject``` to be your Application Insights API key 27 | 28 | (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)) 29 | 30 | 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 31 | should append our content to yours. 32 | 33 | Note: It is *__highly__* recommended that you minify your ```onload.js``` before including it in a production environment. There are many popular tools online 34 | for minifying JavaScript code. Two popular choices are [minifier.org](http://www.minifier.org/) and [JSCompress](https://jscompress.com/). 35 | 36 | (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)) 37 | 38 | ## What Gets Tracked 39 | 40 | The following pages are detected and tracked: 41 | 42 | * Forms Page - the username and password collection page 43 | * Auth Selection Page - the page served when MFA is required. This page lists MFA provider options 44 | * 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) 45 | * Error Page - the ADFS or PFA error page 46 | 47 | 48 | ## Analyzing the Data 49 | 50 | 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. 51 | For more details, see [Analytics](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-analytics). 52 | 53 | Included in this project are a number of useful queries for tracking: 54 | 55 | * User prompting rate served by ADFS server 56 | * Login reliability of your ADFS server 57 | * Interactive completion rate of your ADFS server 58 | 59 | ## Further Reading 60 | 61 | The following documentation is useful for making changes to the ```onload.js``` code and the included queries. 62 | 63 | * [Application Insights Overview](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview) 64 | * [Data Analysis Overivew](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-analytics-tour) 65 | * [Custom App Insights Collection](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-api-custom-events-metrics#_flushing-data) 66 | * [App Insights in JavaScript](https://docs.microsoft.com/en-us/azure/application-insights/app-insights-javascript) 67 | * [App Insights JavaScript GitHub](https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md) 68 | 69 | ## Contributing (Special Note) 70 | 71 | If you are contributing code, please be sure that you __remove your instrumentation key__ from any code you 72 | put in a pull request. This project is public, and anyone on the Internet can see it. 73 | 74 | For the full Contributing details, please see __[the root README](../README.md)__. -------------------------------------------------------------------------------- /pageDetectionTelemetry/UserPromptRateByPlatformQuery.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. 3 | 4 | 5 | 6 | 7 | Browser Sessions with Exactly 1 Unforced Prompt Per Week (By Platform): 8 | 9 | What percentage of browser sessions that are making requests to ADFS are receiving exactly 1 unforced prompt per week, broken down by platform? 10 | 11 | Important filters: 12 | 13 | • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 14 | • We remove all traffic for which there is no correlation ID set 15 | • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 16 | • We remove traffic for users who only saw an error page 17 | • We remove all traffic that has a query parameter that would force a prompt ("wfresh = 0") 18 | 19 | Important Caveats: 20 | 21 | • 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) 22 | • 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 23 | 24 | customEvents 25 | | where timestamp > ago(8d) and timestamp < ago(1d) 26 | | where isempty(operation_SyntheticSource) 27 | | where tostring(customDimensions.CorrelationID) != "NOTSET" 28 | | where tostring(customDimensions.wfresh) != "0" 29 | | extend CleanBrowser = replace('Internet Explorer', 'IE', replace(@'\s', '', replace('UI/WKWebView', '', replace('Mobile', '', replace('[0-9.]+', '', client_Browser))))) 30 | | extend CleanOS = replace(@'\s', '', replace('[0-9.]+', '', client_OS)) 31 | | summarize 32 | EventCount = count(), 33 | DistinctEventCount = dcount(name), 34 | FormsPromptCount = countif(name startswith "Forms"), 35 | AuthPromptCount = countif(name startswith "AuthSelection" and customDimensions.SelectionMethod !has "auto"), 36 | PFAPromptCount = countif(name startswith "Phone"), 37 | EventNames = makelist(name) 38 | by CorrelationID = tostring(customDimensions.CorrelationID), session_Id, CleanOS 39 | | where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35 40 | | extend WasFormsPrompted = iff(FormsPromptCount > 0, 1, 0) 41 | | extend WasAuthPrompted = iff(AuthPromptCount > 0, 1, 0) 42 | | extend WasPFAPrompted = iff(PFAPromptCount > 0, 1, 0) 43 | | extend PromptsPerRequest = WasAuthPrompted + WasFormsPrompted + WasPFAPrompted 44 | | project CorrelationID, session_Id, PromptsPerRequest, CleanOS 45 | | where PromptsPerRequest > 0 // Some users are only getting an Error Page 46 | | summarize 47 | PromptsPerSession = sum(PromptsPerRequest) 48 | by session_Id, CleanOS 49 | | summarize 50 | TotalSessions = count(), 51 | SessionsWith1Prompt = countif(PromptsPerSession <= 3) 52 | by CleanOS 53 | | extend PercentWith1 = (1.0 * SessionsWith1Prompt) / (1.0 * TotalSessions) * 100.0 54 | | sort by TotalSessions desc 55 | -------------------------------------------------------------------------------- /pageDetectionTelemetry/UserPromptRateQuery.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. 3 | 4 | 5 | 6 | Browser Sessions with Exactly 1 Unforced Prompt Per Week: 7 | 8 | What percentage of browser sessions that are making requests to ADFS are receiving exactly 1 unforced prompt per week? 9 | 10 | Important filters: 11 | 12 | • We remove all traffic from known web crawlers, as marked by the App Insights library (using Synthetic Source) 13 | • We remove all traffic for which there is no correlation ID set 14 | • We remove all traffic that appears abnormal, meaning traffic for which there is a high page refresh or page navigation rate 15 | • We remove traffic for users who only saw an error page 16 | • We remove all traffic that has a query parameter that would force a prompt ("wfresh = 0") 17 | 18 | Important Caveats: 19 | 20 | • 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) 21 | • 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 22 | 23 | 24 | customEvents 25 | | where timestamp > ago(8d) and timestamp < ago(1d) 26 | | where isempty(operation_SyntheticSource) 27 | | where tostring(customDimensions.CorrelationID) != "NOTSET" 28 | | where tostring(customDimensions.wfresh) != "0" 29 | | summarize 30 | EventCount = count(), 31 | DistinctEventCount = dcount(name), 32 | FormsPromptCount = countif(name startswith "Forms"), 33 | AuthPromptCount = countif(name startswith "AuthSelection" and customDimensions.SelectionMethod !has "auto"), 34 | PFAPromptCount = countif(name startswith "Phone"), 35 | EventNames = makelist(name) 36 | by CorrelationID = tostring(customDimensions.CorrelationID), session_Id 37 | | where (1.0 * DistinctEventCount) / (1.0 * EventCount) >= 0.35 38 | | extend WasFormsPrompted = iff(FormsPromptCount > 0, 1, 0) 39 | | extend WasAuthPrompted = iff(AuthPromptCount > 0, 1, 0) 40 | | extend WasPFAPrompted = iff(PFAPromptCount > 0, 1, 0) 41 | | extend PromptsPerRequest = WasAuthPrompted + WasFormsPrompted + WasPFAPrompted 42 | | project CorrelationID, session_Id, PromptsPerRequest 43 | | where PromptsPerRequest > 0 // Some users are only getting an Error Page 44 | | summarize 45 | PromptsPerSession = sum(PromptsPerRequest) 46 | by session_Id 47 | | summarize 48 | TotalSessions = count(), 49 | SessionsWith1Prompt = countif(PromptsPerSession <= 2) // We allow 2 here because MFA and Auth Select together count as 1 prompt 50 | | extend PercentWith1 = (1.0 * SessionsWith1Prompt) / (1.0 * TotalSessions) * 100.0 51 | -------------------------------------------------------------------------------- /pageDetectionTelemetry/onload.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | // Telemetry Manager is the App Insights telemetry management object 6 | // Callers MUST call 'Initialize' before using the 'ProducePageDetectionTelemetry' method 7 | // 8 | var TelemetryManager = { 9 | 10 | /* 11 | * Initialize the app state for the current page 12 | */ 13 | Initialize: function () { 14 | var _self = this; 15 | 16 | // Collect some page details for later 17 | var NOT_SET_CONST = 'NOTSET'; 18 | _self.currentUri = window.location.href.split('?')[0]; 19 | _self.mswtrealm = _self.getQueryString("wtrealm") || NOT_SET_CONST; 20 | _self.decodedwtrealm = decodeURIComponent(_self.mswtrealm) || NOT_SET_CONST; 21 | _self.requestID = _self.getQueryString("client-request-id") || NOT_SET_CONST; 22 | _self.wfresh = _self.getQueryString("wfresh") || NOT_SET_CONST; 23 | _self.wauth = _self.getQueryString("wauth") || NOT_SET_CONST; 24 | _self.debugging = _self.getQueryString("debug") || NOT_SET_CONST; 25 | _self.wauth = decodeURIComponent(_self.wauth); 26 | _self.Username = NOT_SET_CONST; 27 | 28 | _self.startedPfaWaiting = false; 29 | _self.pfaTimestamp = null; 30 | _self.pfaTimestampOldBrowser = null; 31 | _self.startedAuthSelectionWaiting = false; 32 | _self.authSelectionTimestamp = null; 33 | _self.authSelectionTimestampOldBrowser = null; 34 | _self.authSelectionLinkClicked = null; 35 | _self.authSelectionMethod = null; 36 | _self.startedFormsPage = false; 37 | 38 | // Create App Insights object with settings 39 | if (!window.appInsights) { 40 | if(console && _self.debugging) console.log("TelemetryManager: Generating a new App Insights object"); 41 | var appInsights = _self.GenerateAppInsightsObject.call(); 42 | 43 | // Set App Insights object against the current window 44 | window.appInsights = appInsights; 45 | if(console && _self.debugging) console.log("TelemetryManager: Set new App Insights object against the current window"); 46 | } 47 | 48 | // 49 | // Add unload callback to window, so we can capture telemetry 50 | // 51 | if (window.addEventListener) { 52 | window.addEventListener("unload", function () { _self.LeavingCurrentPageCallback(_self); }, false); // Modern browsers 53 | } else if (window.attachEvent) { 54 | window.attachEvent('onunload', function () { _self.LeavingCurrentPageCallback(_self); }); // Old IE 55 | } 56 | 57 | if(console && _self.debugging) console.log("Exit: TelemetryManager.Initialize"); 58 | }, 59 | 60 | /* 61 | * Generate an App Insights object to use when 62 | * sending telemetry. 63 | */ 64 | GenerateAppInsightsObject: function() { 65 | return function (config) { 66 | 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 67 | }({ 68 | samplingPercentage: 100, 69 | instrumentationKey: "YOUR-KEY-HERE", 70 | endpointUrl: "https://dc.services.visualstudio.com/v2/track" // modify endpoint for Azure Government or Azure China as per https://docs.microsoft.com/en-us/azure/azure-monitor/app/custom-endpoints 71 | }); 72 | }, 73 | 74 | /* 75 | * Helper function to get a querystring parameter 76 | */ 77 | getQueryString: function(qsName) { 78 | qsName = qsName.replace(/[\[\]]/g, "\\$&"); 79 | var regex = new RegExp("[?&]" + qsName + "(=([^&#]*)|&|#|$)"), 80 | results = regex.exec(location.href); 81 | if (!results) return ""; 82 | if (!results[2]) return ""; 83 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 84 | }, 85 | 86 | /* 87 | * Produces all telemetry for the following pages: 88 | * Forms Page 89 | * AuthSelection Page 90 | * Home Realm Discovery Page 91 | * Phone Factor Authentication Page 92 | * Phone Factor Error Page 93 | * ADFS Error Page 94 | * Phone Factor Authentication Options Page 95 | */ 96 | ProducePageDetectionTelemetry: function () { 97 | 98 | var _self = this; 99 | 100 | if(console && _self.debugging) console.log("Enter: TelemetryManager.ProducePageDetectionTelemetry"); 101 | 102 | // 103 | // Generic Page view tracking 104 | // 105 | window.appInsights.trackPageView("Generic"); 106 | 107 | // 108 | // Home Realm Discovery Page 109 | // 110 | var hrd = document.getElementById('hrd'); 111 | if (hrd) { 112 | window.appInsights.trackPageView("HomeRealmDiscovery"); 113 | if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found HRD Page"); 114 | return; 115 | } 116 | 117 | // 118 | // Forms Page (before creds are entered) 119 | // NOTE: This only works for pages presented in English 120 | // 121 | var pageloginForm = document.getElementById('loginForm'); 122 | if (!hrd && pageloginForm && document.title == 'Sign In') { 123 | window.appInsights.trackPageView("FormsPage"); 124 | window.appInsights.trackEvent("FormsPageStart", 125 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm } 126 | ); 127 | _self.startedFormsPage = true; 128 | if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Forms Page"); 129 | return; 130 | } 131 | 132 | // 133 | // Error Page 134 | // 135 | var ierrorText = document.getElementById("errorText"); 136 | if (ierrorText) { 137 | var ierrorCurrent = ierrorText.innerHTML; 138 | if (ierrorCurrent.length > 0) { 139 | var pageTitle = document.title; 140 | 141 | // 142 | // Try to gather more error information from the page 143 | // 144 | var erruserAccount = _self.GetUserNameFromAuthArea(); 145 | var erractivityId = (document.getElementById('activityId') || {innerText:''}).innerText; 146 | var errcontextId = (document.getElementById('contextId')|| {innerText:''}).innerText; 147 | var errtimestamp = (document.getElementById('timestamp')|| {innerText:''}).innerText; 148 | 149 | if(erractivityId || errcontextId || errtimestamp){ 150 | window.appInsights.trackPageView("ErrorDetailedPage"); 151 | window.appInsights.trackEvent("ErrorDetailedPageStart", 152 | { 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 } 153 | ); 154 | if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Detailed Error Page"); 155 | } 156 | return; 157 | } 158 | } 159 | 160 | // 161 | // AuthSelection Page 162 | // 163 | var authOptions = document.getElementById('authOptions') 164 | var progress = document.getElementById('Progress') 165 | if (authOptions && !progress) { 166 | 167 | var foundUsername = _self.GetUserNameFromAuthArea(); 168 | _self.Username = foundUsername; 169 | 170 | window.appInsights.trackPageView("AuthSelectionPage"); 171 | window.appInsights.trackEvent("AuthSelectionPageStart", 172 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username } 173 | ); 174 | if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found Auth Selection Page"); 175 | 176 | // 177 | // Add click callbacks to the auth selection options 178 | // NOTE: If you have other options you wish to track, add them here 179 | // 180 | var certOption = document.getElementById('CertificateAuthentication'); 181 | if (certOption) { 182 | certOption.addEventListener("click", function () { _self.AuthSelectionPageSubmitCallback("cert", "manual", _self); }, false); 183 | } 184 | var azureOption = document.getElementById('WindowsAzureMultiFactorAuthentication'); 185 | if (azureOption) { 186 | azureOption.addEventListener("click", function () { _self.AuthSelectionPageSubmitCallback("phonefactor", "manual", _self); }, false); 187 | } 188 | 189 | return; 190 | } 191 | 192 | // 193 | // Phone Factor Waiting Page 194 | // 195 | var workArea = document.getElementById('workArea'); 196 | var authArea = document.getElementById('authArea'); 197 | var progressDiv = document.getElementById('Progress'); 198 | var authMethod = document.getElementById('authMethod'); 199 | var errorDiv = document.getElementById('errorDiv'); 200 | if (workArea && authArea && progressDiv && authMethod && !errorDiv) { 201 | 202 | var phonefactorUserID = _self.GetUserNameFromAuthArea(); 203 | var authchildren = authArea.childNodes; 204 | for (var i = 0; i < authchildren.length; i++) { 205 | if (authchildren[i].className === 'fieldMargin bigText') { 206 | window.appInsights.trackPageView("PhoneFactorWaitingPage"); 207 | window.appInsights.trackEvent("PhoneFactorWaitingStart", 208 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: phonefactorUserID } 209 | ); 210 | if(console && _self.debugging) console.log("ProducePageDetectionTelemetry: Found PFA Waiting Page"); 211 | 212 | // Once we detect the pfa page, add a timer to collect the PFA latency 213 | _self.startedPfaWaiting = true; 214 | _self.Username = phonefactorUserID; 215 | 216 | if (performance && performance.now()) { 217 | _self.pfaTimestamp = performance.now(); 218 | } 219 | 220 | if (Date && Date.now()) { 221 | _self.pfaTimestampOldBrowser = Date.now(); 222 | } 223 | 224 | return; 225 | } 226 | } 227 | } 228 | 229 | if(console && _self.debugging) console.log("Exit: TelemetryManager.ProducePageDetectionTelemetry"); 230 | }, 231 | 232 | /* 233 | * Collect the username from the auth area message 234 | * NOTE: This method only works for pages presented in English 235 | */ 236 | GetUserNameFromAuthArea: function () { 237 | var authchildren = document.getElementById('authArea').childNodes; 238 | for (var i = 0; i < authchildren.length; i++) { 239 | if (authchildren[i].className === 'fieldMargin bigText') { 240 | var tempuserAccount = authchildren[i].innerText; 241 | return tempuserAccount.replace("Welcome ", ""); 242 | } 243 | } 244 | }, 245 | 246 | /* 247 | * Callback function when the AuthSelection page is being submitted after an 248 | * auth option was chosen. 249 | */ 250 | AuthSelectionPageSubmitCallback: function (linkClicked, selectionMethod, _self) { 251 | if(console && _self.debugging) console.log("Enter: TelemetryManager.AuthSelectionPageSubmitCallback"); 252 | 253 | // Collect telemetry 254 | window.appInsights.trackEvent("AuthSelectionPicked", 255 | { 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 } 256 | ); 257 | 258 | if(console && _self.debugging) console.log("AuthSelectionPageSubmitCallback: Link Clicked: " + linkClicked); 259 | 260 | // Start the auth selection timer to time from page submit to page unload 261 | _self.startedAuthSelectionWaiting = true; 262 | _self.authSelectionLinkClicked = linkClicked; 263 | _self.authSelectionMethod = selectionMethod; 264 | 265 | if (performance && performance.now()) { 266 | _self.authSelectionTimestamp = performance.now(); 267 | } 268 | 269 | if (Date && Date.now()) { 270 | _self.authSelectionTimestampOldBrowser = Date.now(); 271 | } 272 | 273 | if(console && _self.debugging) console.log("Exit: TelemetryManager.AuthSelectionPageSubmitCallback"); 274 | }, 275 | 276 | /* 277 | * Callback function when any page is being left 278 | * NOTE: Due to browser unload calls, there is no guarantee that the 279 | * processing in this method will complete. Some of the XHR requests made for 280 | * trackEvent calls may not succeed. This telemetry is a best-effort collection 281 | */ 282 | LeavingCurrentPageCallback: function (_self) { 283 | 284 | // Grab the window appInsights object for local use 285 | var localAppInsights = window.appInsights; 286 | var flushMePlease = false; 287 | 288 | if (_self.startedFormsPage) { 289 | _self.Username = document.getElementById(Login.userNameInput).value; 290 | localAppInsights.trackEvent("FormsPageEnd", 291 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username } 292 | ); 293 | _self.startedFormsPage = false; 294 | flushMePlease = true; 295 | } 296 | 297 | var pfaTime = null; 298 | if (_self.pfaTimestamp) { 299 | pfaTime = (performance.now() - _self.pfaTimestamp) / 1000.0; 300 | } 301 | 302 | var pfaTimeOldBrowser = null; 303 | if (_self.pfaTimestampOldBrowser) { 304 | pfaTimeOldBrowser = (Date.now() - _self.pfaTimestampOldBrowser) / 1000.0; 305 | } 306 | 307 | if (pfaTime) { 308 | localAppInsights.trackEvent("PhoneFactorLatency", 309 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username }, 310 | { Latency: pfaTime } 311 | ); 312 | flushMePlease = true; 313 | } else if (pfaTimeOldBrowser) { 314 | localAppInsights.trackEvent("PhoneFactorLatency", 315 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username }, 316 | { OldBrowserLatency: pfaTimeOldBrowser } 317 | ); 318 | flushMePlease = true; 319 | } 320 | 321 | if (_self.startedPfaWaiting) { 322 | 323 | localAppInsights.trackEvent("PhoneFactorWaitingEnd", 324 | { CorrelationID: _self.requestID, CurrentUri: _self.currentUri, CurrentRealm: _self.mswtrealm, wtrealm: _self.decodedwtrealm, wfresh: _self.wfresh, wauth: _self.wauth, Username: _self.Username } 325 | ); 326 | _self.startedPfaWaiting = false; 327 | flushMePlease = true; 328 | } 329 | 330 | if (_self.startedAuthSelectionWaiting) { 331 | localAppInsights.trackEvent("AuthSelectionPageEnd", 332 | { 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 } 333 | ); 334 | _self.startedAuthSelectionWaiting = false; 335 | flushMePlease = true; 336 | } 337 | 338 | var authSelectionTime = null; 339 | if (_self.authSelectionTimestamp) { 340 | authSelectionTime = (performance.now() - _self.authSelectionTimestamp) / 1000.0; 341 | } 342 | 343 | var authSelectionTimeOldBrowser = null; 344 | if (_self.authSelectionTimestampOldBrowser) { 345 | authSelectionTimeOldBrowser = (Date.now() - _self.authSelectionTimestampOldBrowser) / 1000.0; 346 | } 347 | 348 | if (authSelectionTime ) { 349 | localAppInsights.trackEvent("AuthSelectionLatency", 350 | { CorrelationID: _self.requestID, Username: _self.Username, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm, Type: _self.authSelectionLinkClicked, SelectionMethod: _self.authSelectionMethod }, 351 | { Latency: authSelectionTime } 352 | ); 353 | flushMePlease = true; 354 | } else if (authSelectionTimeOldBrowser) { 355 | localAppInsights.trackEvent("AuthSelectionLatency", 356 | { CorrelationID: _self.requestID, Username: _self.Username, wauth: _self.wauth, wfresh: _self.wfresh, wtrealm: _self.decodedwtrealm, Type: _self.authSelectionLinkClicked, SelectionMethod: _self.authSelectionMethod }, 357 | { OldBrowserLatency: authSelectionTimeOldBrowser } 358 | ); 359 | flushMePlease = true; 360 | } 361 | 362 | if (flushMePlease) { 363 | if (localAppInsights) { 364 | if (localAppInsights.flush) { 365 | localAppInsights.flush(); 366 | } 367 | } 368 | } 369 | }, 370 | }; 371 | 372 | // Produce telemetry 373 | if(console) console.log("TelemetryManager: Start trying to produce telemetry"); 374 | var pageTelemetryManager = TelemetryManager; 375 | pageTelemetryManager.Initialize(); 376 | pageTelemetryManager.ProducePageDetectionTelemetry(); 377 | if(console) console.log("TelemetryManager: End trying to produce telemetry"); 378 | --------------------------------------------------------------------------------