├── .gitattributes ├── .github ├── FUNDING.yml └── stale.yml ├── .gitignore ├── LICENSE.txt ├── README.md └── src └── Lithnet.Laps.Web ├── Lithnet.Laps.Web.sln └── Lithnet.Laps.Web ├── App_Data └── Templates │ ├── EmailAuditFailure.html │ ├── EmailAuditSuccess.html │ ├── LogAuditFailure.txt │ └── LogAuditSuccess.txt ├── App_LocalResources ├── LogMessages.Designer.cs ├── LogMessages.resx ├── UIMessages.Designer.cs ├── UIMessages.it.Designer.cs ├── UIMessages.it.resx └── UIMessages.resx ├── App_Start ├── BundleConfig.cs ├── FilterConfig.cs └── RouteConfig.cs ├── ConfigSection ├── AuditElement.cs ├── LapsConfigSection.cs ├── RateLimitIPElement.cs ├── RateLimitUserElement.cs ├── ReaderCollection.cs ├── ReaderElement.cs ├── TargetCollection.cs ├── TargetElement.cs └── TargetType.cs ├── Content ├── Site.css ├── bootstrap-grid.css ├── bootstrap-grid.css.map ├── bootstrap-grid.min.css ├── bootstrap-grid.min.css.map ├── bootstrap-reboot.css ├── bootstrap-reboot.css.map ├── bootstrap-reboot.min.css ├── bootstrap-reboot.min.css.map ├── bootstrap.css ├── bootstrap.css.map ├── bootstrap.min.css ├── bootstrap.min.css.map ├── fontawesome │ ├── fa-brands.css │ ├── fa-brands.min.css │ ├── fa-regular.css │ ├── fa-regular.min.css │ ├── fa-solid.css │ ├── fa-solid.min.css │ ├── fontawesome-all.css │ ├── fontawesome-all.min.css │ ├── fontawesome.css │ └── fontawesome.min.css └── webfonts │ ├── Inconsolata-Regular.ttf │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── Controllers ├── HomeController.cs └── LapController.cs ├── Exceptions └── NoMatchingTargetException.cs ├── Global.asax ├── Global.asax.cs ├── Internal ├── Directory.cs ├── EventIDs.cs ├── Extensions.cs ├── RateLimiter.cs └── Reporting.cs ├── Lithnet.Laps.Web.csproj ├── Lithnet.Laps.Web.csproj.DotSettings ├── Models ├── LapEntryModel.cs └── LapRequestModel.cs ├── Properties ├── AssemblyInfo.cs └── PublishProfiles │ ├── FolderProfile.pubxml │ └── Publish to local IIS.pubxml ├── Scripts ├── README.md ├── bootstrap.bundle.js ├── bootstrap.bundle.js.map ├── bootstrap.bundle.min.js ├── bootstrap.bundle.min.js.map ├── bootstrap.js ├── bootstrap.js.map ├── bootstrap.min.js ├── bootstrap.min.js.map ├── clipboard.min.js ├── esm │ ├── popper-utils.js │ ├── popper-utils.js.map │ ├── popper-utils.min.js │ ├── popper-utils.min.js.map │ ├── popper.js │ ├── popper.js.map │ ├── popper.min.js │ └── popper.min.js.map ├── index.d.ts ├── init-clip.js ├── jquery-3.3.1.intellisense.js ├── jquery-3.3.1.js ├── jquery-3.3.1.min.js ├── jquery-3.3.1.min.map ├── jquery-3.3.1.slim.js ├── jquery-3.3.1.slim.min.js ├── jquery-3.3.1.slim.min.map ├── jquery.validate-vsdoc.js ├── jquery.validate.js ├── jquery.validate.min.js ├── jquery.validate.unobtrusive.js ├── jquery.validate.unobtrusive.min.js ├── popper-utils.js ├── popper-utils.js.map ├── popper-utils.min.js ├── popper-utils.min.js.map ├── popper.js ├── popper.js.map ├── popper.min.js ├── popper.min.js.map └── umd │ ├── popper-utils.js │ ├── popper-utils.js.map │ ├── popper-utils.min.js │ ├── popper-utils.min.js.map │ ├── popper.js │ ├── popper.js.map │ ├── popper.min.js │ └── popper.min.js.map ├── Startup.cs ├── Views ├── Home │ ├── AuthNError.cshtml │ ├── Index.cshtml │ └── LogOut.cshtml ├── Lap │ ├── Get.cshtml │ ├── RateLimitExceeded.cshtml │ └── Show.cshtml ├── Shared │ ├── Error.cshtml │ └── _Layout.cshtml ├── Web.config └── _ViewStart.cshtml ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── clipboard.min.js ├── favicon.ico ├── images └── logo.png └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: ['https://lithnet.io/donate'] 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed in 7 days if no further activity occurs. 15 | # Comment to post when closing a stale issue. Set to `false` to disable 16 | closeComment: false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | secrets.config 264 | *.dev.config -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lithnet 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 | ![](https://github.com/lithnet/laps-web/wiki/images/logo-ex-small.png) 2 | # Lithnet LAPS Web App is now Lithnet Access Manager! 3 | [Lithnet Access Manager](https://lithnet.io/products/access-manager) (AMS) is the next generation of Lithnet LAPS web. Lithnet Access Manager provides all the functionality of LAPS web, and more! This guide will explain the key differences between the products, and how to get started on your upgrade journey. 4 | 5 | ### User experience 6 | LAPS web users will feel a sense of familiarity with the Access Manager interface - while we have freshened-up the user interface, the experience is kept similar to LAPS web to minimize the organization change impact of upgrading to AMS. 7 | We've added features like showing a phonetic breakdown of the password (great for reading passwords out over the phone) and reading the password aloud using the text-to-speech engine of the browser. 8 | 9 | Our products have been first and foremost designed to enhance the experience of support staff in the field, and that experience continues with AMS. 10 | 11 | ### Administration and configuration experience 12 | We've ditched the need for IIS completely, so AMS runs as a standalone service. Along with a single installer executable, this drastically reduces the complexity of the setup and upgrade process. 13 | 14 | One of the biggest benefits to LAPS web administrators is that the dreaded config file has now been replaced with an intuitive configuration user interface. All configuration is now done through the configuration tool. 15 | 16 | Instead of fighting with XML, you're now able to use the familiar built-in security editor to assign permissions. 17 | 18 | We've rebuilt the authorization engine to solve common complaints about LAPS web, such as the computer not being able to be part of multiple targets, or errors that occur when organizational units used in targets are removed from the directory. 19 | 20 | You'll find support for new things like smart card authentication, sending audit notifications to Slack and Microsoft Teams, and scripts automatically generated by the application to help you configure things like permission delegation in AD. 21 | 22 | We know you'll just love the new features and configuration experience! 23 | 24 | ### Licensing 25 | Access Manager comes in two editions - Standard and Enterprise edition. Standard edition is free for all organizations, while enterprise edition is a paid product. 26 | 27 | However, **all scenarios supported by LAPS web, continue to be free in the Standard Edition of Access Manager**. LAPS web users can upgrade to AMS Standard edition, without any loss of functionality. In fact, the standard edition of AMS brings many new features that were not available in LAPS web, including support for accessing BitLocker recovery keys, and providing just-in-time administrative access to Windows computers. 28 | 29 | Enterprise edition offers features that were never available in LAPS web, and includes a dedicated support offering. 30 | 31 | See our [comparison guide](https://lithnet.io/ams-features) for more information on the differences between the standard and enterprise edition offerings. 32 | 33 | ### Migration 34 | Ready to get started? 35 | 36 | 1. First, download the latest edition of Access Manager from the [downloads](https://lithnet.io/products/access-manager/downloads) page. 37 | 2. We recommend starting with a new server to install AMS on. Otherwise, you may run into contention over the use of the web server ports between LAPS web and IIS and the new product. 38 | 3. [Install and configure the Access Manager Service](https://docs.lithnet.io/ams/installation/getting-started). You'll need to re-setup some things like the authentication provider, email setup, and UI options. 39 | 4. Finally, you can automatically import all your authorization rules directly from your LAPS web config file, using the [import wizard](https://docs.lithnet.io/ams/configuration/importing/importing-rules-from-lithnet-laps-web-app) 40 | 41 | If you run into any issues, you can log an [issue here on GitHub](https://github.com/lithnet/access-manager/issues/new) for support 42 | 43 | ### Support for LAPS Web 44 | The Lithnet LAPS Web product is no longer actively supported. The repository and wiki will remain available for historical purposes. If you experience any issues with LAPS web, please migrate to Access Manager, and if the issue is still not solved, then please raise an [issue on the Access Manager GitHub page](https://github.com/lithnet/access-manager/issues/new) 45 | 46 | Organizations that have licensed Access Manager Enterprise edition are eligible for a fixed-term support for LAPS web, while they transition to Access Manager. 47 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2000 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lithnet.Laps.Web", "Lithnet.Laps.Web\Lithnet.Laps.Web.csproj", "{AFF2B8F3-9BF2-4BDB-A841-F13AA9F4CEDC}" 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 | {AFF2B8F3-9BF2-4BDB-A841-F13AA9F4CEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AFF2B8F3-9BF2-4BDB-A841-F13AA9F4CEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AFF2B8F3-9BF2-4BDB-A841-F13AA9F4CEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AFF2B8F3-9BF2-4BDB-A841-F13AA9F4CEDC}.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 = {9CD0854C-BB55-4ACD-9C02-7944A982D6B9} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Data/Templates/EmailAuditFailure.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
{message}
11 | 12 | 13 | 14 | 15 | 18 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 | 58 | 61 | 62 | 63 | 64 | 67 | 70 | 71 | 72 | 73 | 76 | 79 | 80 | 81 | 82 | 85 | 88 | 89 | 90 | 91 | 94 | 97 | 98 | 99 | 102 | 105 | 106 | 107 | 108 | 109 | 112 | 115 | 116 | 117 | 120 | 123 | 124 | 125 |
16 | Date 17 | 19 | {datetime} 20 |
24 | User SAM account name 25 | 27 | {user.SamAccountName} 28 |
32 | User diplay name 33 | 35 | {user.DisplayName} 36 |
40 | User DN 41 | 43 | {user.DistinguishedName} 44 |
48 | User principal name 49 | 51 | {user.UserPrincipalName} 52 |
56 | User SID 57 | 59 | {user.Sid} 60 |
65 | User IP 66 | 68 | {request.UnmaskedIPAddress} 69 |
74 | User Hostname 75 | 77 | {request.HostName} 78 |
83 | Requested computer name 84 | 86 | {requestedComputerName} 87 |
92 | Computer SAM account name 93 | 95 | {computer.SamAccountName} 96 |
100 | Computer DN 101 | 103 | {computer.DistinguishedName} 104 |
110 | Matched target ID 111 | 113 | {target.ID} 114 |
118 | Matched reader principal 119 | 121 | {reader.Principal} 122 |

126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Data/Templates/EmailAuditSuccess.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 18 | 21 | 22 | 23 | 26 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 53 | 54 | 55 | 56 | 59 | 62 | 63 | 64 | 65 | 68 | 71 | 72 | 73 | 74 | 77 | 80 | 81 | 82 | 85 | 88 | 89 | 90 | 91 | 94 | 97 | 98 | 99 | 100 | 103 | 106 | 107 | 108 | 111 | 114 | 115 | 116 |
8 | Date 9 | 11 | {datetime} 12 |
16 | User SAM account name 17 | 19 | {user.SamAccountName} 20 |
24 | User diplay name 25 | 27 | {user.DisplayName} 28 |
32 | User DN 33 | 35 | {user.DistinguishedName} 36 |
40 | User principal name 41 | 43 | {user.UserPrincipalName} 44 |
48 | User SID 49 | 51 | {user.Sid} 52 |
57 | User IP 58 | 60 | {request.UnmaskedIPAddress} 61 |
66 | User Hostname 67 | 69 | {request.HostName} 70 |
75 | Computer SAM account name 76 | 78 | {computer.SamAccountName} 79 |
83 | Computer DN 84 | 86 | {computer.DistinguishedName} 87 |
92 | Password expiry 93 | 95 | {computer.LapsExpiryDate} 96 |
101 | Matched target ID 102 | 104 | {target.ID} 105 |
109 | Matched reader principal 110 | 112 | {reader.Principal} 113 |

117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Data/Templates/LogAuditFailure.txt: -------------------------------------------------------------------------------- 1 | AUDIT: FAILURE 2 | Username: {user.SamAccountName} 3 | Name: {user.DisplayName} 4 | UPN: {user.UserPrincipalName} 5 | SID: {user.Sid} 6 | User DN: {user.DistinguishedName} 7 | Requestor IP: {request.UnmaskedIPAddress} 8 | Requestor Hostname: {request.HostName} 9 | Requested computer: {requestedComputerName} 10 | Computer name: {computer.SamAccountName} 11 | Computer DN: {computer.DistinguishedName} 12 | Target ID: {target.ID} 13 | Reader principal: {reader.Principal} 14 | Detail: {message} -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Data/Templates/LogAuditSuccess.txt: -------------------------------------------------------------------------------- 1 | AUDIT: SUCCESS 2 | Username: {user.SamAccountName} 3 | Name: {user.DisplayName} 4 | UPN: {user.UserPrincipalName} 5 | SID: {user.Sid} 6 | User DN: {user.DistinguishedName} 7 | Requestor IP: {request.UnmaskedIPAddress} 8 | Requestor Hostname: {request.HostName} 9 | Computer name: {computer.SamAccountName} 10 | Computer DN: {computer.DistinguishedName} 11 | Password expiry: {computer.LapsExpiryDate} 12 | Target ID: {target.ID} 13 | Reader principal: {reader.Principal} -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/LogMessages.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 | The user {0} requested the password for computer {1} which was not found in the directory 122 | 123 | 124 | {0} has requested the password for {1} 125 | 126 | 127 | The user {0} requested access to computer {1} but no targets exist for this computer 128 | 129 | 130 | The computer {0} requested by {1} did not have a LAPS password in the directory 131 | 132 | 133 | The user {0} requested access to computer {1} but does not match any of the reader principals that are allowed access to this computer 134 | 135 | 136 | An unhandled error occurred while processing the request for the password for {0} by user {1} 137 | 138 | 139 | No user claim was found with the type {0} 140 | 141 | 142 | The request could not be processed because the authenticated user was not found in the directory. Claim information: 143 | {0} 144 | 145 | 146 | Target rule requires password to change after {0} 147 | 148 | 149 | unknown 150 | 151 | 152 | LAP access denied for {user.SamAccountName} to {computer.SamAccountName} 153 | 154 | 155 | LAP for {computer.SamAccountName} accessed by {user.SamAccountName} 156 | 157 | 158 | AUDIT: FAILURE 159 | Username: {user.SamAccountName} 160 | Name: {user.DisplayName} 161 | UPN: {user.UserPrincipalName} 162 | SID: {user.Sid} 163 | User DN: {user.DistinguishedName} 164 | Computer Name: {computer.SamAccountName} 165 | Computer DN: {computer.DistinguishedName} 166 | Target ID: {target.ID} 167 | Reader principal: {reader.Principal} 168 | Detail: {message} 169 | 170 | 171 | AUDIT: SUCCESS 172 | Username: {user.SamAccountName} 173 | Name: {user.DisplayName} 174 | UPN: {user.UserPrincipalName} 175 | SID: {user.Sid} 176 | User DN: {user.DistinguishedName} 177 | Computer Name: {computer.SamAccountName} 178 | Computer DN: {computer.DistinguishedName} 179 | Target ID: {target.ID} 180 | Reader principal: {reader.Principal} 181 | 182 | 183 | Successfully authenticated and mapped directory user 184 | {0} 185 | 186 | 187 | The authentication provider returned an error 188 | 189 | 190 | An error occurred during the authorization code flow 191 | 192 | 193 | The user {0} on IP {1} has exceeded the maximum allowed number of requests per IP ({2} per {3} seconds) 194 | 195 | 196 | The user {0} on IP {1} has exceeded the maximum allowed number of requests per user ({2} per {3} seconds) 197 | 198 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/UIMessages.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 Lithnet.Laps.Web.App_LocalResources { 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 | public class UIMessages { 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 UIMessages() { 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 | public 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("Lithnet.Laps.Web.App_LocalResources.UIMessages", typeof(UIMessages).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 | public 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 An authentication error occurred. 65 | /// 66 | public static string AuthNError { 67 | get { 68 | return ResourceManager.GetString("AuthNError", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Request another password. 74 | /// 75 | public static string ButtonRequestAnotherPassword { 76 | get { 77 | return ResourceManager.GetString("ButtonRequestAnotherPassword", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Request password. 83 | /// 84 | public static string ButtonRequestPassword { 85 | get { 86 | return ResourceManager.GetString("ButtonRequestPassword", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Computer name. 92 | /// 93 | public static string ComputerName { 94 | get { 95 | return ResourceManager.GetString("ComputerName", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Computer name is required. 101 | /// 102 | public static string ComputerNameIsRequired { 103 | get { 104 | return ResourceManager.GetString("ComputerNameIsRequired", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to The requested computer was not found in the directory. 110 | /// 111 | public static string ComputerNotFoundInDirectory { 112 | get { 113 | return ResourceManager.GetString("ComputerNotFoundInDirectory", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to Password details. 119 | /// 120 | public static string HeadingPasswordDetails { 121 | get { 122 | return ResourceManager.GetString("HeadingPasswordDetails", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to Request password. 128 | /// 129 | public static string HeadingRequestPassword { 130 | get { 131 | return ResourceManager.GetString("HeadingRequestPassword", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to You have been logged out. Please close all browser windows.. 137 | /// 138 | public static string LoggedOut { 139 | get { 140 | return ResourceManager.GetString("LoggedOut", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to The requested computer does not have a LAPS password. 146 | /// 147 | public static string NoLapsPassword { 148 | get { 149 | return ResourceManager.GetString("NoLapsPassword", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized string similar to <not set>. 155 | /// 156 | public static string NoLapsPasswordPlaceholder { 157 | get { 158 | return ResourceManager.GetString("NoLapsPasswordPlaceholder", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// Looks up a localized string similar to You are not authorized to access the password for this computer. 164 | /// 165 | public static string NotAuthorized { 166 | get { 167 | return ResourceManager.GetString("NotAuthorized", resourceCulture); 168 | } 169 | } 170 | 171 | /// 172 | /// Looks up a localized string similar to Password. 173 | /// 174 | public static string Password { 175 | get { 176 | return ResourceManager.GetString("Password", resourceCulture); 177 | } 178 | } 179 | 180 | /// 181 | /// Looks up a localized string similar to You have exceeded the maximum number of requests allowed in a specified period of time. 182 | /// 183 | public static string RateLimitError { 184 | get { 185 | return ResourceManager.GetString("RateLimitError", resourceCulture); 186 | } 187 | } 188 | 189 | /// 190 | /// Looks up a localized string similar to Your request could not be processed because your SSO identity could not be found in the directory. 191 | /// 192 | public static string SsoIdentityNotFound { 193 | get { 194 | return ResourceManager.GetString("SsoIdentityNotFound", resourceCulture); 195 | } 196 | } 197 | 198 | /// 199 | /// Looks up a localized string similar to An unexpected error occurred. 200 | /// 201 | public static string UnexpectedError { 202 | get { 203 | return ResourceManager.GetString("UnexpectedError", resourceCulture); 204 | } 205 | } 206 | 207 | /// 208 | /// Looks up a localized string similar to Valid until. 209 | /// 210 | public static string ValidUntil { 211 | get { 212 | return ResourceManager.GetString("ValidUntil", resourceCulture); 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/UIMessages.it.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/UIMessages.it.Designer.cs -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/UIMessages.it.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 | The requested computer was not found in the directory 122 | 123 | 124 | Non sei autorizzato ad accedere alla password per questo computer 125 | 126 | 127 | La tua richiesta non può essere elaborata perché la tua identità non può essere trovata nella directory 128 | 129 | 130 | Il computer richiesto non ha una password di amministratore locale 131 | 132 | 133 | Si è verificato un errore imprevisto 134 | 135 | 136 | Il nome del computer è richiesto 137 | 138 | 139 | Si è verificato un errore di autenticazione 140 | 141 | 142 | Richiedi un'altra password 143 | 144 | 145 | Richiedi password 146 | 147 | 148 | Nomi del computer 149 | 150 | 151 | Dettagli della password 152 | 153 | 154 | Richiedi password 155 | 156 | 157 | Password 158 | 159 | 160 | Valido fino a 161 | 162 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_LocalResources/UIMessages.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 | The requested computer was not found in the directory 122 | 123 | 124 | You are not authorized to access the password for this computer 125 | 126 | 127 | Your request could not be processed because your SSO identity could not be found in the directory 128 | 129 | 130 | The requested computer does not have a LAPS password 131 | 132 | 133 | An unexpected error occurred 134 | 135 | 136 | Computer name is required 137 | 138 | 139 | An authentication error occurred 140 | 141 | 142 | Request another password 143 | 144 | 145 | Request password 146 | 147 | 148 | Computer name 149 | 150 | 151 | Password details 152 | 153 | 154 | Request password 155 | 156 | 157 | Password 158 | 159 | 160 | Valid until 161 | 162 | 163 | <not set> 164 | 165 | 166 | You have exceeded the maximum number of requests allowed in a specified period of time 167 | 168 | 169 | You have been logged out. Please close all browser windows. 170 | 171 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace Lithnet.Laps.Web 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 15 | "~/Scripts/jquery.validate*")); 16 | 17 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 18 | "~/Scripts/bootstrap.js")); 19 | 20 | bundles.Add(new StyleBundle("~/Content/css").Include( 21 | "~/Content/bootstrap.css", 22 | "~/Content/site.css")); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace Lithnet.Laps.Web 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace Lithnet.Laps.Web 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/AuditElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class AuditElement : ConfigurationElement 10 | { 11 | private const string PropNotifySuccess = "emailOnSuccess"; 12 | private const string PropNotifyFailure = "emailOnFailure"; 13 | 14 | private const string PropEmailAddresses = "emailAddresses"; 15 | 16 | [ConfigurationProperty(PropNotifySuccess, IsRequired = false, DefaultValue = true)] 17 | public bool NotifySuccess => (bool) this[PropNotifySuccess]; 18 | 19 | [ConfigurationProperty(PropNotifyFailure, IsRequired = false, DefaultValue = true)] 20 | public bool NotifyFailure => (bool)this[PropNotifyFailure]; 21 | 22 | [ConfigurationProperty(PropEmailAddresses, IsRequired = false)] 23 | public string EmailAddresses => (string) this[PropEmailAddresses]; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/LapsConfigSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class LapsConfigSection : ConfigurationSection 10 | { 11 | private const string SectionName = "lithnet-laps"; 12 | private const string PropTargets = "targets"; 13 | private const string PropTarget = "target"; 14 | private const string PropAudit = "audit"; 15 | private const string PropRateLimitIP = "rate-limit-ip"; 16 | private const string PropRateLimitUser = "rate-limit-user"; 17 | 18 | 19 | [ConfigurationProperty(PropTargets)] 20 | [ConfigurationCollection(typeof(TargetCollection), AddItemName = PropTarget, CollectionType = ConfigurationElementCollectionType.BasicMap)] 21 | public TargetCollection Targets => (TargetCollection)this[PropTargets]; 22 | 23 | internal static LapsConfigSection GetConfiguration() 24 | { 25 | LapsConfigSection section = (LapsConfigSection)ConfigurationManager.GetSection(SectionName); 26 | 27 | if (section == null) 28 | { 29 | section = new LapsConfigSection(); 30 | } 31 | 32 | return section; 33 | } 34 | 35 | [ConfigurationProperty(LapsConfigSection.PropAudit, IsRequired = false)] 36 | public AuditElement Audit => (AuditElement)this[LapsConfigSection.PropAudit]; 37 | 38 | [ConfigurationProperty(PropRateLimitIP, IsRequired = false)] 39 | public RateLimitIPElement RateLimitIP => (RateLimitIPElement)this[PropRateLimitIP]; 40 | 41 | [ConfigurationProperty(PropRateLimitUser, IsRequired = false)] 42 | public RateLimitUserElement RateLimitUser => (RateLimitUserElement)this[PropRateLimitUser]; 43 | 44 | internal static LapsConfigSection Configuration { get; private set; } 45 | 46 | static LapsConfigSection() 47 | { 48 | LapsConfigSection.Configuration = LapsConfigSection.GetConfiguration(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/RateLimitIPElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class RateLimitIPElement : ConfigurationElement 10 | { 11 | private const string PropEnabled = "enabled"; 12 | private const string PropReqPerMinute = "requestsPerMinute"; 13 | private const string PropReqPerHour = "requestsPerHour"; 14 | private const string PropReqPerDay = "requestsPerDay"; 15 | private const string PropThrottleOnXffIP = "rateLimitOnXffIP"; 16 | 17 | [ConfigurationProperty(PropEnabled, IsRequired = false, DefaultValue = true)] 18 | public bool Enabled => (bool) this[PropEnabled]; 19 | 20 | [ConfigurationProperty(PropReqPerMinute, IsRequired = false, DefaultValue = 10)] 21 | public int ReqPerMinute => (int) this[PropReqPerMinute]; 22 | 23 | [ConfigurationProperty(PropReqPerHour, IsRequired = false, DefaultValue = 50)] 24 | public int ReqPerHour => (int) this[PropReqPerHour]; 25 | 26 | [ConfigurationProperty(PropReqPerDay, IsRequired = false, DefaultValue = 100)] 27 | public int ReqPerDay => (int) this[PropReqPerDay]; 28 | 29 | [ConfigurationProperty(PropThrottleOnXffIP, IsRequired = false, DefaultValue = false)] 30 | public bool ThrottleOnXffIP => (bool)this[PropThrottleOnXffIP]; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/RateLimitUserElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class RateLimitUserElement : ConfigurationElement 10 | { 11 | private const string PropEnabled = "enabled"; 12 | private const string PropReqPerMinute = "requestsPerMinute"; 13 | private const string PropReqPerHour = "requestsPerHour"; 14 | private const string PropReqPerDay = "requestsPerDay"; 15 | 16 | [ConfigurationProperty(PropEnabled, IsRequired = false, DefaultValue = true)] 17 | public bool Enabled => (bool) this[PropEnabled]; 18 | 19 | [ConfigurationProperty(PropReqPerMinute, IsRequired = false, DefaultValue = 10)] 20 | public int ReqPerMinute => (int) this[PropReqPerMinute]; 21 | 22 | [ConfigurationProperty(PropReqPerHour, IsRequired = false, DefaultValue = 50)] 23 | public int ReqPerHour => (int) this[PropReqPerHour]; 24 | 25 | [ConfigurationProperty(PropReqPerDay, IsRequired = false, DefaultValue = 100)] 26 | public int ReqPerDay => (int) this[PropReqPerDay]; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/ReaderCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class ReaderCollection : ConfigurationElementCollection 10 | { 11 | public ReaderElement this[int index] 12 | { 13 | get => (ReaderElement) this.BaseGet(index); 14 | set 15 | { 16 | if (this.BaseGet(index) != null) 17 | { 18 | this.BaseRemoveAt(index); 19 | } 20 | 21 | this.BaseAdd(index, value); 22 | } 23 | } 24 | 25 | public void Add(ReaderElement reader) 26 | { 27 | this.BaseAdd(reader); 28 | } 29 | 30 | public void Clear() 31 | { 32 | this.BaseClear(); 33 | } 34 | 35 | protected override ConfigurationElement CreateNewElement() 36 | { 37 | return new ReaderElement(); 38 | } 39 | 40 | protected override object GetElementKey(ConfigurationElement element) 41 | { 42 | return ((ReaderElement) element).Principal; 43 | } 44 | 45 | public void Remove(ReaderElement reader) 46 | { 47 | this.BaseRemove(reader.Principal); 48 | } 49 | 50 | public void RemoveAt(int index) 51 | { 52 | this.BaseRemoveAt(index); 53 | } 54 | 55 | public void Remove(string name) 56 | { 57 | this.BaseRemove(name); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/ReaderElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class ReaderElement : ConfigurationElement 10 | { 11 | private const string PropAudit = "audit"; 12 | private const string PropPrincipal = "principal"; 13 | 14 | [ConfigurationProperty(ReaderElement.PropAudit, IsRequired = false)] 15 | public AuditElement Audit => (AuditElement)this[ReaderElement.PropAudit]; 16 | 17 | [ConfigurationProperty(PropPrincipal, IsRequired = true, IsKey = true)] 18 | public string Principal => (string)this[PropPrincipal]; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/TargetCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public class TargetCollection : ConfigurationElementCollection 10 | { 11 | public TargetElement this[int index] 12 | { 13 | get => (TargetElement) this.BaseGet(index); 14 | set 15 | { 16 | if (this.BaseGet(index) != null) 17 | { 18 | this.BaseRemoveAt(index); 19 | } 20 | 21 | this.BaseAdd(index, value); 22 | } 23 | } 24 | 25 | public void Add(TargetElement reader) 26 | { 27 | this.BaseAdd(reader); 28 | } 29 | 30 | public void Clear() 31 | { 32 | this.BaseClear(); 33 | } 34 | 35 | protected override ConfigurationElement CreateNewElement() 36 | { 37 | return new TargetElement(); 38 | } 39 | 40 | protected override object GetElementKey(ConfigurationElement element) 41 | { 42 | return ((TargetElement) element).Name; 43 | } 44 | 45 | public void Remove(TargetElement reader) 46 | { 47 | this.BaseRemove(reader.Name); 48 | } 49 | 50 | public void RemoveAt(int index) 51 | { 52 | this.BaseRemoveAt(index); 53 | } 54 | 55 | public void Remove(string name) 56 | { 57 | this.BaseRemove(name); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/TargetElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.DirectoryServices; 5 | using System.Linq; 6 | using System.Security.Principal; 7 | using System.Web; 8 | using System.DirectoryServices.AccountManagement; 9 | 10 | namespace Lithnet.Laps.Web 11 | { 12 | public class TargetElement : ConfigurationElement 13 | { 14 | private Principal principal; 15 | 16 | private const string PropAudit = "audit"; 17 | private const string PropReaders = "readers"; 18 | private const string PropReader = "reader"; 19 | private const string PropExpireAfter = "expire-after"; 20 | private const string PropID = "name"; 21 | private const string PropIDType = "type"; 22 | 23 | [ConfigurationProperty(TargetElement.PropAudit, IsRequired = false)] 24 | public AuditElement Audit => (AuditElement) this[TargetElement.PropAudit]; 25 | 26 | [ConfigurationProperty(PropIDType, IsRequired = false)] 27 | public TargetType Type => (TargetType) this[PropIDType]; 28 | 29 | [ConfigurationProperty(PropID, IsRequired = true, IsKey = true)] 30 | public string Name => (string) this[PropID]; 31 | 32 | [ConfigurationProperty(TargetElement.PropExpireAfter, IsRequired = false)] 33 | public string ExpireAfter => (string) this[TargetElement.PropExpireAfter]; 34 | 35 | [ConfigurationProperty(PropReaders, IsRequired = true)] 36 | [ConfigurationCollection(typeof(ReaderCollection), AddItemName = PropReader, CollectionType = ConfigurationElementCollectionType.BasicMap)] 37 | public ReaderCollection Readers => (ReaderCollection) this[PropReaders]; 38 | 39 | internal Principal PrincipalObject 40 | { 41 | get 42 | { 43 | if (this.principal == null) 44 | { 45 | PrincipalContext ctx = new PrincipalContext(ContextType.Domain); 46 | this.principal = Principal.FindByIdentity(ctx, this.Name); 47 | } 48 | 49 | return this.principal; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/ConfigSection/TargetType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web 8 | { 9 | public enum TargetType 10 | { 11 | Computer, 12 | Group, 13 | Container 14 | } 15 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/Site.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Inconsolata; 3 | src: url('webfonts/Inconsolata-Regular.ttf'); 4 | } 5 | 6 | body { 7 | color: #666; 8 | /*font-family: "Open Sans",Arial,Helvetica,sans-serif;*/ 9 | font-size: 13px; 10 | } 11 | 12 | .app-header { 13 | display: block; 14 | font-size: 18px; 15 | font-weight: 300; 16 | color: #242424; 17 | padding: 15px 13px; 18 | border-bottom-width: 0px; 19 | border-bottom-style: solid; 20 | border-bottom-color: rgba(0, 0, 0, .1); 21 | background: white; 22 | position: relative; 23 | border: 1px solid #dcdcdc; 24 | align-items: center; 25 | text-align: center; 26 | } 27 | 28 | .center-content { 29 | align-items: center; 30 | text-align: center; 31 | } 32 | 33 | .app-logo { 34 | max-width: 200px; 35 | } 36 | 37 | .app-title { 38 | font-weight: 400; 39 | } 40 | 41 | .content { 42 | width: 500px; 43 | margin-left: auto; 44 | margin-right: auto; 45 | padding: 10px; 46 | } 47 | 48 | .form-container { 49 | background-color: #fefbfb; 50 | border: 1px solid #dcdcdc; 51 | position: relative; 52 | } 53 | 54 | .form-container header { 55 | display: block; 56 | font-size: 18px; 57 | font-weight: 300; 58 | color: #242424; 59 | padding: 15px 13px; 60 | margin: 0; 61 | border-bottom-width: 1px; 62 | border-bottom-style: solid; 63 | border-bottom-color: rgba(0,0,0,.1); 64 | background: rgba(248,248,248,.9); 65 | position: relative; 66 | } 67 | 68 | .form-container fieldset { 69 | display: block; 70 | padding: 25px 14px 5px; 71 | border: none; 72 | background: rgba(255,255,255,.9); 73 | position: relative; 74 | } 75 | 76 | .form-container section { 77 | margin-bottom: 15px; 78 | position: relative; 79 | } 80 | 81 | .form-container label.title { 82 | display: block; 83 | margin-bottom: 3px; 84 | line-height: 19px; 85 | font-weight: 600; 86 | font-size: 13px; 87 | color: #333; 88 | } 89 | 90 | .form-container label.input { 91 | position: relative; 92 | display: block; 93 | font-weight: 400; 94 | max-width: 100%; 95 | font-size: 13px; 96 | } 97 | 98 | .form-container i.icon-append { 99 | border-color: #bdbdbd; 100 | color: #a2a2a2; 101 | right: 5px; 102 | padding-left: 3px; 103 | border-left-width: 1px; 104 | border-left-style: solid; 105 | position: absolute; 106 | top: 5px; 107 | width: 22px; 108 | height: 22px; 109 | font-size: 14px; 110 | line-height: 22px; 111 | text-align: center; 112 | } 113 | 114 | .form-container input.dataEntry { 115 | display: block; 116 | padding-right: 37px; 117 | border-color: #bdbdbd; 118 | width: 100%; 119 | height: 32px; 120 | padding: 5px 10px; 121 | outline: 0; 122 | border-width: 1px; 123 | border-style: solid; 124 | border-radius: 0; 125 | background: #fff; 126 | color: #404040; 127 | } 128 | 129 | .form-container .tooltip { 130 | position: absolute; 131 | bottom: 100%; 132 | margin-bottom: 15px; 133 | z-index: 99999; 134 | left: -9999px; 135 | padding: 2px 8px 3px; 136 | font-size: 11px; 137 | line-height: 16px; 138 | font-weight: 400; 139 | background: rgba(0,0,0,.9); 140 | color: #fff; 141 | opacity: 0; 142 | } 143 | 144 | .form-container .tooltip::after { 145 | top: 100%; 146 | right: 11px; 147 | border-top: 4px solid rgba(0,0,0,.9); 148 | border-right: 4px solid transparent; 149 | border-left: 4px solid transparent; 150 | content: ''; 151 | position: absolute; 152 | margin: 0; 153 | padding: 0; 154 | } 155 | 156 | .form-container footer { 157 | display: block; 158 | padding: 7px 14px 15px; 159 | border-top: 1px solid rgba(0,0,0,.1); 160 | background: rgba(248,248,248,.9); 161 | margin: 0; 162 | } 163 | 164 | .form-container .error { 165 | color: #8f2626; 166 | text-align: center; 167 | display: block; 168 | } 169 | 170 | .resultsText { 171 | border-color: #f0f0f0; 172 | height: 32px; 173 | padding: 5px 10px; 174 | outline: 0; 175 | border-width: 1px; 176 | border-style: solid; 177 | border-radius: 0; 178 | background: #fbfbfb; 179 | color: #404040; 180 | width: 100%; 181 | display: table-cell; 182 | position: relative; 183 | } 184 | 185 | .input-group { 186 | width: 100%; 187 | display: table; 188 | } 189 | 190 | .input-group-append { 191 | width: 1%; 192 | vertical-align: middle; 193 | display: table-cell; 194 | height: 32px; 195 | } 196 | 197 | .input-group-append button { 198 | width: 32px; 199 | height: 32px; 200 | } 201 | 202 | .password-field { 203 | font-family: Inconsolata, Consolas, Courier New, Courier, monospace; 204 | font-size: large; 205 | font-weight: bold; 206 | } 207 | 208 | #copy-button { 209 | margin-left: 10px; 210 | } 211 | 212 | .password-char-digit { 213 | color: darkblue; 214 | display: inline-block; 215 | } 216 | 217 | .password-char-letter { 218 | color: slategray; 219 | display: inline-block; 220 | } 221 | 222 | .password-char-other { 223 | color: darkred; 224 | display: inline-block; 225 | } 226 | 227 | @media only screen and (max-width: 600px) { 228 | .form-container { 229 | width: auto; 230 | margin-right: 0; 231 | margin-left: 0; 232 | border: 0; 233 | margin-top: 0; 234 | box-shadow: none; 235 | } 236 | 237 | /*.auth-container .auth-content { 238 | max-width: 316px; 239 | margin: 0 auto; 240 | }*/ 241 | } 242 | 243 | @media only screen and (max-device-width: 480px) { 244 | .content { 245 | margin-top: 0; 246 | width: 100%; 247 | } 248 | } 249 | 250 | @media only screen and (max-width: 400px) { 251 | .content { 252 | width: 100%; 253 | } 254 | } 255 | 256 | @media only screen and (max-height: 750px) { 257 | .content { 258 | margin-top: 0; 259 | } 260 | } 261 | 262 | .logout-link { 263 | color: #777; 264 | } 265 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 22 | display: block; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 28 | font-size: 1rem; 29 | font-weight: 400; 30 | line-height: 1.5; 31 | color: #212529; 32 | text-align: left; 33 | background-color: #fff; 34 | } 35 | 36 | [tabindex="-1"]:focus { 37 | outline: 0 !important; 38 | } 39 | 40 | hr { 41 | box-sizing: content-box; 42 | height: 0; 43 | overflow: visible; 44 | } 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | margin-top: 0; 48 | margin-bottom: 0.5rem; 49 | } 50 | 51 | p { 52 | margin-top: 0; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | abbr[title], 57 | abbr[data-original-title] { 58 | text-decoration: underline; 59 | -webkit-text-decoration: underline dotted; 60 | text-decoration: underline dotted; 61 | cursor: help; 62 | border-bottom: 0; 63 | -webkit-text-decoration-skip-ink: none; 64 | text-decoration-skip-ink: none; 65 | } 66 | 67 | address { 68 | margin-bottom: 1rem; 69 | font-style: normal; 70 | line-height: inherit; 71 | } 72 | 73 | ol, 74 | ul, 75 | dl { 76 | margin-top: 0; 77 | margin-bottom: 1rem; 78 | } 79 | 80 | ol ol, 81 | ul ul, 82 | ol ul, 83 | ul ol { 84 | margin-bottom: 0; 85 | } 86 | 87 | dt { 88 | font-weight: 700; 89 | } 90 | 91 | dd { 92 | margin-bottom: .5rem; 93 | margin-left: 0; 94 | } 95 | 96 | blockquote { 97 | margin: 0 0 1rem; 98 | } 99 | 100 | b, 101 | strong { 102 | font-weight: bolder; 103 | } 104 | 105 | small { 106 | font-size: 80%; 107 | } 108 | 109 | sub, 110 | sup { 111 | position: relative; 112 | font-size: 75%; 113 | line-height: 0; 114 | vertical-align: baseline; 115 | } 116 | 117 | sub { 118 | bottom: -.25em; 119 | } 120 | 121 | sup { 122 | top: -.5em; 123 | } 124 | 125 | a { 126 | color: #007bff; 127 | text-decoration: none; 128 | background-color: transparent; 129 | } 130 | 131 | a:hover { 132 | color: #0056b3; 133 | text-decoration: underline; 134 | } 135 | 136 | a:not([href]):not([tabindex]) { 137 | color: inherit; 138 | text-decoration: none; 139 | } 140 | 141 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { 142 | color: inherit; 143 | text-decoration: none; 144 | } 145 | 146 | a:not([href]):not([tabindex]):focus { 147 | outline: 0; 148 | } 149 | 150 | pre, 151 | code, 152 | kbd, 153 | samp { 154 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 155 | font-size: 1em; 156 | } 157 | 158 | pre { 159 | margin-top: 0; 160 | margin-bottom: 1rem; 161 | overflow: auto; 162 | } 163 | 164 | figure { 165 | margin: 0 0 1rem; 166 | } 167 | 168 | img { 169 | vertical-align: middle; 170 | border-style: none; 171 | } 172 | 173 | svg { 174 | overflow: hidden; 175 | vertical-align: middle; 176 | } 177 | 178 | table { 179 | border-collapse: collapse; 180 | } 181 | 182 | caption { 183 | padding-top: 0.75rem; 184 | padding-bottom: 0.75rem; 185 | color: #6c757d; 186 | text-align: left; 187 | caption-side: bottom; 188 | } 189 | 190 | th { 191 | text-align: inherit; 192 | } 193 | 194 | label { 195 | display: inline-block; 196 | margin-bottom: 0.5rem; 197 | } 198 | 199 | button { 200 | border-radius: 0; 201 | } 202 | 203 | button:focus { 204 | outline: 1px dotted; 205 | outline: 5px auto -webkit-focus-ring-color; 206 | } 207 | 208 | input, 209 | button, 210 | select, 211 | optgroup, 212 | textarea { 213 | margin: 0; 214 | font-family: inherit; 215 | font-size: inherit; 216 | line-height: inherit; 217 | } 218 | 219 | button, 220 | input { 221 | overflow: visible; 222 | } 223 | 224 | button, 225 | select { 226 | text-transform: none; 227 | } 228 | 229 | select { 230 | word-wrap: normal; 231 | } 232 | 233 | button, 234 | [type="button"], 235 | [type="reset"], 236 | [type="submit"] { 237 | -webkit-appearance: button; 238 | } 239 | 240 | button:not(:disabled), 241 | [type="button"]:not(:disabled), 242 | [type="reset"]:not(:disabled), 243 | [type="submit"]:not(:disabled) { 244 | cursor: pointer; 245 | } 246 | 247 | button::-moz-focus-inner, 248 | [type="button"]::-moz-focus-inner, 249 | [type="reset"]::-moz-focus-inner, 250 | [type="submit"]::-moz-focus-inner { 251 | padding: 0; 252 | border-style: none; 253 | } 254 | 255 | input[type="radio"], 256 | input[type="checkbox"] { 257 | box-sizing: border-box; 258 | padding: 0; 259 | } 260 | 261 | input[type="date"], 262 | input[type="time"], 263 | input[type="datetime-local"], 264 | input[type="month"] { 265 | -webkit-appearance: listbox; 266 | } 267 | 268 | textarea { 269 | overflow: auto; 270 | resize: vertical; 271 | } 272 | 273 | fieldset { 274 | min-width: 0; 275 | padding: 0; 276 | margin: 0; 277 | border: 0; 278 | } 279 | 280 | legend { 281 | display: block; 282 | width: 100%; 283 | max-width: 100%; 284 | padding: 0; 285 | margin-bottom: .5rem; 286 | font-size: 1.5rem; 287 | line-height: inherit; 288 | color: inherit; 289 | white-space: normal; 290 | } 291 | 292 | progress { 293 | vertical-align: baseline; 294 | } 295 | 296 | [type="number"]::-webkit-inner-spin-button, 297 | [type="number"]::-webkit-outer-spin-button { 298 | height: auto; 299 | } 300 | 301 | [type="search"] { 302 | outline-offset: -2px; 303 | -webkit-appearance: none; 304 | } 305 | 306 | [type="search"]::-webkit-search-decoration { 307 | -webkit-appearance: none; 308 | } 309 | 310 | ::-webkit-file-upload-button { 311 | font: inherit; 312 | -webkit-appearance: button; 313 | } 314 | 315 | output { 316 | display: inline-block; 317 | } 318 | 319 | summary { 320 | display: list-item; 321 | cursor: pointer; 322 | } 323 | 324 | template { 325 | display: none; 326 | } 327 | 328 | [hidden] { 329 | display: none !important; 330 | } 331 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-brands.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Brands'; 7 | font-style: normal; 8 | font-weight: normal; 9 | src: url("../webfonts/fa-brands-400.eot"); 10 | src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); } 11 | 12 | .fab { 13 | font-family: 'Font Awesome 5 Brands'; } 14 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-brands.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands} -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-regular.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Free'; 7 | font-style: normal; 8 | font-weight: 400; 9 | src: url("../webfonts/fa-regular-400.eot"); 10 | src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); } 11 | 12 | .far { 13 | font-family: 'Font Awesome 5 Free'; 14 | font-weight: 400; } 15 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-regular.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400} -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-solid.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: 'Font Awesome 5 Free'; 7 | font-style: normal; 8 | font-weight: 900; 9 | src: url("../webfonts/fa-solid-900.eot"); 10 | src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); } 11 | 12 | .fa, 13 | .fas { 14 | font-family: 'Font Awesome 5 Free'; 15 | font-weight: 900; } 16 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/fontawesome/fa-solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900} -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/Content/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using Lithnet.Laps.Web.Models; 7 | 8 | namespace Lithnet.Laps.Web.Controllers 9 | { 10 | public class HomeController : Controller 11 | { 12 | public ActionResult Index() 13 | { 14 | return this.RedirectToAction("Get", "Lap"); 15 | } 16 | 17 | public ActionResult AuthNError() 18 | { 19 | return this.View(); 20 | } 21 | 22 | public ActionResult SignOut() 23 | { 24 | if (this.Request.GetOwinContext().Authentication.User.Identity.IsAuthenticated) 25 | { 26 | this.Request.GetOwinContext() 27 | .Authentication 28 | .SignOut(this.HttpContext.GetOwinContext() 29 | .Authentication.GetAuthenticationTypes() 30 | .Select(o => o.AuthenticationType).ToArray()); 31 | } 32 | 33 | return this.View("LogOut"); 34 | } 35 | 36 | public ActionResult LogOut() 37 | { 38 | return this.View(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Exceptions/NoMatchingTargetException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace Lithnet.Laps.Web 7 | { 8 | [System.Serializable] 9 | public class NoMatchingTargetException : Exception 10 | { 11 | public NoMatchingTargetException() 12 | { 13 | } 14 | 15 | public NoMatchingTargetException(string message) 16 | : base(message) 17 | { 18 | } 19 | 20 | public NoMatchingTargetException(string message, Exception inner) 21 | : base(message, inner) 22 | { 23 | } 24 | 25 | 26 | protected NoMatchingTargetException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 27 | : base(info, context) 28 | { 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="Lithnet.Laps.Web.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.DirectoryServices.AccountManagement; 5 | using System.Linq; 6 | using System.Web; 7 | using System.Web.Mvc; 8 | using System.Web.Optimization; 9 | using System.Web.Routing; 10 | 11 | namespace Lithnet.Laps.Web 12 | { 13 | public class MvcApplication : System.Web.HttpApplication 14 | { 15 | protected void Application_Start() 16 | { 17 | AreaRegistration.RegisterAllAreas(); 18 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 19 | RouteConfig.RegisterRoutes(RouteTable.Routes); 20 | BundleConfig.RegisterBundles(BundleTable.Bundles); 21 | } 22 | 23 | public static bool CanLogout => Startup.CanLogout && (HttpContext.Current?.User?.Identity?.IsAuthenticated ?? false); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Internal/Directory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.DirectoryServices.AccountManagement; 5 | using System.Linq; 6 | using System.Security.Principal; 7 | using System.Web; 8 | 9 | namespace Lithnet.Laps.Web 10 | { 11 | internal static class Directory 12 | { 13 | public const string AttrSamAccountName = "samAccountName"; 14 | public const string AttrMsMcsAdmPwd = "ms-Mcs-AdmPwd"; 15 | public const string AttrMsMcsAdmPwdExpirationTime = "ms-Mcs-AdmPwdExpirationTime"; 16 | 17 | internal static bool IsPrincipalInOu(Principal p, string ou) 18 | { 19 | DirectorySearcher d = new DirectorySearcher(); 20 | d.SearchRoot = new DirectoryEntry($"LDAP://{ou}"); 21 | d.SearchScope = SearchScope.Subtree; 22 | d.Filter = $"objectGuid={p.Guid.ToOctetString()}"; 23 | 24 | return d.FindOne() != null; 25 | } 26 | 27 | internal static ComputerPrincipal GetComputerPrincipal(string name) 28 | { 29 | PrincipalContext ctx = new PrincipalContext(ContextType.Domain); 30 | return ComputerPrincipal.FindByIdentity(ctx, name); 31 | } 32 | 33 | internal static GroupPrincipal GetGroupPrincipal(string name) 34 | { 35 | PrincipalContext ctx = new PrincipalContext(ContextType.Domain); 36 | return GroupPrincipal.FindByIdentity(ctx, name); 37 | } 38 | 39 | internal static Principal GetPrincipal(string name) 40 | { 41 | PrincipalContext ctx = new PrincipalContext(ContextType.Domain); 42 | return Principal.FindByIdentity(ctx, name); 43 | } 44 | 45 | internal static Principal GetPrincipal(IdentityType type, string name) 46 | { 47 | PrincipalContext ctx = new PrincipalContext(ContextType.Domain); 48 | return Principal.FindByIdentity(ctx, type, name); 49 | } 50 | 51 | internal static bool IsPrincipalInGroup(Principal p, GroupPrincipal group) 52 | { 53 | if (group?.Sid == null || group.Sid.BinaryLength == 0) 54 | { 55 | return false; 56 | } 57 | 58 | byte[] groupSid = new byte[group.Sid.BinaryLength]; 59 | group.Sid.GetBinaryForm(groupSid, 0); 60 | return IsPrincipalInGroup(p, groupSid); 61 | } 62 | 63 | private static bool IsPrincipalInGroup(Principal p, byte[] groupSid) 64 | { 65 | SearchResult result = GetDirectoryEntry(p, "tokenGroups"); 66 | 67 | if (result == null) 68 | { 69 | return false; 70 | } 71 | 72 | foreach (byte[] value in result.Properties["tokenGroups"].OfType()) 73 | { 74 | if (groupSid.SequenceEqual(value)) 75 | { 76 | return true; 77 | } 78 | } 79 | 80 | return false; 81 | } 82 | 83 | internal static SearchResult GetDirectoryEntry(Principal p, params string[] propertiesToLoad) 84 | { 85 | return GetDirectoryEntry(p.DistinguishedName, propertiesToLoad); 86 | } 87 | 88 | internal static SearchResult GetDirectoryEntry(string dn, params string[] propertiesToLoad) 89 | { 90 | DirectorySearcher d = new DirectorySearcher(); 91 | d.SearchRoot = new DirectoryEntry($"LDAP://{dn}"); 92 | d.SearchScope = SearchScope.Base; 93 | d.Filter = $"objectClass=*"; 94 | foreach (string prop in propertiesToLoad) 95 | { 96 | d.PropertiesToLoad.Add(prop); 97 | } 98 | 99 | return d.FindOne(); 100 | } 101 | 102 | internal static string ToOctetString(this Guid? guid) 103 | { 104 | if (!guid.HasValue) 105 | { 106 | return null; 107 | } 108 | 109 | return $"\\{string.Join("\\", guid.Value.ToByteArray().Select(t => t.ToString("X2")))}"; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Internal/EventIDs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace Lithnet.Laps.Web 7 | { 8 | internal static class EventIDs 9 | { 10 | public const int PasswordAccessed = 200; 11 | public const int UserAuthenticated = 201; 12 | public const int UserRequestedPassword = 202; 13 | 14 | public const int SsoIdentityNotFound = 400; 15 | public const int ComputerNotFound = 401; 16 | public const int LapsPasswordNotPresent = 402; 17 | 18 | public const int AuthZFailedNoReaderPrincipalMatch = 500; 19 | public const int AuthZFailedNoTargetMatch = 501; 20 | public const int RateLimitExceededIP = 502; 21 | public const int RateLimitExceededUser = 503; 22 | 23 | public const int UnexpectedError = 600; 24 | public const int AuditErrorCannotSendSuccessEmail = 601; 25 | public const int AuditErrorCannotSendFailureEmail = 602; 26 | public const int ErrorLoadingTemplateResource = 603; 27 | 28 | public const int OidcAuthZCodeError = 700; 29 | public const int OwinAuthNError = 701; 30 | } 31 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Internal/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | using System.DirectoryServices.AccountManagement; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Text; 8 | using System.Web; 9 | 10 | namespace Lithnet.Laps.Web 11 | { 12 | internal static class Extensions 13 | { 14 | public static string GetXffList(this HttpRequest request) 15 | { 16 | return request.ServerVariables["HTTP_X_FORWARDED_FOR"]; 17 | } 18 | 19 | public static string GetXffIP(this HttpRequest request) 20 | { 21 | return request.GetXffList()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)?.FirstOrDefault(); 22 | } 23 | 24 | public static string GetUnmaskedIP(this HttpRequest request) 25 | { 26 | string ip = request.GetXffIP(); 27 | 28 | return string.IsNullOrWhiteSpace(ip) ? request.UserHostAddress : ip; 29 | } 30 | 31 | public static string GetXffList(this HttpRequestBase request) 32 | { 33 | return request.ServerVariables["HTTP_X_FORWARDED_FOR"]; 34 | } 35 | 36 | public static string GetXffIP(this HttpRequestBase request) 37 | { 38 | return request.GetXffList()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)?.FirstOrDefault(); 39 | } 40 | 41 | public static string GetUnmaskedIP(this HttpRequestBase request) 42 | { 43 | string ip = request.GetXffIP(); 44 | 45 | return string.IsNullOrWhiteSpace(ip) ? request.UserHostAddress : ip; 46 | } 47 | 48 | 49 | public static UserPrincipal FindUserPrincipalByClaim(this ClaimsIdentity p, IdentityType identityType, params string[] claimNames) 50 | { 51 | foreach (string claimName in claimNames) 52 | { 53 | Claim c = p.FindFirst(claimName); 54 | if (c != null) 55 | { 56 | return UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain), identityType, c.Value); 57 | } 58 | } 59 | 60 | return null; 61 | } 62 | 63 | public static string ToClaimList(this ClaimsIdentity p) 64 | { 65 | StringBuilder builder = new StringBuilder(); 66 | foreach (Claim c in p.Claims) 67 | { 68 | builder.Append(c.Type).Append(": ").AppendLine(c.Value); 69 | } 70 | 71 | return builder.ToString(); 72 | } 73 | 74 | public static DateTime? GetPropertyDateTimeFromLong(this SearchResult result, string propertyName) 75 | { 76 | if (!result.Properties.Contains(propertyName)) 77 | { 78 | return null; 79 | } 80 | 81 | long value = (long)result.Properties[propertyName][0]; 82 | return DateTime.FromFileTimeUtc(value).ToLocalTime(); 83 | } 84 | 85 | public static string GetPropertyString(this SearchResult result, string propertyName) 86 | { 87 | if (!result.Properties.Contains(propertyName)) 88 | { 89 | return null; 90 | } 91 | 92 | return result.Properties[Directory.AttrMsMcsAdmPwd][0]?.ToString(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Internal/RateLimiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.DirectoryServices.AccountManagement; 3 | using System.Web; 4 | using System.Web.Caching; 5 | using Lithnet.Laps.Web.App_LocalResources; 6 | using Lithnet.Laps.Web.Models; 7 | 8 | namespace Lithnet.Laps.Web 9 | { 10 | internal static class RateLimiter 11 | { 12 | public static bool IsRateLimitExceeded(LapRequestModel model, UserPrincipal p, HttpRequestBase r) 13 | { 14 | if (LapsConfigSection.Configuration.RateLimitIP.Enabled) 15 | { 16 | if (RateLimiter.IsIpThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitIP.ReqPerMinute, 60) 17 | || RateLimiter.IsIpThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitIP.ReqPerHour, 3600) 18 | || RateLimiter.IsIpThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitIP.ReqPerDay, 86400)) 19 | { 20 | return true; 21 | } 22 | } 23 | 24 | if (LapsConfigSection.Configuration.RateLimitUser.Enabled) 25 | { 26 | if (RateLimiter.IsUserThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitUser.ReqPerMinute, 60) 27 | || RateLimiter.IsUserThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitUser.ReqPerHour, 3600) 28 | || RateLimiter.IsUserThresholdExceeded(model, p, r, LapsConfigSection.Configuration.RateLimitUser.ReqPerDay, 86400)) 29 | { 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | private static bool IsIpThresholdExceeded(LapRequestModel model, UserPrincipal p, HttpRequestBase r, int threshold, int duration) 37 | { 38 | if (RateLimiter.IsThresholdExceeded(r, threshold, duration)) 39 | { 40 | Reporting.PerformAuditFailureActions(model, UIMessages.RateLimitError, EventIDs.RateLimitExceededIP, 41 | string.Format(LogMessages.RateLimitExceededIP, p.SamAccountName, r.UserHostAddress, threshold, duration), null, null, null, p, null); 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | private static bool IsUserThresholdExceeded(LapRequestModel model, UserPrincipal p, HttpRequestBase r, int threshold, int duration) 49 | { 50 | if (RateLimiter.IsThresholdExceeded(p, threshold, duration)) 51 | { 52 | Reporting.PerformAuditFailureActions(model, UIMessages.RateLimitError, EventIDs.RateLimitExceededUser, 53 | string.Format(LogMessages.RateLimitExceededUser, p.SamAccountName, r.UserHostAddress, threshold, duration), null, null, null, p, null); 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | private static bool IsThresholdExceeded(HttpRequestBase r, int threshold, int duration) 61 | { 62 | string key1 = string.Join(@"-", duration, threshold, LapsConfigSection.Configuration.RateLimitIP.ThrottleOnXffIP ? r.GetUnmaskedIP() : r.UserHostAddress); 63 | 64 | return IsThresholdExceeded(key1, threshold, duration); 65 | } 66 | 67 | private static bool IsThresholdExceeded(UserPrincipal p, int threshold, int duration) 68 | { 69 | string key1 = string.Join(@"-", duration, threshold, p.Sid); 70 | 71 | return IsThresholdExceeded(key1, threshold, duration); 72 | } 73 | 74 | private static bool IsThresholdExceeded(string key, int threshold, int duration) 75 | { 76 | int count = 1; 77 | 78 | if (HttpRuntime.Cache[key] != null) 79 | { 80 | count = (int)HttpRuntime.Cache[key] + 1; 81 | } 82 | 83 | HttpRuntime.Cache.Insert( 84 | key, 85 | count, 86 | null, 87 | DateTime.UtcNow.AddSeconds(duration), 88 | Cache.NoSlidingExpiration, 89 | CacheItemPriority.Low, 90 | null 91 | ); 92 | 93 | return count > threshold; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Lithnet.Laps.Web.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | Yes -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Models/LapEntryModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace Lithnet.Laps.Web.Models 8 | { 9 | public class LapEntryModel 10 | { 11 | [Required] 12 | public string ComputerName { get; set; } 13 | 14 | public string Password { get; set; } 15 | 16 | public string HtmlPassword { get; set; } 17 | 18 | public DateTime? ValidUntil { get; set; } 19 | 20 | public string FailureReason { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Models/LapRequestModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Web; 7 | using Lithnet.Laps.Web.App_LocalResources; 8 | 9 | namespace Lithnet.Laps.Web.Models 10 | { 11 | [Localizable(true)] 12 | public class LapRequestModel 13 | { 14 | [Required(ErrorMessageResourceType = typeof(UIMessages), ErrorMessageResourceName = "ComputerNameIsRequired")] 15 | public string ComputerName { get; set; } 16 | 17 | public string FailureReason { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/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("Lithnet.Laps.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Lithnet.Laps.Web")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 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("d32d2723-5997-4d89-a31e-bc583df24da7")] 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 Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.*")] -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | FileSystem 10 | Release 11 | Any CPU 12 | 13 | True 14 | False 15 | bin\Release\Publish 16 | True 17 | True 18 | True 19 | False 20 | DonotMerge 21 | 22 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Properties/PublishProfiles/Publish to local IIS.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | MSDeploy 9 | Release 10 | Any CPU 11 | 12 | True 13 | False 14 | localhost 15 | laps-test 16 | 17 | True 18 | InProc 19 | False 20 | 21 | <_SavePWD>False 22 | 23 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.0 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])}); -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/esm/popper-utils.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */function a(a,b){if(1!==a.nodeType)return[];var c=getComputedStyle(a,null);return b?c[b]:c}function b(a){return'HTML'===a.nodeName?a:a.parentNode||a.host}function c(d){if(!d)return document.body;switch(d.nodeName){case'HTML':case'BODY':return d.ownerDocument.body;case'#document':return d.body;}var e=a(d),f=e.overflow,g=e.overflowX,h=e.overflowY;return /(auto|scroll|overlay)/.test(f+h+g)?d:c(b(d))}var d={},e=function(){var a=0=c.clientWidth&&d>=c.clientHeight}),k=0, ggray , rhysd , joscha , seckardt , marcfallows 4 | 5 | // This file only declares the public portions of the API. 6 | // It should not define internal pieces such as utils or modifier details. 7 | 8 | export type Position = 'top' | 'right' | 'bottom' | 'left'; 9 | 10 | export type Placement = 'auto-start' 11 | | 'auto' 12 | | 'auto-end' 13 | | 'top-start' 14 | | 'top' 15 | | 'top-end' 16 | | 'right-start' 17 | | 'right' 18 | | 'right-end' 19 | | 'bottom-end' 20 | | 'bottom' 21 | | 'bottom-start' 22 | | 'left-end' 23 | | 'left' 24 | | 'left-start'; 25 | 26 | export type Boundary = 'scrollParent' | 'viewport' | 'window'; 27 | 28 | export type Behavior = 'flip' | 'clockwise' | 'counterclockwise'; 29 | 30 | export type ModifierFn = (data: Data, options: Object) => Data; 31 | 32 | export interface BaseModifier { 33 | order?: number; 34 | enabled?: boolean; 35 | fn?: ModifierFn; 36 | } 37 | 38 | export interface Modifiers { 39 | shift?: BaseModifier; 40 | offset?: BaseModifier & { 41 | offset?: number | string, 42 | }; 43 | preventOverflow?: BaseModifier & { 44 | priority?: Position[], 45 | padding?: number, 46 | boundariesElement?: Boundary | Element, 47 | escapeWithReference?: boolean 48 | }; 49 | keepTogether?: BaseModifier; 50 | arrow?: BaseModifier & { 51 | element?: string | Element, 52 | }; 53 | flip?: BaseModifier & { 54 | behavior?: Behavior | Position[], 55 | padding?: number, 56 | boundariesElement?: Boundary | Element, 57 | }; 58 | inner?: BaseModifier; 59 | hide?: BaseModifier; 60 | applyStyle?: BaseModifier & { 61 | onLoad?: Function, 62 | gpuAcceleration?: boolean, 63 | }; 64 | computeStyle?: BaseModifier & { 65 | gpuAcceleration?: boolean; 66 | x?: 'bottom' | 'top', 67 | y?: 'left' | 'right' 68 | }; 69 | 70 | [name: string]: (BaseModifier & Record) | undefined; 71 | } 72 | 73 | export interface Offset { 74 | top: number; 75 | left: number; 76 | width: number; 77 | height: number; 78 | } 79 | 80 | export interface Data { 81 | instance: Popper; 82 | placement: Placement; 83 | originalPlacement: Placement; 84 | flipped: boolean; 85 | hide: boolean; 86 | arrowElement: Element; 87 | styles: CSSStyleDeclaration; 88 | boundaries: Object; 89 | offsets: { 90 | popper: Offset, 91 | reference: Offset, 92 | arrow: { 93 | top: number, 94 | left: number, 95 | }, 96 | }; 97 | } 98 | 99 | export interface PopperOptions { 100 | placement?: Placement; 101 | positionFixed?: boolean; 102 | eventsEnabled?: boolean; 103 | modifiers?: Modifiers; 104 | removeOnDestroy?: boolean; 105 | 106 | onCreate?(data: Data): void; 107 | 108 | onUpdate?(data: Data): void; 109 | } 110 | 111 | export interface ReferenceObject { 112 | clientHeight: number; 113 | clientWidth: number; 114 | 115 | getBoundingClientRect(): ClientRect; 116 | } 117 | 118 | declare class Popper { 119 | static modifiers: (BaseModifier & { name: string })[]; 120 | static placements: Placement[]; 121 | static Defaults: PopperOptions; 122 | 123 | options: PopperOptions; 124 | 125 | constructor(reference: Element | ReferenceObject, popper: Element, options?: PopperOptions); 126 | 127 | destroy(): void; 128 | 129 | update(): void; 130 | 131 | scheduleUpdate(): void; 132 | 133 | enableEventListeners(): void; 134 | 135 | disableEventListeners(): void; 136 | } 137 | 138 | export default Popper; 139 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/init-clip.js: -------------------------------------------------------------------------------- 1 | new ClipboardJS('#copy-button'); -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* NUGET: BEGIN LICENSE TEXT 16 | * 17 | * Microsoft grants you the right to use these script files for the sole 18 | * purpose of either: (i) interacting through your browser with the Microsoft 19 | * website or online service, subject to the applicable licensing or use 20 | * terms; or (ii) using the files as included with a Microsoft product subject 21 | * to that product's license terms. Microsoft reserves all other rights to the 22 | * files not expressly granted by Microsoft, whether by implication, estoppel 23 | * or otherwise. Insofar as a script file is dual licensed under GPL, 24 | * Microsoft neither took the code under GPL nor distributes it thereunder but 25 | * under the terms set out in this paragraph. All notices and licenses 26 | * below are for informational purposes only. 27 | * 28 | * NUGET: END LICENSE TEXT */ 29 | /* 30 | ** Unobtrusive validation support library for jQuery and jQuery Validate 31 | ** Copyright (C) Microsoft Corporation. All rights reserved. 32 | */ 33 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/popper-utils.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */function a(a,b){if(1!==a.nodeType)return[];const c=getComputedStyle(a,null);return b?c[b]:c}function b(a){return'HTML'===a.nodeName?a:a.parentNode||a.host}function c(d){if(!d)return document.body;switch(d.nodeName){case'HTML':case'BODY':return d.ownerDocument.body;case'#document':return d.body;}const{overflow:e,overflowX:f,overflowY:g}=a(d);return /(auto|scroll|overlay)/.test(e+g+f)?d:c(b(d))}const d={};var e=function(a='all'){return(a=a.toString(),d.hasOwnProperty(a))?d[a]:('11'===a?d[a]=-1!==navigator.userAgent.indexOf('Trident'):'10'===a?d[a]=-1!==navigator.appVersion.indexOf('MSIE 10'):'all'===a?d[a]=-1!==navigator.userAgent.indexOf('Trident')||-1!==navigator.userAgent.indexOf('MSIE'):void 0,d.all=d.all||Object.keys(d).some((a)=>d[a]),d[a])};function f(b){if(!b)return document.documentElement;const c=e(10)?document.body:null;let d=b.offsetParent;for(;d===c&&b.nextElementSibling;)d=(b=b.nextElementSibling).offsetParent;const g=d&&d.nodeName;return g&&'BODY'!==g&&'HTML'!==g?-1!==['TD','TABLE'].indexOf(d.nodeName)&&'static'===a(d,'position')?f(d):d:b?b.ownerDocument.documentElement:document.documentElement}function g(a){const{nodeName:b}=a;return'BODY'!==b&&('HTML'===b||f(a.firstElementChild)===a)}function h(a){return null===a.parentNode?a:h(a.parentNode)}function i(a,b){if(!a||!a.nodeType||!b||!b.nodeType)return document.documentElement;const c=a.compareDocumentPosition(b)&Node.DOCUMENT_POSITION_FOLLOWING,d=c?a:b,e=c?b:a,j=document.createRange();j.setStart(d,0),j.setEnd(e,0);const{commonAncestorContainer:k}=j;if(a!==k&&b!==k||d.contains(e))return g(k)?k:f(k);const l=h(a);return l.host?i(l.host,b):i(a,h(b).host)}function j(a,b='top'){const c='top'===b?'scrollTop':'scrollLeft',d=a.nodeName;if('BODY'===d||'HTML'===d){const b=a.ownerDocument.documentElement,d=a.ownerDocument.scrollingElement||b;return d[c]}return a[c]}function k(a,b,c=!1){const d=j(b,'top'),e=j(b,'left'),f=c?-1:1;return a.top+=d*f,a.bottom+=d*f,a.left+=e*f,a.right+=e*f,a}function l(a,b){const c='x'===b?'Left':'Top',d='Left'==c?'Right':'Bottom';return parseFloat(a[`border${c}Width`],10)+parseFloat(a[`border${d}Width`],10)}function m(a,b,c,d){return Math.max(b[`offset${a}`],b[`scroll${a}`],c[`client${a}`],c[`offset${a}`],c[`scroll${a}`],e(10)?c[`offset${a}`]+d[`margin${'Height'===a?'Top':'Left'}`]+d[`margin${'Height'===a?'Bottom':'Right'}`]:0)}function n(){const a=document.body,b=document.documentElement,c=e(10)&&getComputedStyle(b);return{height:m('Height',a,b,c),width:m('Width',a,b,c)}}var o=Object.assign||function(a){for(var b,c=1;co({key:a},h[a],{area:w(h[a])})).sort((c,a)=>a.area-c.area),j=i.filter(({width:a,height:b})=>a>=c.clientWidth&&b>=c.clientHeight),k=0{b||(b=!0,window.Promise.resolve().then(()=>{b=!1,a()}))}}function C(a){let b=!1;return()=>{b||(b=!0,setTimeout(()=>{b=!1,a()},A))}}const D=y&&window.Promise;var E=D?B:C;function F(a,b){return Array.prototype.find?a.find(b):a.filter(b)[0]}function G(a,b,c){if(Array.prototype.findIndex)return a.findIndex((a)=>a[b]===c);const d=F(a,(a)=>a[b]===c);return a.indexOf(d)}function H(a){let b;if('HTML'===a.nodeName){const{width:a,height:c}=n();b={width:a,height:c,left:0,top:0}}else b={width:a.offsetWidth,height:a.offsetHeight,left:a.offsetLeft,top:a.offsetTop};return p(b)}function I(a){const b=getComputedStyle(a),c=parseFloat(b.marginTop)+parseFloat(b.marginBottom),d=parseFloat(b.marginLeft)+parseFloat(b.marginRight),e={width:a.offsetWidth+d,height:a.offsetHeight+c};return e}function J(a){const b={left:'right',right:'left',bottom:'top',top:'bottom'};return a.replace(/left|right|bottom|top/g,(a)=>b[a])}function K(a,b,c){c=c.split('-')[0];const d=I(a),e={width:d.width,height:d.height},f=-1!==['right','left'].indexOf(c),g=f?'top':'left',h=f?'left':'top',i=f?'height':'width',j=f?'width':'height';return e[g]=b[g]+b[i]/2-d[i]/2,e[h]=c===h?b[h]-d[j]:b[J(h)],e}function L(a,b,c,d=null){const e=d?u(b):i(b,c);return r(c,e,d)}function M(a){const b=[!1,'ms','Webkit','Moz','O'],c=a.charAt(0).toUpperCase()+a.slice(1);for(let d=0;dc&&a===b)}function P(a,b,c){const d=F(a,({name:a})=>a===b),e=!!d&&a.some((a)=>a.name===c&&a.enabled&&a.order{a.removeEventListener('scroll',b.updateBound)}),b.updateBound=null,b.scrollParents=[],b.scrollElement=null,b.eventsEnabled=!1,b}function T(a,b,c){const d=void 0===c?a:a.slice(0,G(a,'name',c));return d.forEach((a)=>{a['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');const c=a['function']||a.fn;a.enabled&&N(c)&&(b.offsets.popper=p(b.offsets.popper),b.offsets.reference=p(b.offsets.reference),b=c(b,a))}),b}function U(a,b){Object.keys(b).forEach(function(c){const d=b[c];!1===d?a.removeAttribute(c):a.setAttribute(c,b[c])})}function V(a,b){Object.keys(b).forEach((c)=>{let d='';-1!==['width','height','top','right','bottom','left'].indexOf(c)&&Q(b[c])&&(d='px'),a.style[c]=b[c]+d})}function W(a,b,d,e){const f='BODY'===a.nodeName,g=f?a.ownerDocument.defaultView:a;g.addEventListener(b,d,{passive:!0}),f||W(c(g.parentNode),b,d,e),e.push(g)}function X(a,b,d,e){d.updateBound=e,R(a).addEventListener('resize',d.updateBound,{passive:!0});const f=c(a);return W(f,'scroll',d.updateBound,d.scrollParents),d.scrollElement=f,d.eventsEnabled=!0,d}var Y={computeAutoPlacement:x,debounce:E,findIndex:G,getBordersSize:l,getBoundaries:v,getBoundingClientRect:q,getClientRect:p,getOffsetParent:f,getOffsetRect:H,getOffsetRectRelativeToArbitraryNode:r,getOuterSizes:I,getParentNode:b,getPopperOffsets:K,getReferenceOffsets:L,getScroll:j,getScrollParent:c,getStyleComputedProperty:a,getSupportedPropertyName:M,getWindowSizes:n,isFixed:t,isFunction:N,isModifierEnabled:O,isModifierRequired:P,isNumeric:Q,removeEventListeners:S,runModifiers:T,setAttributes:U,setStyles:V,setupEventListeners:X};export{x as computeAutoPlacement,E as debounce,G as findIndex,l as getBordersSize,v as getBoundaries,q as getBoundingClientRect,p as getClientRect,f as getOffsetParent,H as getOffsetRect,r as getOffsetRectRelativeToArbitraryNode,I as getOuterSizes,b as getParentNode,K as getPopperOffsets,L as getReferenceOffsets,j as getScroll,c as getScrollParent,a as getStyleComputedProperty,M as getSupportedPropertyName,n as getWindowSizes,t as isFixed,N as isFunction,O as isModifierEnabled,P as isModifierRequired,Q as isNumeric,S as removeEventListeners,T as runModifiers,U as setAttributes,V as setStyles,X as setupEventListeners};export default Y; 5 | //# sourceMappingURL=popper-utils.min.js.map 6 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Scripts/umd/popper-utils.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b(exports):'function'==typeof define&&define.amd?define(['exports'],b):b(a.PopperUtils={})})(this,function(a){'use strict';function b(a,b){if(1!==a.nodeType)return[];var c=getComputedStyle(a,null);return b?c[b]:c}function c(a){return'HTML'===a.nodeName?a:a.parentNode||a.host}function d(a){if(!a)return document.body;switch(a.nodeName){case'HTML':case'BODY':return a.ownerDocument.body;case'#document':return a.body;}var e=b(a),f=e.overflow,g=e.overflowX,h=e.overflowY;return /(auto|scroll|overlay)/.test(f+h+g)?a:d(c(a))}function e(a){if(!a)return document.documentElement;for(var c=S(10)?document.body:null,d=a.offsetParent;d===c&&a.nextElementSibling;)d=(a=a.nextElementSibling).offsetParent;var f=d&&d.nodeName;return f&&'BODY'!==f&&'HTML'!==f?-1!==['TD','TABLE'].indexOf(d.nodeName)&&'static'===b(d,'position')?e(d):d:a?a.ownerDocument.documentElement:document.documentElement}function f(a){var b=a.nodeName;return'BODY'!==b&&('HTML'===b||e(a.firstElementChild)===a)}function g(a){return null===a.parentNode?a:g(a.parentNode)}function h(a,b){if(!a||!a.nodeType||!b||!b.nodeType)return document.documentElement;var c=a.compareDocumentPosition(b)&Node.DOCUMENT_POSITION_FOLLOWING,d=c?a:b,i=c?b:a,j=document.createRange();j.setStart(d,0),j.setEnd(i,0);var k=j.commonAncestorContainer;if(a!==k&&b!==k||d.contains(i))return f(k)?k:e(k);var l=g(a);return l.host?h(l.host,b):h(a,g(b).host)}function j(a){var b=1=c.clientWidth&&d>=c.clientHeight}),k=0 7 |
    8 |
    9 | 10 | @UIMessages.AuthNError 11 | 12 |
    13 |
    14 |

    @Request.Params["message"]

    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Home Page"; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Home/LogOut.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Configuration 2 | @{ 3 | ViewBag.Title = "Logged out"; 4 | } 5 | 6 |
    7 |
    8 |
    9 |
    10 | @UIMessages.LoggedOut 11 |
    12 |
    13 |
    14 |
    -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Lap/Get.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Request Password"; 3 | } 4 | 5 | @model Lithnet.Laps.Web.Models.LapRequestModel 6 | 7 |
    8 | @using (Html.BeginForm("Get", "Lap", FormMethod.Post)) 9 | { 10 |
    11 | @UIMessages.HeadingRequestPassword 12 |
    13 |
    14 | 15 |
    16 | 17 | 22 |
    23 | 24 | @if (Model?.FailureReason != null) 25 | { 26 |
    27 | 28 | @Html.DisplayTextFor(model => model.FailureReason) 29 | 30 |
    31 | } 32 | 33 |
    34 | 35 |
    36 |
    37 | @Html.AntiForgeryToken() 38 | 39 |
    40 |
    41 | } 42 |
    -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Lap/RateLimitExceeded.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Configuration 2 | @{ 3 | ViewBag.Title = "Too many requests"; 4 | } 5 | 6 |
    7 |
    8 |
    9 | 10 | @UIMessages.RateLimitError 11 | 12 |
    13 |
    14 |
    -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Lap/Show.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Password"; 3 | } 4 | 5 | @model Lithnet.Laps.Web.Models.LapEntryModel 6 | 7 | 8 | 9 |
    10 | 11 |
    12 | @UIMessages.HeadingPasswordDetails 13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 | 22 |
    23 | 24 |
    25 |
    26 | @Html.Raw(Model.HtmlPassword) 27 |
    28 | 29 | 32 | 33 |
    34 |
    35 | 36 |
    37 | 38 |
    39 | 40 |
    41 |
    42 |
    43 | 44 |
    45 |
    46 | @using (Html.BeginForm("Get", "Lap", FormMethod.Get)) 47 | { 48 | 49 | } 50 |
    51 |
    52 |
    -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Configuration 2 | @{ 3 | ViewBag.Title = "Error"; 4 | } 5 | 6 |
    7 |
    8 |
    9 | 10 | @UIMessages.UnexpectedError 11 | 12 |
    13 |
    14 |

    @ViewBag.Message

    15 |
    16 |
    17 |
    -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Configuration 2 | 3 | 4 | 5 | 6 | 7 | @ConfigurationManager.AppSettings["app:Title"] 8 | @Styles.Render("~/Content/css") 9 | 10 | 11 | 12 | 13 |
    14 |
    15 | 16 |
    @ConfigurationManager.AppSettings["app:Title"]
    17 |
    18 | @RenderBody() 19 |
    20 | @if (MvcApplication.CanLogout) 21 | { 22 |
    23 | Logout 24 |
    25 | } 26 | 27 | @Scripts.Render("~/bundles/jquery") 28 | @Scripts.Render("~/bundles/bootstrap") 29 | @RenderSection("scripts", required: false) 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/Web.config: -------------------------------------------------------------------------------- 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 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 |
    8 |
    9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 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 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.0 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])}); -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/favicon.ico -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lithnet/laps-web/0e478beeee01cdf6b542d3900634a8c1a7c2446f/src/Lithnet.Laps.Web/Lithnet.Laps.Web/images/logo.png -------------------------------------------------------------------------------- /src/Lithnet.Laps.Web/Lithnet.Laps.Web/packages.config: -------------------------------------------------------------------------------- 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 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | --------------------------------------------------------------------------------