├── src └── IndentityServer │ ├── Views │ ├── _ViewStart.cshtml │ ├── _ViewImports.cshtml │ ├── Device │ │ ├── Success.cshtml │ │ ├── UserCodeCapture.cshtml │ │ └── UserCodeConfirmation.cshtml │ ├── Account │ │ ├── AccessDenied.cshtml │ │ ├── Logout.cshtml │ │ ├── LoggedOut.cshtml │ │ └── Login.cshtml │ ├── Shared │ │ ├── _ValidationSummary.cshtml │ │ ├── Redirect.cshtml │ │ ├── _ScopeListItem.cshtml │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── Diagnostics │ │ └── Index.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Grants │ │ └── Index.cshtml │ └── Consent │ │ └── Index.cshtml │ ├── wwwroot │ ├── icon.jpg │ ├── icon.png │ ├── favicon.ico │ ├── js │ │ ├── signin-redirect.js │ │ └── signout-redirect.js │ ├── lib │ │ └── bootstrap │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── js │ │ │ └── bootstrap.min.js │ └── css │ │ ├── site.min.css │ │ ├── site.css │ │ └── site.less │ ├── Quickstart │ ├── Account │ │ ├── LogoutInputModel.cs │ │ ├── RedirectViewModel.cs │ │ ├── LogoutViewModel.cs │ │ ├── ExternalProvider.cs │ │ ├── LoginInputModel.cs │ │ ├── LoggedOutViewModel.cs │ │ ├── LoginViewModel.cs │ │ ├── AccountOptions.cs │ │ ├── ExternalController.cs │ │ └── AccountController.cs │ ├── Device │ │ ├── DeviceAuthorizationInputModel.cs │ │ ├── DeviceAuthorizationViewModel.cs │ │ └── DeviceController.cs │ ├── Consent │ │ ├── ConsentInputModel.cs │ │ ├── ScopeViewModel.cs │ │ ├── ProcessConsentResult.cs │ │ ├── ConsentViewModel.cs │ │ ├── ConsentOptions.cs │ │ └── ConsentController.cs │ ├── Home │ │ ├── ErrorViewModel.cs │ │ └── HomeController.cs │ ├── Extensions.cs │ ├── Grants │ │ ├── GrantsViewModel.cs │ │ └── GrantsController.cs │ ├── Diagnostics │ │ ├── DiagnosticsController.cs │ │ └── DiagnosticsViewModel.cs │ ├── TestUsers.cs │ └── SecurityHeadersAttribute.cs │ ├── IndentityServer.csproj │ ├── Properties │ └── launchSettings.json │ ├── tempkey.rsa │ ├── Program.cs │ ├── Startup.cs │ └── Config.cs ├── idaas.sln ├── README.md └── .gitignore /src/IndentityServer/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using IndentityServer 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/icon.jpg -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/icon.png -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/js/signin-redirect.js: -------------------------------------------------------------------------------- 1 | window.location.href = document.querySelector("meta[http-equiv=refresh]").getAttribute("data-url"); 2 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Device/Success.cshtml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/js/signout-redirect.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var a = document.querySelector("a.PostLogoutRedirectUri"); 3 | if (a) { 4 | window.location = a.href; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpikpl/identity-as-a-service/HEAD/src/IndentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/IndentityServer/Views/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | 2 |
3 | 6 | 7 |

You do not have access to that resource.

8 |
-------------------------------------------------------------------------------- /src/IndentityServer/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
4 | Error 5 |
6 |
7 | } -------------------------------------------------------------------------------- /src/IndentityServer/Views/Shared/Redirect.cshtml: -------------------------------------------------------------------------------- 1 | @model RedirectViewModel 2 | 3 |

You are now being returned to the application.

4 |

Once complete, you may close this tab

5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/LogoutInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class LogoutInputModel 8 | { 9 | public string LogoutId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/RedirectViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | 6 | namespace IndentityServer 7 | { 8 | public class RedirectViewModel 9 | { 10 | public string RedirectUrl { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/LogoutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class LogoutViewModel : LogoutInputModel 8 | { 9 | public bool ShowLogoutPrompt { get; set; } = true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Device/UserCodeCapture.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Device/DeviceAuthorizationInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer.Device 6 | { 7 | public class DeviceAuthorizationInputModel : ConsentInputModel 8 | { 9 | public string UserCode { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/ExternalProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class ExternalProvider 8 | { 9 | public string DisplayName { get; set; } 10 | public string AuthenticationScheme { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Device/DeviceAuthorizationViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer.Device 6 | { 7 | public class DeviceAuthorizationViewModel : ConsentViewModel 8 | { 9 | public string UserCode { get; set; } 10 | public bool ConfirmUserCode { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ConsentInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IndentityServer 8 | { 9 | public class ConsentInputModel 10 | { 11 | public string Button { get; set; } 12 | public IEnumerable ScopesConsented { get; set; } 13 | public bool RememberConsent { get; set; } 14 | public string ReturnUrl { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ScopeViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class ScopeViewModel 8 | { 9 | public string Name { get; set; } 10 | public string DisplayName { get; set; } 11 | public string Description { get; set; } 12 | public bool Emphasize { get; set; } 13 | public bool Required { get; set; } 14 | public bool Checked { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/LoginInputModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace IndentityServer 8 | { 9 | public class LoginInputModel 10 | { 11 | [Required] 12 | public string Username { get; set; } 13 | [Required] 14 | public string Password { get; set; } 15 | public bool RememberLogin { get; set; } 16 | public string ReturnUrl { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Home/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Models; 6 | 7 | namespace IndentityServer 8 | { 9 | public class ErrorViewModel 10 | { 11 | public ErrorViewModel() 12 | { 13 | } 14 | 15 | public ErrorViewModel(string error) 16 | { 17 | Error = new ErrorMessage { Error = error }; 18 | } 19 | 20 | public ErrorMessage Error { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/IndentityServer/IndentityServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @model LogoutViewModel 2 | 3 |
4 | 7 | 8 |
9 |
10 |

Would you like to logout of IdentityServer?

11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/IndentityServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "SelfHost": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "http://localhost:5000" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/IndentityServer/Views/Diagnostics/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model DiagnosticsViewModel 2 | 3 |

Authentication cookie

4 | 5 |

Claims

6 |
7 | @foreach (var claim in Model.AuthenticateResult.Principal.Claims) 8 | { 9 |
@claim.Type
10 |
@claim.Value
11 | } 12 |
13 | 14 |

Properties

15 |
16 | @foreach (var prop in Model.AuthenticateResult.Properties.Items) 17 | { 18 |
@prop.Key
19 |
@prop.Value
20 | } 21 |
22 | 23 | @if (Model.Clients.Any()) 24 | { 25 |

Clients

26 | 32 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ProcessConsentResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class ProcessConsentResult 8 | { 9 | public bool IsRedirect => RedirectUri != null; 10 | public string RedirectUri { get; set; } 11 | public string ClientId { get; set; } 12 | 13 | public bool ShowView => ViewModel != null; 14 | public ConsentViewModel ViewModel { get; set; } 15 | 16 | public bool HasValidationError => ValidationError != null; 17 | public string ValidationError { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ConsentViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace IndentityServer 8 | { 9 | public class ConsentViewModel : ConsentInputModel 10 | { 11 | public string ClientName { get; set; } 12 | public string ClientUrl { get; set; } 13 | public string ClientLogoUrl { get; set; } 14 | public bool AllowRememberConsent { get; set; } 15 | 16 | public IEnumerable IdentityScopes { get; set; } 17 | public IEnumerable ResourceScopes { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/LoggedOutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class LoggedOutViewModel 8 | { 9 | public string PostLogoutRedirectUri { get; set; } 10 | public string ClientName { get; set; } 11 | public string SignOutIframeUrl { get; set; } 12 | 13 | public bool AutomaticRedirectAfterSignOut { get; set; } 14 | 15 | public string LogoutId { get; set; } 16 | public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; 17 | public string ExternalAuthenticationScheme { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ConsentOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | namespace IndentityServer 6 | { 7 | public class ConsentOptions 8 | { 9 | public static bool EnableOfflineAccess = true; 10 | public static string OfflineAccessDisplayName = "Offline Access"; 11 | public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; 12 | 13 | public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; 14 | public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using IdentityServer4.Stores; 3 | 4 | namespace IndentityServer 5 | { 6 | public static class Extensions 7 | { 8 | /// 9 | /// Determines whether the client is configured to use PKCE. 10 | /// 11 | /// The store. 12 | /// The client identifier. 13 | /// 14 | public static async Task IsPkceClientAsync(this IClientStore store, string client_id) 15 | { 16 | if (!string.IsNullOrWhiteSpace(client_id)) 17 | { 18 | var client = await store.FindEnabledClientByIdAsync(client_id); 19 | return client?.RequirePkce == true; 20 | } 21 | 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Grants/GrantsViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace IndentityServer 9 | { 10 | public class GrantsViewModel 11 | { 12 | public IEnumerable Grants { get; set; } 13 | } 14 | 15 | public class GrantViewModel 16 | { 17 | public string ClientId { get; set; } 18 | public string ClientName { get; set; } 19 | public string ClientUrl { get; set; } 20 | public string ClientLogoUrl { get; set; } 21 | public DateTime Created { get; set; } 22 | public DateTime? Expires { get; set; } 23 | public IEnumerable IdentityGrantNames { get; set; } 24 | public IEnumerable ApiGrantNames { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/IndentityServer/Views/Account/LoggedOut.cshtml: -------------------------------------------------------------------------------- 1 | @model LoggedOutViewModel 2 | 3 | @{ 4 | // set this so the layout rendering sees an anonymous user 5 | ViewData["signed-out"] = true; 6 | } 7 | 8 | 27 | 28 | @section scripts 29 | { 30 | @if (Model.AutomaticRedirectAfterSignOut) 31 | { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace IndentityServer 10 | { 11 | public class LoginViewModel : LoginInputModel 12 | { 13 | public bool AllowRememberLogin { get; set; } = true; 14 | public bool EnableLocalLogin { get; set; } = true; 15 | 16 | public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); 17 | public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); 18 | 19 | public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; 20 | public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; 21 | } 22 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Diagnostics/DiagnosticsController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Mvc; 10 | 11 | namespace IndentityServer 12 | { 13 | [SecurityHeaders] 14 | [Authorize] 15 | public class DiagnosticsController : Controller 16 | { 17 | public async Task Index() 18 | { 19 | var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; 20 | if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) 21 | { 22 | return NotFound(); 23 | } 24 | 25 | var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); 26 | return View(model); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/IndentityServer/Views/Shared/_ScopeListItem.cshtml: -------------------------------------------------------------------------------- 1 | @model ScopeViewModel 2 | 3 |
  • 4 | 24 | @if (Model.Required) 25 | { 26 | (required) 27 | } 28 | @if (Model.Description != null) 29 | { 30 | 33 | } 34 |
  • -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/AccountOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | 7 | namespace IndentityServer 8 | { 9 | public class AccountOptions 10 | { 11 | public static bool AllowLocalLogin = true; 12 | public static bool AllowRememberLogin = true; 13 | public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); 14 | 15 | public static bool ShowLogoutPrompt = true; 16 | public static bool AutomaticRedirectAfterSignOut = false; 17 | 18 | // specify the Windows authentication scheme being used 19 | public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; 20 | // if user uses windows auth, should we load the groups from windows 21 | public static bool IncludeWindowsGroups = false; 22 | 23 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | 3 | @{ 4 | var error = Model?.Error?.Error; 5 | var errorDescription = Model?.Error?.ErrorDescription; 6 | var request_id = Model?.Error?.RequestId; 7 | } 8 | 9 |
    10 | 13 | 14 |
    15 |
    16 |
    17 | Sorry, there was an error 18 | 19 | @if (error != null) 20 | { 21 | 22 | 23 | : @error 24 | 25 | 26 | 27 | if (errorDescription != null) 28 | { 29 |
    @errorDescription
    30 | } 31 | } 32 |
    33 | 34 | @if (request_id != null) 35 | { 36 |
    Request Id: @request_id
    37 | } 38 |
    39 |
    40 |
    41 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Newtonsoft.Json; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | 11 | namespace IndentityServer 12 | { 13 | public class DiagnosticsViewModel 14 | { 15 | public DiagnosticsViewModel(AuthenticateResult result) 16 | { 17 | AuthenticateResult = result; 18 | 19 | if (result.Properties.Items.ContainsKey("client_list")) 20 | { 21 | var encoded = result.Properties.Items["client_list"]; 22 | var bytes = Base64Url.Decode(encoded); 23 | var value = Encoding.UTF8.GetString(bytes); 24 | 25 | Clients = JsonConvert.DeserializeObject(value); 26 | } 27 | } 28 | 29 | public AuthenticateResult AuthenticateResult { get; } 30 | public IEnumerable Clients { get; } = new List(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{margin-top:65px;}.navbar-header{position:relative;top:-4px;}.navbar-brand>.icon-banner{position:relative;top:-2px;display:inline;}.icon{position:relative;top:-10px;}.logged-out iframe{display:none;width:0;height:0;}.page-consent .client-logo{float:left;}.page-consent .client-logo img{width:80px;height:80px;}.page-consent .consent-buttons{margin-top:25px;}.page-consent .consent-form .consent-scopecheck{display:inline-block;margin-right:5px;}.page-consent .consent-form .consent-description{margin-left:25px;}.page-consent .consent-form .consent-description label{font-weight:normal;}.page-consent .consent-form .consent-remember{padding-left:16px;}.grants .page-header{margin-bottom:10px;}.grants .grant{margin-top:20px;padding-bottom:20px;border-bottom:1px solid #d3d3d3;}.grants .grant img{width:100px;height:100px;}.grants .grant .clientname{font-size:140%;font-weight:bold;}.grants .grant .granttype{font-size:120%;font-weight:bold;}.grants .grant .created{font-size:120%;font-weight:bold;}.grants .grant .expires{font-size:120%;font-weight:bold;}.grants .grant li{list-style-type:none;display:inline;}.grants .grant li:after{content:', ';}.grants .grant li:last-child:after{content:'';} -------------------------------------------------------------------------------- /src/IndentityServer/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var version = typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.GetName().Version.ToString(); 3 | } 4 | 5 |
    6 | 15 | 16 |
    17 |
    18 |

    19 | IdentityServer publishes a 20 | discovery document 21 | where you can find metadata and links to all the endpoints, key material, etc. 22 |

    23 |
    24 |
    25 |

    26 | Click here to manage your stored grants. 27 |

    28 |
    29 |
    30 |
    31 |
    32 |

    33 | Here are links to the 34 | source code repository, 35 | and ready to use samples. 36 |

    37 |
    38 |
    39 |
    40 | -------------------------------------------------------------------------------- /src/IndentityServer/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"9e1dd5a1c0ce622b50e6d0f1bc506edf","Parameters":{"D":"EAe0VnNwMe/tefhUykFSRHz7yfNx6FN8HPFsLIslO6A/zid7JCcawDsEb8RSftQs7eV3qXpMSB0e8rk75OzRSXk+kEtY6jJZIDHFf3wdoCVhNJYYE4KezJCUHAlfmO3gyWjQskQgrJYVF0gz4r0rsWchD16joWmeIw3am1MY2RJjgudWG/0dueHDUDivPTITtXntQAp5sshPGnPQEviy7Wu3ikPzeWJmY1h5UgU3GULmQi2z762mojsOYxKYQt9FhAsMJg59398xyBAsDY7OJXVjuW4DPHrP1QWnzuBS1MhxOYOWXl3CprIgWLt9XjAnobLq6s/qMiFOWXnHzORXDQ==","DP":"vNjj7FF0sQ9MiMGGo5QknAHPQTuo10pTDL2POJ3XZlC5oDCL/xCKhaUxiRwcW9poE1JAzycpBUx1EbsIB51nHpWA0LI3B+r6EPO0ZmK32r9k1x12J5FiasVp1hHNTqmSVZJsxnZZ0OP8hVGwLUeAD7/vsixRZXI/vkQ3S4oE6DE=","DQ":"EEn0Kqk3lGyq3M98kfD4KA53B+vEvmXeQuMEunB5XMF2+rOzgfhTKNMtCGyEwTHzEcOi4LqQYnLNkEl99o7x++BQzTI2Gn4nARvrDtdb1A/XpAxjXL+c0Yt70NUo1OgFFtaVT4HMX/XiZEUe3l5VpuC89L1Z8ju0GSweRNjt2ik=","Exponent":"AQAB","InverseQ":"DoN/j39Qjytpx2YkFBiaA1owz63rcaM8HUgp6YaspHl8CB9DmGpLT2gJzkDhSayeZkn66FybS9Z6rmordAumdI8nQ3dSy9IF6aBvrntXiWifL/pNyOAQobdBbViYT2NSDhabgMScN5V7WZX2OdOr7AbngeDi+7FJZIHAOHxwScw=","Modulus":"oGXK/JS+1HQtm9YDSVulgnyig8koVkbmDeBxbLgLcW9hkDUM7sheK4M56Az8k37kKghJ1I4Q9ar41N3VijRnAGbf2A0aD9wDz//A4xJJWqYvgc4EG6kgByrasApN472BKMyn1g3uf4G9yK+eFrD571K2kXHdoFkY5PZXp/fMffLkekZZ+I9J2EHopD1u0SsYtYfABrHNv4Hmv1DDwoO/GngLAbN6S+qAkJZHHsuNQS8MXBKhrAsFAfLaURrDR1i72Yrakmifm2mibfcuaD972CzSPotVkdUzDuUiWUiSgv69gbYlDc+1WbJHVahFkVDTNpJ2gv5HM6YnKCkeZ2oHeQ==","P":"1ckiiFQO8Jx+ZkOZ7UNN2JV6nBe/IhBIjW/S9UAVhvFxQtFN4QfOtoEwuSYSQQQac4yM39M/9lTFmoGONNc5Kvwfp1xQIvXoq0gjWZ8q4ejBiWTK3pXKq8lCC4x56JNz1exgZ3QxxV80IfQIIchphiEA6VLrgd1RogXcllrQDq0=","Q":"wBHkZHlVGhTJQcoU4oAkhsylvZ5iTJRXu4WlykHyypvam2/XuojB31AjK9QFa/8XSwSLHgFXSKUUIdtBf8/BHsgdXRZaTzwJ6jXOMsbqsZdWrexCYNzKWQBWyoYEs3zp7kxIIjo2zMsjv9zFS96nWES2phsFyM3m+91WXERK8X0="}} -------------------------------------------------------------------------------- /src/IndentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Serilog; 8 | using Serilog.Events; 9 | using Serilog.Sinks.SystemConsole.Themes; 10 | using System; 11 | 12 | namespace IndentityServer 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | Console.Title = "IdentityServer4"; 19 | 20 | CreateWebHostBuilder(args).Build().Run(); 21 | } 22 | 23 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) 24 | { 25 | return WebHost.CreateDefaultBuilder(args) 26 | .UseStartup() 27 | .UseSerilog((context, configuration) => 28 | { 29 | configuration 30 | .MinimumLevel.Debug() 31 | .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) 32 | .MinimumLevel.Override("System", LogEventLevel.Warning) 33 | .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) 34 | .Enrich.FromLogContext() 35 | .WriteTo.File(@"identityserver4_log.txt") 36 | .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate); 37 | }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | .navbar-header { 5 | position: relative; 6 | top: -4px; 7 | } 8 | .navbar-brand > .icon-banner { 9 | position: relative; 10 | top: -2px; 11 | display: inline; 12 | } 13 | .icon { 14 | position: relative; 15 | top: -10px; 16 | } 17 | .logged-out iframe { 18 | display: none; 19 | width: 0; 20 | height: 0; 21 | } 22 | .page-consent .client-logo { 23 | float: left; 24 | } 25 | .page-consent .client-logo img { 26 | width: 80px; 27 | height: 80px; 28 | } 29 | .page-consent .consent-buttons { 30 | margin-top: 25px; 31 | } 32 | .page-consent .consent-form .consent-scopecheck { 33 | display: inline-block; 34 | margin-right: 5px; 35 | } 36 | .page-consent .consent-form .consent-description { 37 | margin-left: 25px; 38 | } 39 | .page-consent .consent-form .consent-description label { 40 | font-weight: normal; 41 | } 42 | .page-consent .consent-form .consent-remember { 43 | padding-left: 16px; 44 | } 45 | .grants .page-header { 46 | margin-bottom: 10px; 47 | } 48 | .grants .grant { 49 | margin-top: 20px; 50 | padding-bottom: 20px; 51 | border-bottom: 1px solid lightgray; 52 | } 53 | .grants .grant img { 54 | width: 100px; 55 | height: 100px; 56 | } 57 | .grants .grant .clientname { 58 | font-size: 140%; 59 | font-weight: bold; 60 | } 61 | .grants .grant .granttype { 62 | font-size: 120%; 63 | font-weight: bold; 64 | } 65 | .grants .grant .created { 66 | font-size: 120%; 67 | font-weight: bold; 68 | } 69 | .grants .grant .expires { 70 | font-size: 120%; 71 | font-weight: bold; 72 | } 73 | .grants .grant li { 74 | list-style-type: none; 75 | display: inline; 76 | } 77 | .grants .grant li:after { 78 | content: ', '; 79 | } 80 | .grants .grant li:last-child:after { 81 | content: ''; 82 | } -------------------------------------------------------------------------------- /idaas.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09B38C47-1985-4010-8677-8294060DAA38}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IndentityServer", "src\IndentityServer\IndentityServer.csproj", "{C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|x64.Build.0 = Debug|Any CPU 27 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Debug|x86.Build.0 = Debug|Any CPU 29 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|x64.ActiveCfg = Release|Any CPU 32 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|x64.Build.0 = Release|Any CPU 33 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|x86.ActiveCfg = Release|Any CPU 34 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B}.Release|x86.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(NestedProjects) = preSolution 37 | {C20FCB20-C1A0-42FC-A6ED-7EFF8DA4E91B} = {09B38C47-1985-4010-8677-8294060DAA38} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Home/HomeController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Logging; 10 | using System.Threading.Tasks; 11 | 12 | namespace IndentityServer 13 | { 14 | [SecurityHeaders] 15 | [AllowAnonymous] 16 | public class HomeController : Controller 17 | { 18 | private readonly IIdentityServerInteractionService _interaction; 19 | private readonly IHostingEnvironment _environment; 20 | private readonly ILogger _logger; 21 | 22 | public HomeController(IIdentityServerInteractionService interaction, IHostingEnvironment environment, ILogger logger) 23 | { 24 | _interaction = interaction; 25 | _environment = environment; 26 | _logger = logger; 27 | } 28 | 29 | public IActionResult Index() 30 | { 31 | if (_environment.IsDevelopment()) 32 | { 33 | // only show in development 34 | return View(); 35 | } 36 | 37 | _logger.LogInformation("Homepage is disabled in production. Returning 404."); 38 | return NotFound(); 39 | } 40 | 41 | /// 42 | /// Shows the error page 43 | /// 44 | public async Task Error(string errorId) 45 | { 46 | var vm = new ErrorViewModel(); 47 | 48 | // retrieve error details from identityserver 49 | var message = await _interaction.GetErrorContextAsync(errorId); 50 | if (message != null) 51 | { 52 | vm.Error = message; 53 | 54 | if (!_environment.IsDevelopment()) 55 | { 56 | // only show in development 57 | message.ErrorDescription = null; 58 | } 59 | } 60 | 61 | return View("Error", vm); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/TestUsers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Test; 7 | using System.Collections.Generic; 8 | using System.Security.Claims; 9 | 10 | namespace IndentityServer 11 | { 12 | public class TestUsers 13 | { 14 | public static List Users = new List 15 | { 16 | new TestUser{SubjectId = "818727", Username = "alice", Password = "alice", 17 | Claims = 18 | { 19 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 20 | new Claim(JwtClaimTypes.GivenName, "Alice"), 21 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 22 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 23 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 24 | new Claim(JwtClaimTypes.WebSite, "http://alice.com"), 25 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) 26 | } 27 | }, 28 | new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob", 29 | Claims = 30 | { 31 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 32 | new Claim(JwtClaimTypes.GivenName, "Bob"), 33 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 34 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 35 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 36 | new Claim(JwtClaimTypes.WebSite, "http://bob.com"), 37 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 38 | new Claim("location", "somewhere") 39 | } 40 | } 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /src/IndentityServer/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using IdentityServer4.Services; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace IndentityServer 13 | { 14 | public class Startup 15 | { 16 | public IHostingEnvironment Environment { get; } 17 | 18 | public Startup(IHostingEnvironment environment) 19 | { 20 | Environment = environment; 21 | } 22 | 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | // uncomment, if you want to add an MVC-based UI 26 | services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); 27 | 28 | var builder = services.AddIdentityServer() 29 | .AddInMemoryIdentityResources(Config.GetIdentityResources()) 30 | .AddInMemoryApiResources(Config.GetApis()) 31 | .AddInMemoryClients(Config.GetClients()) 32 | .AddTestUsers(Config.GetTestUsers()); 33 | 34 | if (Environment.IsDevelopment()) 35 | { 36 | builder.AddDeveloperSigningCredential(); 37 | } 38 | else 39 | { 40 | throw new Exception("need to configure key material"); 41 | } 42 | 43 | var cors = new DefaultCorsPolicyService(new LoggerFactory().CreateLogger()) 44 | { 45 | AllowAll = true 46 | }; 47 | services.AddSingleton(cors); 48 | } 49 | 50 | public void Configure(IApplicationBuilder app) 51 | { 52 | if (Environment.IsDevelopment()) 53 | { 54 | app.UseDeveloperExceptionPage(); 55 | } 56 | 57 | // uncomment if you want to support static files 58 | app.UseStaticFiles(); 59 | 60 | app.UseIdentityServer(); 61 | 62 | // uncomment, if you want to add an MVC-based UI 63 | app.UseMvcWithDefaultRoute(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/css/site.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | 5 | .navbar-header { 6 | position: relative; 7 | top: -4px; 8 | } 9 | 10 | .navbar-brand > .icon-banner { 11 | position: relative; 12 | top: -2px; 13 | display: inline; 14 | } 15 | 16 | .icon { 17 | position: relative; 18 | top: -10px; 19 | } 20 | 21 | .logged-out iframe { 22 | display: none; 23 | width: 0; 24 | height: 0; 25 | } 26 | 27 | .page-consent { 28 | .client-logo { 29 | float: left; 30 | 31 | img { 32 | width: 80px; 33 | height: 80px; 34 | } 35 | } 36 | 37 | .consent-buttons { 38 | margin-top: 25px; 39 | } 40 | 41 | .consent-form { 42 | .consent-scopecheck { 43 | display: inline-block; 44 | margin-right: 5px; 45 | } 46 | 47 | .consent-scopecheck[disabled] { 48 | //visibility:hidden; 49 | } 50 | 51 | .consent-description { 52 | margin-left: 25px; 53 | 54 | label { 55 | font-weight: normal; 56 | } 57 | } 58 | 59 | .consent-remember { 60 | padding-left: 16px; 61 | } 62 | } 63 | } 64 | 65 | .grants { 66 | .page-header { 67 | margin-bottom: 10px; 68 | } 69 | 70 | .grant { 71 | margin-top: 20px; 72 | padding-bottom: 20px; 73 | border-bottom: 1px solid lightgray; 74 | 75 | img { 76 | width: 100px; 77 | height: 100px; 78 | } 79 | 80 | .clientname { 81 | font-size: 140%; 82 | font-weight: bold; 83 | } 84 | 85 | .granttype { 86 | font-size: 120%; 87 | font-weight: bold; 88 | } 89 | 90 | .created { 91 | font-size: 120%; 92 | font-weight: bold; 93 | } 94 | 95 | .expires { 96 | font-size: 120%; 97 | font-weight: bold; 98 | } 99 | 100 | li { 101 | list-style-type: none; 102 | display: inline; 103 | 104 | &:after { 105 | content: ', '; 106 | } 107 | 108 | &:last-child:after { 109 | content: ''; 110 | } 111 | 112 | .displayname { 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Extensions 2 | @{ 3 | string name = null; 4 | if (!true.Equals(ViewData["signed-out"])) 5 | { 6 | name = Context.User?.GetDisplayName(); 7 | } 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | IdentityServer4 16 | 17 | 18 | 19 | 20 | 21 | 22 | 52 | 53 |
    54 | @RenderBody() 55 |
    56 | 57 | 58 | 59 | @RenderSection("scripts", required: false) 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/SecurityHeadersAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Filters; 7 | 8 | namespace IndentityServer 9 | { 10 | public class SecurityHeadersAttribute : ActionFilterAttribute 11 | { 12 | public override void OnResultExecuting(ResultExecutingContext context) 13 | { 14 | var result = context.Result; 15 | if (result is ViewResult) 16 | { 17 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 18 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) 19 | { 20 | context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 21 | } 22 | 23 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 24 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) 25 | { 26 | context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); 27 | } 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 30 | var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; 31 | // also consider adding upgrade-insecure-requests once you have HTTPS in place for production 32 | //csp += "upgrade-insecure-requests;"; 33 | // also an example if you need client images to be displayed from twitter 34 | // csp += "img-src 'self' https://pbs.twimg.com;"; 35 | 36 | // once for standards compliant browsers 37 | if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) 38 | { 39 | context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); 40 | } 41 | // and once again for IE 42 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) 43 | { 44 | context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); 45 | } 46 | 47 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy 48 | var referrer_policy = "no-referrer"; 49 | if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) 50 | { 51 | context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Grants/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model GrantsViewModel 2 | 3 |
    4 | 12 | 13 | @if (Model.Grants.Any() == false) 14 | { 15 |
    16 |
    17 |
    18 | You have not given access to any applications 19 |
    20 |
    21 |
    22 | } 23 | else 24 | { 25 | foreach (var grant in Model.Grants) 26 | { 27 |
    28 |
    29 | @if (grant.ClientLogoUrl != null) 30 | { 31 | 32 | } 33 |
    34 |
    35 |
    @grant.ClientName
    36 |
    37 | Created: @grant.Created.ToString("yyyy-MM-dd") 38 |
    39 | @if (grant.Expires.HasValue) 40 | { 41 |
    42 | Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") 43 |
    44 | } 45 | @if (grant.IdentityGrantNames.Any()) 46 | { 47 |
    48 |
    Identity Grants
    49 |
      50 | @foreach (var name in grant.IdentityGrantNames) 51 | { 52 |
    • @name
    • 53 | } 54 |
    55 |
    56 | } 57 | @if (grant.ApiGrantNames.Any()) 58 | { 59 |
    60 |
    API Grants
    61 |
      62 | @foreach (var name in grant.ApiGrantNames) 63 | { 64 |
    • @name
    • 65 | } 66 |
    67 |
    68 | } 69 |
    70 |
    71 |
    72 | 73 | 74 |
    75 |
    76 |
    77 | } 78 | } 79 |
    -------------------------------------------------------------------------------- /src/IndentityServer/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model ConsentViewModel 2 | 3 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Grants/GrantsController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Services; 6 | using IdentityServer4.Stores; 7 | using Microsoft.AspNetCore.Mvc; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using Microsoft.AspNetCore.Authorization; 12 | using IdentityServer4.Events; 13 | using IdentityServer4.Extensions; 14 | 15 | namespace IndentityServer 16 | { 17 | /// 18 | /// This sample controller allows a user to revoke grants given to clients 19 | /// 20 | [SecurityHeaders] 21 | [Authorize] 22 | public class GrantsController : Controller 23 | { 24 | private readonly IIdentityServerInteractionService _interaction; 25 | private readonly IClientStore _clients; 26 | private readonly IResourceStore _resources; 27 | private readonly IEventService _events; 28 | 29 | public GrantsController(IIdentityServerInteractionService interaction, 30 | IClientStore clients, 31 | IResourceStore resources, 32 | IEventService events) 33 | { 34 | _interaction = interaction; 35 | _clients = clients; 36 | _resources = resources; 37 | _events = events; 38 | } 39 | 40 | /// 41 | /// Show list of grants 42 | /// 43 | [HttpGet] 44 | public async Task Index() 45 | { 46 | return View("Index", await BuildViewModelAsync()); 47 | } 48 | 49 | /// 50 | /// Handle postback to revoke a client 51 | /// 52 | [HttpPost] 53 | [ValidateAntiForgeryToken] 54 | public async Task Revoke(string clientId) 55 | { 56 | await _interaction.RevokeUserConsentAsync(clientId); 57 | await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); 58 | 59 | return RedirectToAction("Index"); 60 | } 61 | 62 | private async Task BuildViewModelAsync() 63 | { 64 | var grants = await _interaction.GetAllUserConsentsAsync(); 65 | 66 | var list = new List(); 67 | foreach(var grant in grants) 68 | { 69 | var client = await _clients.FindClientByIdAsync(grant.ClientId); 70 | if (client != null) 71 | { 72 | var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); 73 | 74 | var item = new GrantViewModel() 75 | { 76 | ClientId = client.ClientId, 77 | ClientName = client.ClientName ?? client.ClientId, 78 | ClientLogoUrl = client.LogoUri, 79 | ClientUrl = client.ClientUri, 80 | Created = grant.CreationTime, 81 | Expires = grant.Expiration, 82 | IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), 83 | ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() 84 | }; 85 | 86 | list.Add(item); 87 | } 88 | } 89 | 90 | return new GrantsViewModel 91 | { 92 | Grants = list 93 | }; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/IndentityServer/Config.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4; 7 | using IdentityServer4.Models; 8 | using IdentityServer4.Test; 9 | using System.Collections.Generic; 10 | using System.Security.Claims; 11 | 12 | namespace IndentityServer 13 | { 14 | public static class Config 15 | { 16 | public static IEnumerable GetIdentityResources() 17 | { 18 | return new IdentityResource[] 19 | { 20 | new IdentityResources.OpenId(), 21 | new IdentityResources.Profile(), 22 | new IdentityResources.Email() 23 | }; 24 | } 25 | 26 | public static IEnumerable GetApis() 27 | { 28 | return new ApiResource[] 29 | { 30 | new ApiResource("api", "Acme Fireworks Co. payroll") 31 | }; 32 | } 33 | 34 | public static IEnumerable GetClients() 35 | { 36 | return new Client[] 37 | { 38 | new Client 39 | { 40 | ClientId = "client", 41 | // no interactive user, use the clientid/secret for authentication 42 | AllowedGrantTypes = GrantTypes.ClientCredentials, 43 | // secret for authentication 44 | ClientSecrets = 45 | { 46 | new Secret("secret".Sha256()) 47 | }, 48 | // scopes that client has access to 49 | AllowedScopes = { "api" } 50 | }, 51 | new Client 52 | { 53 | ClientId = "spa", 54 | ClientName = "Single Page Javascript App", 55 | AllowedGrantTypes = GrantTypes.Code, 56 | // Specifies whether this client can request refresh tokens 57 | AllowOfflineAccess = true, 58 | RequireClientSecret = false, 59 | 60 | // where to redirect to after login 61 | RedirectUris = { "http://localhost:8080/callback.html" }, 62 | 63 | // where to redirect to after logout 64 | PostLogoutRedirectUris = { "http://localhost:8080/logout.html" }, 65 | 66 | AllowedScopes = new List 67 | { 68 | IdentityServerConstants.StandardScopes.OpenId, 69 | IdentityServerConstants.StandardScopes.Profile, 70 | IdentityServerConstants.StandardScopes.Email, 71 | "api" 72 | } 73 | } 74 | }; 75 | } 76 | 77 | internal static List GetTestUsers() 78 | { 79 | return new List 80 | { 81 | new TestUser { SubjectId = "1", Username = "alice", Password = "alice", 82 | Claims = 83 | { 84 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 85 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com") 86 | } 87 | }, 88 | new TestUser { SubjectId = "11", Username = "bob", Password = "bob", 89 | Claims = 90 | { 91 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 92 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com") 93 | } 94 | } 95 | }; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Device/UserCodeConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @model IndentityServer.Device.DeviceAuthorizationViewModel 2 | 3 | -------------------------------------------------------------------------------- /src/IndentityServer/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # identity-as-a-service workshop 2 | This workshop follows examples from https://identityserver4.readthedocs.io/en/latest/index.html 3 | It's a continuation of https://github.com/karpikpl/oidc-spa-client 4 | 5 | ## Build sample Identity As A Service project 6 | ```bash 7 | dotnet new -i IdentityServer4.Templates 8 | 9 | mkdir idaas && cd idaas 10 | mkdir src && cd src 11 | 12 | dotnet new is4empty -n IdentityServer 13 | cd .. 14 | 15 | dotnet new sln -n idaas 16 | dotnet sln add .\src\IdentityServer\IdentityServer.csproj 17 | ``` 18 | 19 | ## Protect the API 20 | In *config.cs* add an API resource: 21 | ```csharp 22 | public static IEnumerable GetApis() 23 | { 24 | return new ApiResource[] 25 | { 26 | new ApiResource("api", "Acme Fireworks Co. payroll") 27 | }; 28 | } 29 | ``` 30 | 31 | add a client: 32 | ```csharp 33 | return new Client[] 34 | { 35 | new Client 36 | { 37 | ClientId = "client", 38 | // no interactive user, use the clientid/secret for authentication 39 | AllowedGrantTypes = GrantTypes.ClientCredentials, 40 | // secret for authentication 41 | ClientSecrets = 42 | { 43 | new Secret("secret".Sha256()) 44 | }, 45 | // scopes that client has access to 46 | AllowedScopes = { "api" } 47 | } 48 | }; 49 | ``` 50 | 51 | # Change Authority in the API project 52 | ```csharp 53 | options.Authority = "http://localhost:5000"; 54 | options.RequireHttpsMetadata = false; 55 | options.Audience = "api"; 56 | ``` 57 | 58 | # Get the token 59 | ## Get the token 60 | ```bash 61 | curl -v -X POST http://localhost:5000/connect/token -d "client_id=client&client_secret=secret&grant_type=client_credentials&scope=api" | json_pp 62 | ``` 63 | 64 | ## Call the API 65 | ```bash 66 | curl https://localhost:6001/api/secure -v -k --header "Authorization: Bearer xxx" | json_pp 67 | ``` 68 | where *xxx* is the **access_token** 69 | 70 | ## Inspect the token 71 | http://jwt.io 72 | 73 | ## Add UI to IdentityServer 74 | ```bash 75 | cd src/IdentityServer 76 | dotnet new is4ui 77 | ``` 78 | 79 | In *startup.cs*: 80 | ```csharp 81 | public void ConfigureServices(IServiceCollection services) 82 | { 83 | // uncomment, if you want to add an MVC-based UI 84 | services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); 85 | ``` 86 | and 87 | ```csharp 88 | // uncomment if you want to support static files 89 | app.UseStaticFiles(); 90 | 91 | app.UseIdentityServer(); 92 | 93 | // uncomment, if you want to add an MVC-based UI 94 | app.UseMvcWithDefaultRoute(); 95 | ``` 96 | 97 | ## Setup CORS 98 | In *startup.cs* - *public void ConfigureServices(IServiceCollection services)* 99 | ```csharp 100 | var cors = new DefaultCorsPolicyService(new LoggerFactory().CreateLogger()) 101 | { 102 | AllowAll = true 103 | }; 104 | services.AddSingleton(cors); 105 | ``` 106 | 107 | ## Register UI Client 108 | Add identity resources in *config.cs*: 109 | ```csharp 110 | return new IdentityResource[] 111 | { 112 | new IdentityResources.OpenId(), 113 | new IdentityResources.Profile(), 114 | new IdentityResources.Email() 115 | }; 116 | ``` 117 | 118 | ## Add SPA client: 119 | ```csharp 120 | new Client 121 | { 122 | ClientId = "spa", 123 | ClientName = "Single Page Javascript App", 124 | AllowedGrantTypes = GrantTypes.Code, 125 | // Specifies whether this client can request refresh tokens 126 | AllowOfflineAccess = true, 127 | RequireClientSecret = false, 128 | 129 | // where to redirect to after login 130 | RedirectUris = { "http://localhost:8080/callback.html" }, 131 | 132 | // where to redirect to after logout 133 | PostLogoutRedirectUris = { "http://localhost:8080/logout.html" }, 134 | 135 | AllowedScopes = new List 136 | { 137 | IdentityServerConstants.StandardScopes.OpenId, 138 | IdentityServerConstants.StandardScopes.Profile, 139 | IdentityServerConstants.StandardScopes.Email, 140 | "api" 141 | } 142 | ``` 143 | 144 | ## Add Users 145 | In *startup.cs:* 146 | ```csharp 147 | var builder = services.AddIdentityServer() 148 | .AddInMemoryIdentityResources(Config.GetIdentityResources()) 149 | .AddInMemoryApiResources(Config.GetApis()) 150 | .AddInMemoryClients(Config.GetClients()) 151 | .AddTestUsers(Config.GetTestUsers()); 152 | ``` 153 | In *config.cs*: 154 | ```csharp 155 | internal static List GetTestUsers() 156 | { 157 | return new List 158 | { 159 | new TestUser { SubjectId = "1", Username = "alice", Password = "alice", 160 | Claims = 161 | { 162 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 163 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com") 164 | } 165 | }, 166 | new TestUser { SubjectId = "11", Username = "bob", Password = "bob", 167 | Claims = 168 | { 169 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 170 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com") 171 | } 172 | } 173 | }; 174 | } 175 | ``` 176 | 177 | ## Point SPA to local Identity Server 178 | ```js 179 | const config = { 180 | authority: 'http://localhost:5000/', 181 | client_id: 'spa', 182 | redirect_uri: 'http://localhost:8080/callback.html', 183 | response_type: 'code', 184 | scope: 'openid profile email api offline_access' 185 | }; 186 | ``` 187 | 188 | ## Run SPA and test 189 | ```bash 190 | npx http-server 191 | ``` 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | identityserver4_log.txt 6 | 7 | # User-specific files 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # JustCode is a .NET coding add-in 131 | .JustCode 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ 354 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Device/DeviceController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using IdentityServer4.Events; 9 | using IdentityServer4.Extensions; 10 | using IdentityServer4.Models; 11 | using IdentityServer4.Services; 12 | using IdentityServer4.Stores; 13 | using Microsoft.AspNetCore.Authorization; 14 | using Microsoft.AspNetCore.Mvc; 15 | using Microsoft.Extensions.Logging; 16 | 17 | namespace IndentityServer.Device 18 | { 19 | [Authorize] 20 | [SecurityHeaders] 21 | public class DeviceController : Controller 22 | { 23 | private readonly IDeviceFlowInteractionService _interaction; 24 | private readonly IClientStore _clientStore; 25 | private readonly IResourceStore _resourceStore; 26 | private readonly IEventService _events; 27 | private readonly ILogger _logger; 28 | 29 | public DeviceController( 30 | IDeviceFlowInteractionService interaction, 31 | IClientStore clientStore, 32 | IResourceStore resourceStore, 33 | IEventService eventService, 34 | ILogger logger) 35 | { 36 | _interaction = interaction; 37 | _clientStore = clientStore; 38 | _resourceStore = resourceStore; 39 | _events = eventService; 40 | _logger = logger; 41 | } 42 | 43 | [HttpGet] 44 | public async Task Index([FromQuery(Name = "user_code")] string userCode) 45 | { 46 | if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); 47 | 48 | var vm = await BuildViewModelAsync(userCode); 49 | if (vm == null) return View("Error"); 50 | 51 | vm.ConfirmUserCode = true; 52 | return View("UserCodeConfirmation", vm); 53 | } 54 | 55 | [HttpPost] 56 | [ValidateAntiForgeryToken] 57 | public async Task UserCodeCapture(string userCode) 58 | { 59 | var vm = await BuildViewModelAsync(userCode); 60 | if (vm == null) return View("Error"); 61 | 62 | return View("UserCodeConfirmation", vm); 63 | } 64 | 65 | [HttpPost] 66 | [ValidateAntiForgeryToken] 67 | public async Task Callback(DeviceAuthorizationInputModel model) 68 | { 69 | if (model == null) throw new ArgumentNullException(nameof(model)); 70 | 71 | var result = await ProcessConsent(model); 72 | if (result.HasValidationError) return View("Error"); 73 | 74 | return View("Success"); 75 | } 76 | 77 | private async Task ProcessConsent(DeviceAuthorizationInputModel model) 78 | { 79 | var result = new ProcessConsentResult(); 80 | 81 | var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); 82 | if (request == null) return result; 83 | 84 | ConsentResponse grantedConsent = null; 85 | 86 | // user clicked 'no' - send back the standard 'access_denied' response 87 | if (model.Button == "no") 88 | { 89 | grantedConsent = ConsentResponse.Denied; 90 | 91 | // emit event 92 | await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested)); 93 | } 94 | // user clicked 'yes' - validate the data 95 | else if (model.Button == "yes") 96 | { 97 | // if the user consented to some scope, build the response model 98 | if (model.ScopesConsented != null && model.ScopesConsented.Any()) 99 | { 100 | var scopes = model.ScopesConsented; 101 | if (ConsentOptions.EnableOfflineAccess == false) 102 | { 103 | scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); 104 | } 105 | 106 | grantedConsent = new ConsentResponse 107 | { 108 | RememberConsent = model.RememberConsent, 109 | ScopesConsented = scopes.ToArray() 110 | }; 111 | 112 | // emit event 113 | await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, grantedConsent.RememberConsent)); 114 | } 115 | else 116 | { 117 | result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; 118 | } 119 | } 120 | else 121 | { 122 | result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; 123 | } 124 | 125 | if (grantedConsent != null) 126 | { 127 | // communicate outcome of consent back to identityserver 128 | await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); 129 | 130 | // indicate that's it ok to redirect back to authorization endpoint 131 | result.RedirectUri = model.ReturnUrl; 132 | result.ClientId = request.ClientId; 133 | } 134 | else 135 | { 136 | // we need to redisplay the consent UI 137 | result.ViewModel = await BuildViewModelAsync(model.UserCode, model); 138 | } 139 | 140 | return result; 141 | } 142 | 143 | private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) 144 | { 145 | var request = await _interaction.GetAuthorizationContextAsync(userCode); 146 | if (request != null) 147 | { 148 | var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); 149 | if (client != null) 150 | { 151 | var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); 152 | if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) 153 | { 154 | return CreateConsentViewModel(userCode, model, client, resources); 155 | } 156 | else 157 | { 158 | _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); 159 | } 160 | } 161 | else 162 | { 163 | _logger.LogError("Invalid client id: {0}", request.ClientId); 164 | } 165 | } 166 | 167 | return null; 168 | } 169 | 170 | private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, Client client, Resources resources) 171 | { 172 | var vm = new DeviceAuthorizationViewModel 173 | { 174 | UserCode = userCode, 175 | 176 | RememberConsent = model?.RememberConsent ?? true, 177 | ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), 178 | 179 | ClientName = client.ClientName ?? client.ClientId, 180 | ClientUrl = client.ClientUri, 181 | ClientLogoUrl = client.LogoUri, 182 | AllowRememberConsent = client.AllowRememberConsent 183 | }; 184 | 185 | vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 186 | vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 187 | if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) 188 | { 189 | vm.ResourceScopes = vm.ResourceScopes.Union(new[] 190 | { 191 | GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) 192 | }); 193 | } 194 | 195 | return vm; 196 | } 197 | 198 | private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) 199 | { 200 | return new ScopeViewModel 201 | { 202 | Name = identity.Name, 203 | DisplayName = identity.DisplayName, 204 | Description = identity.Description, 205 | Emphasize = identity.Emphasize, 206 | Required = identity.Required, 207 | Checked = check || identity.Required 208 | }; 209 | } 210 | 211 | public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) 212 | { 213 | return new ScopeViewModel 214 | { 215 | Name = scope.Name, 216 | DisplayName = scope.DisplayName, 217 | Description = scope.Description, 218 | Emphasize = scope.Emphasize, 219 | Required = scope.Required, 220 | Checked = check || scope.Required 221 | }; 222 | } 223 | private ScopeViewModel GetOfflineAccessScope(bool check) 224 | { 225 | return new ScopeViewModel 226 | { 227 | Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, 228 | DisplayName = ConsentOptions.OfflineAccessDisplayName, 229 | Description = ConsentOptions.OfflineAccessDescription, 230 | Emphasize = true, 231 | Checked = check 232 | }; 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Consent/ConsentController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityServer4.Events; 6 | using IdentityServer4.Models; 7 | using IdentityServer4.Services; 8 | using IdentityServer4.Stores; 9 | using IdentityServer4.Extensions; 10 | using Microsoft.AspNetCore.Authorization; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.Extensions.Logging; 13 | using System.Linq; 14 | using System.Threading.Tasks; 15 | 16 | namespace IndentityServer 17 | { 18 | /// 19 | /// This controller processes the consent UI 20 | /// 21 | [SecurityHeaders] 22 | [Authorize] 23 | public class ConsentController : Controller 24 | { 25 | private readonly IIdentityServerInteractionService _interaction; 26 | private readonly IClientStore _clientStore; 27 | private readonly IResourceStore _resourceStore; 28 | private readonly IEventService _events; 29 | private readonly ILogger _logger; 30 | 31 | public ConsentController( 32 | IIdentityServerInteractionService interaction, 33 | IClientStore clientStore, 34 | IResourceStore resourceStore, 35 | IEventService events, 36 | ILogger logger) 37 | { 38 | _interaction = interaction; 39 | _clientStore = clientStore; 40 | _resourceStore = resourceStore; 41 | _events = events; 42 | _logger = logger; 43 | } 44 | 45 | /// 46 | /// Shows the consent screen 47 | /// 48 | /// 49 | /// 50 | [HttpGet] 51 | public async Task Index(string returnUrl) 52 | { 53 | var vm = await BuildViewModelAsync(returnUrl); 54 | if (vm != null) 55 | { 56 | return View("Index", vm); 57 | } 58 | 59 | return View("Error"); 60 | } 61 | 62 | /// 63 | /// Handles the consent screen postback 64 | /// 65 | [HttpPost] 66 | [ValidateAntiForgeryToken] 67 | public async Task Index(ConsentInputModel model) 68 | { 69 | var result = await ProcessConsent(model); 70 | 71 | if (result.IsRedirect) 72 | { 73 | if (await _clientStore.IsPkceClientAsync(result.ClientId)) 74 | { 75 | // if the client is PKCE then we assume it's native, so this change in how to 76 | // return the response is for better UX for the end user. 77 | return View("Redirect", new RedirectViewModel { RedirectUrl = result.RedirectUri }); 78 | } 79 | 80 | return Redirect(result.RedirectUri); 81 | } 82 | 83 | if (result.HasValidationError) 84 | { 85 | ModelState.AddModelError(string.Empty, result.ValidationError); 86 | } 87 | 88 | if (result.ShowView) 89 | { 90 | return View("Index", result.ViewModel); 91 | } 92 | 93 | return View("Error"); 94 | } 95 | 96 | /*****************************************/ 97 | /* helper APIs for the ConsentController */ 98 | /*****************************************/ 99 | private async Task ProcessConsent(ConsentInputModel model) 100 | { 101 | var result = new ProcessConsentResult(); 102 | 103 | // validate return url is still valid 104 | var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 105 | if (request == null) return result; 106 | 107 | ConsentResponse grantedConsent = null; 108 | 109 | // user clicked 'no' - send back the standard 'access_denied' response 110 | if (model?.Button == "no") 111 | { 112 | grantedConsent = ConsentResponse.Denied; 113 | 114 | // emit event 115 | await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested)); 116 | } 117 | // user clicked 'yes' - validate the data 118 | else if (model?.Button == "yes") 119 | { 120 | // if the user consented to some scope, build the response model 121 | if (model.ScopesConsented != null && model.ScopesConsented.Any()) 122 | { 123 | var scopes = model.ScopesConsented; 124 | if (ConsentOptions.EnableOfflineAccess == false) 125 | { 126 | scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); 127 | } 128 | 129 | grantedConsent = new ConsentResponse 130 | { 131 | RememberConsent = model.RememberConsent, 132 | ScopesConsented = scopes.ToArray() 133 | }; 134 | 135 | // emit event 136 | await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, grantedConsent.RememberConsent)); 137 | } 138 | else 139 | { 140 | result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; 141 | } 142 | } 143 | else 144 | { 145 | result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; 146 | } 147 | 148 | if (grantedConsent != null) 149 | { 150 | // communicate outcome of consent back to identityserver 151 | await _interaction.GrantConsentAsync(request, grantedConsent); 152 | 153 | // indicate that's it ok to redirect back to authorization endpoint 154 | result.RedirectUri = model.ReturnUrl; 155 | result.ClientId = request.ClientId; 156 | } 157 | else 158 | { 159 | // we need to redisplay the consent UI 160 | result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); 161 | } 162 | 163 | return result; 164 | } 165 | 166 | private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) 167 | { 168 | var request = await _interaction.GetAuthorizationContextAsync(returnUrl); 169 | if (request != null) 170 | { 171 | var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); 172 | if (client != null) 173 | { 174 | var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); 175 | if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any())) 176 | { 177 | return CreateConsentViewModel(model, returnUrl, request, client, resources); 178 | } 179 | else 180 | { 181 | _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); 182 | } 183 | } 184 | else 185 | { 186 | _logger.LogError("Invalid client id: {0}", request.ClientId); 187 | } 188 | } 189 | else 190 | { 191 | _logger.LogError("No consent request matching request: {0}", returnUrl); 192 | } 193 | 194 | return null; 195 | } 196 | 197 | private ConsentViewModel CreateConsentViewModel( 198 | ConsentInputModel model, string returnUrl, 199 | AuthorizationRequest request, 200 | Client client, Resources resources) 201 | { 202 | var vm = new ConsentViewModel 203 | { 204 | RememberConsent = model?.RememberConsent ?? true, 205 | ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), 206 | 207 | ReturnUrl = returnUrl, 208 | 209 | ClientName = client.ClientName ?? client.ClientId, 210 | ClientUrl = client.ClientUri, 211 | ClientLogoUrl = client.LogoUri, 212 | AllowRememberConsent = client.AllowRememberConsent 213 | }; 214 | 215 | vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 216 | vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); 217 | if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess) 218 | { 219 | vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] { 220 | GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null) 221 | }); 222 | } 223 | 224 | return vm; 225 | } 226 | 227 | private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) 228 | { 229 | return new ScopeViewModel 230 | { 231 | Name = identity.Name, 232 | DisplayName = identity.DisplayName, 233 | Description = identity.Description, 234 | Emphasize = identity.Emphasize, 235 | Required = identity.Required, 236 | Checked = check || identity.Required 237 | }; 238 | } 239 | 240 | public ScopeViewModel CreateScopeViewModel(Scope scope, bool check) 241 | { 242 | return new ScopeViewModel 243 | { 244 | Name = scope.Name, 245 | DisplayName = scope.DisplayName, 246 | Description = scope.Description, 247 | Emphasize = scope.Emphasize, 248 | Required = scope.Required, 249 | Checked = check || scope.Required 250 | }; 251 | } 252 | 253 | private ScopeViewModel GetOfflineAccessScope(bool check) 254 | { 255 | return new ScopeViewModel 256 | { 257 | Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, 258 | DisplayName = ConsentOptions.OfflineAccessDisplayName, 259 | Description = ConsentOptions.OfflineAccessDescription, 260 | Emphasize = true, 261 | Checked = check 262 | }; 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/ExternalController.cs: -------------------------------------------------------------------------------- 1 | using IdentityModel; 2 | using IdentityServer4.Events; 3 | using IdentityServer4.Services; 4 | using IdentityServer4.Stores; 5 | using IdentityServer4.Test; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.Extensions.Logging; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Security.Claims; 15 | using System.Security.Principal; 16 | using System.Threading.Tasks; 17 | 18 | namespace IndentityServer 19 | { 20 | [SecurityHeaders] 21 | [AllowAnonymous] 22 | public class ExternalController : Controller 23 | { 24 | private readonly TestUserStore _users; 25 | private readonly IIdentityServerInteractionService _interaction; 26 | private readonly IClientStore _clientStore; 27 | private readonly ILogger _logger; 28 | private readonly IEventService _events; 29 | 30 | public ExternalController( 31 | IIdentityServerInteractionService interaction, 32 | IClientStore clientStore, 33 | IEventService events, 34 | ILogger logger, 35 | TestUserStore users = null) 36 | { 37 | // if the TestUserStore is not in DI, then we'll just use the global users collection 38 | // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) 39 | _users = users ?? new TestUserStore(TestUsers.Users); 40 | 41 | _interaction = interaction; 42 | _clientStore = clientStore; 43 | _logger = logger; 44 | _events = events; 45 | } 46 | 47 | /// 48 | /// initiate roundtrip to external authentication provider 49 | /// 50 | [HttpGet] 51 | public async Task Challenge(string provider, string returnUrl) 52 | { 53 | if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; 54 | 55 | // validate returnUrl - either it is a valid OIDC URL or back to a local page 56 | if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) 57 | { 58 | // user might have clicked on a malicious link - should be logged 59 | throw new Exception("invalid return URL"); 60 | } 61 | 62 | if (AccountOptions.WindowsAuthenticationSchemeName == provider) 63 | { 64 | // windows authentication needs special handling 65 | return await ProcessWindowsLoginAsync(returnUrl); 66 | } 67 | else 68 | { 69 | // start challenge and roundtrip the return URL and scheme 70 | var props = new AuthenticationProperties 71 | { 72 | RedirectUri = Url.Action(nameof(Callback)), 73 | Items = 74 | { 75 | { "returnUrl", returnUrl }, 76 | { "scheme", provider }, 77 | } 78 | }; 79 | 80 | return Challenge(props, provider); 81 | } 82 | } 83 | 84 | /// 85 | /// Post processing of external authentication 86 | /// 87 | [HttpGet] 88 | public async Task Callback() 89 | { 90 | // read external identity from the temporary cookie 91 | var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 92 | if (result?.Succeeded != true) 93 | { 94 | throw new Exception("External authentication error"); 95 | } 96 | 97 | if (_logger.IsEnabled(LogLevel.Debug)) 98 | { 99 | var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); 100 | _logger.LogDebug("External claims: {@claims}", externalClaims); 101 | } 102 | 103 | // lookup our user and external provider info 104 | var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result); 105 | if (user == null) 106 | { 107 | // this might be where you might initiate a custom workflow for user registration 108 | // in this sample we don't show how that would be done, as our sample implementation 109 | // simply auto-provisions new external user 110 | user = AutoProvisionUser(provider, providerUserId, claims); 111 | } 112 | 113 | // this allows us to collect any additonal claims or properties 114 | // for the specific prtotocols used and store them in the local auth cookie. 115 | // this is typically used to store data needed for signout from those protocols. 116 | var additionalLocalClaims = new List(); 117 | var localSignInProps = new AuthenticationProperties(); 118 | ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps); 119 | ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps); 120 | ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps); 121 | 122 | // issue authentication cookie for user 123 | await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray()); 124 | 125 | // delete temporary cookie used during external authentication 126 | await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); 127 | 128 | // retrieve return URL 129 | var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; 130 | 131 | // check if external login is in the context of an OIDC request 132 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 133 | await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId)); 134 | 135 | if (context != null) 136 | { 137 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 138 | { 139 | // if the client is PKCE then we assume it's native, so this change in how to 140 | // return the response is for better UX for the end user. 141 | return View("Redirect", new RedirectViewModel { RedirectUrl = returnUrl }); 142 | } 143 | } 144 | 145 | return Redirect(returnUrl); 146 | } 147 | 148 | private async Task ProcessWindowsLoginAsync(string returnUrl) 149 | { 150 | // see if windows auth has already been requested and succeeded 151 | var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName); 152 | if (result?.Principal is WindowsPrincipal wp) 153 | { 154 | // we will issue the external cookie and then redirect the 155 | // user back to the external callback, in essence, treating windows 156 | // auth the same as any other external authentication mechanism 157 | var props = new AuthenticationProperties() 158 | { 159 | RedirectUri = Url.Action("Callback"), 160 | Items = 161 | { 162 | { "returnUrl", returnUrl }, 163 | { "scheme", AccountOptions.WindowsAuthenticationSchemeName }, 164 | } 165 | }; 166 | 167 | var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName); 168 | id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name)); 169 | id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); 170 | 171 | // add the groups as claims -- be careful if the number of groups is too large 172 | if (AccountOptions.IncludeWindowsGroups) 173 | { 174 | var wi = wp.Identity as WindowsIdentity; 175 | var groups = wi.Groups.Translate(typeof(NTAccount)); 176 | var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value)); 177 | id.AddClaims(roles); 178 | } 179 | 180 | await HttpContext.SignInAsync( 181 | IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, 182 | new ClaimsPrincipal(id), 183 | props); 184 | return Redirect(props.RedirectUri); 185 | } 186 | else 187 | { 188 | // trigger windows auth 189 | // since windows auth don't support the redirect uri, 190 | // this URL is re-triggered when we call challenge 191 | return Challenge(AccountOptions.WindowsAuthenticationSchemeName); 192 | } 193 | } 194 | 195 | private (TestUser user, string provider, string providerUserId, IEnumerable claims) FindUserFromExternalProvider(AuthenticateResult result) 196 | { 197 | var externalUser = result.Principal; 198 | 199 | // try to determine the unique id of the external user (issued by the provider) 200 | // the most common claim type for that are the sub claim and the NameIdentifier 201 | // depending on the external provider, some other claim type might be used 202 | var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? 203 | externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? 204 | throw new Exception("Unknown userid"); 205 | 206 | // remove the user id claim so we don't include it as an extra claim if/when we provision the user 207 | var claims = externalUser.Claims.ToList(); 208 | claims.Remove(userIdClaim); 209 | 210 | var provider = result.Properties.Items["scheme"]; 211 | var providerUserId = userIdClaim.Value; 212 | 213 | // find external user 214 | var user = _users.FindByExternalProvider(provider, providerUserId); 215 | 216 | return (user, provider, providerUserId, claims); 217 | } 218 | 219 | private TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable claims) 220 | { 221 | var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); 222 | return user; 223 | } 224 | 225 | private void ProcessLoginCallbackForOidc(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 226 | { 227 | // if the external system sent a session id claim, copy it over 228 | // so we can use it for single sign-out 229 | var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); 230 | if (sid != null) 231 | { 232 | localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); 233 | } 234 | 235 | // if the external provider issued an id_token, we'll keep it for signout 236 | var id_token = externalResult.Properties.GetTokenValue("id_token"); 237 | if (id_token != null) 238 | { 239 | localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); 240 | } 241 | } 242 | 243 | private void ProcessLoginCallbackForWsFed(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 244 | { 245 | } 246 | 247 | private void ProcessLoginCallbackForSaml2p(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) 248 | { 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/IndentityServer/Quickstart/Account/AccountController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using IdentityModel; 6 | using IdentityServer4.Events; 7 | using IdentityServer4.Extensions; 8 | using IdentityServer4.Models; 9 | using IdentityServer4.Services; 10 | using IdentityServer4.Stores; 11 | using IdentityServer4.Test; 12 | using Microsoft.AspNetCore.Authentication; 13 | using Microsoft.AspNetCore.Authorization; 14 | using Microsoft.AspNetCore.Http; 15 | using Microsoft.AspNetCore.Mvc; 16 | using System; 17 | using System.Linq; 18 | using System.Threading.Tasks; 19 | 20 | namespace IndentityServer 21 | { 22 | /// 23 | /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. 24 | /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! 25 | /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval 26 | /// 27 | [SecurityHeaders] 28 | [AllowAnonymous] 29 | public class AccountController : Controller 30 | { 31 | private readonly TestUserStore _users; 32 | private readonly IIdentityServerInteractionService _interaction; 33 | private readonly IClientStore _clientStore; 34 | private readonly IAuthenticationSchemeProvider _schemeProvider; 35 | private readonly IEventService _events; 36 | 37 | public AccountController( 38 | IIdentityServerInteractionService interaction, 39 | IClientStore clientStore, 40 | IAuthenticationSchemeProvider schemeProvider, 41 | IEventService events, 42 | TestUserStore users = null) 43 | { 44 | // if the TestUserStore is not in DI, then we'll just use the global users collection 45 | // this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity) 46 | _users = users ?? new TestUserStore(TestUsers.Users); 47 | 48 | _interaction = interaction; 49 | _clientStore = clientStore; 50 | _schemeProvider = schemeProvider; 51 | _events = events; 52 | } 53 | 54 | /// 55 | /// Entry point into the login workflow 56 | /// 57 | [HttpGet] 58 | public async Task Login(string returnUrl) 59 | { 60 | // build a model so we know what to show on the login page 61 | var vm = await BuildLoginViewModelAsync(returnUrl); 62 | 63 | if (vm.IsExternalLoginOnly) 64 | { 65 | // we only have one option for logging in and it's an external provider 66 | return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl }); 67 | } 68 | 69 | return View(vm); 70 | } 71 | 72 | /// 73 | /// Handle postback from username/password login 74 | /// 75 | [HttpPost] 76 | [ValidateAntiForgeryToken] 77 | public async Task Login(LoginInputModel model, string button) 78 | { 79 | // check if we are in the context of an authorization request 80 | var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); 81 | 82 | // the user clicked the "cancel" button 83 | if (button != "login") 84 | { 85 | if (context != null) 86 | { 87 | // if the user cancels, send a result back into IdentityServer as if they 88 | // denied the consent (even if this client does not require consent). 89 | // this will send back an access denied OIDC error response to the client. 90 | await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); 91 | 92 | // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 93 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 94 | { 95 | // if the client is PKCE then we assume it's native, so this change in how to 96 | // return the response is for better UX for the end user. 97 | return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 98 | } 99 | 100 | return Redirect(model.ReturnUrl); 101 | } 102 | else 103 | { 104 | // since we don't have a valid context, then we just go back to the home page 105 | return Redirect("~/"); 106 | } 107 | } 108 | 109 | if (ModelState.IsValid) 110 | { 111 | // validate username/password against in-memory store 112 | if (_users.ValidateCredentials(model.Username, model.Password)) 113 | { 114 | var user = _users.FindByUsername(model.Username); 115 | await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.ClientId)); 116 | 117 | // only set explicit expiration here if user chooses "remember me". 118 | // otherwise we rely upon expiration configured in cookie middleware. 119 | AuthenticationProperties props = null; 120 | if (AccountOptions.AllowRememberLogin && model.RememberLogin) 121 | { 122 | props = new AuthenticationProperties 123 | { 124 | IsPersistent = true, 125 | ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) 126 | }; 127 | }; 128 | 129 | // issue authentication cookie with subject ID and username 130 | await HttpContext.SignInAsync(user.SubjectId, user.Username, props); 131 | 132 | if (context != null) 133 | { 134 | if (await _clientStore.IsPkceClientAsync(context.ClientId)) 135 | { 136 | // if the client is PKCE then we assume it's native, so this change in how to 137 | // return the response is for better UX for the end user. 138 | return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); 139 | } 140 | 141 | // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null 142 | return Redirect(model.ReturnUrl); 143 | } 144 | 145 | // request for a local page 146 | if (Url.IsLocalUrl(model.ReturnUrl)) 147 | { 148 | return Redirect(model.ReturnUrl); 149 | } 150 | else if (string.IsNullOrEmpty(model.ReturnUrl)) 151 | { 152 | return Redirect("~/"); 153 | } 154 | else 155 | { 156 | // user might have clicked on a malicious link - should be logged 157 | throw new Exception("invalid return URL"); 158 | } 159 | } 160 | 161 | await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.ClientId)); 162 | ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); 163 | } 164 | 165 | // something went wrong, show form with error 166 | var vm = await BuildLoginViewModelAsync(model); 167 | return View(vm); 168 | } 169 | 170 | 171 | /// 172 | /// Show logout page 173 | /// 174 | [HttpGet] 175 | public async Task Logout(string logoutId) 176 | { 177 | // build a model so the logout page knows what to display 178 | var vm = await BuildLogoutViewModelAsync(logoutId); 179 | 180 | if (vm.ShowLogoutPrompt == false) 181 | { 182 | // if the request for logout was properly authenticated from IdentityServer, then 183 | // we don't need to show the prompt and can just log the user out directly. 184 | return await Logout(vm); 185 | } 186 | 187 | return View(vm); 188 | } 189 | 190 | /// 191 | /// Handle logout page postback 192 | /// 193 | [HttpPost] 194 | [ValidateAntiForgeryToken] 195 | public async Task Logout(LogoutInputModel model) 196 | { 197 | // build a model so the logged out page knows what to display 198 | var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); 199 | 200 | if (User?.Identity.IsAuthenticated == true) 201 | { 202 | // delete local authentication cookie 203 | await HttpContext.SignOutAsync(); 204 | 205 | // raise the logout event 206 | await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); 207 | } 208 | 209 | // check if we need to trigger sign-out at an upstream identity provider 210 | if (vm.TriggerExternalSignout) 211 | { 212 | // build a return URL so the upstream provider will redirect back 213 | // to us after the user has logged out. this allows us to then 214 | // complete our single sign-out processing. 215 | string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); 216 | 217 | // this triggers a redirect to the external provider for sign-out 218 | return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); 219 | } 220 | 221 | return View("LoggedOut", vm); 222 | } 223 | 224 | [HttpGet] 225 | public IActionResult AccessDenied() 226 | { 227 | return View(); 228 | } 229 | 230 | 231 | /*****************************************/ 232 | /* helper APIs for the AccountController */ 233 | /*****************************************/ 234 | private async Task BuildLoginViewModelAsync(string returnUrl) 235 | { 236 | var context = await _interaction.GetAuthorizationContextAsync(returnUrl); 237 | if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) 238 | { 239 | var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider; 240 | 241 | // this is meant to short circuit the UI and only trigger the one external IdP 242 | var vm = new LoginViewModel 243 | { 244 | EnableLocalLogin = local, 245 | ReturnUrl = returnUrl, 246 | Username = context?.LoginHint, 247 | }; 248 | 249 | if (!local) 250 | { 251 | vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; 252 | } 253 | 254 | return vm; 255 | } 256 | 257 | var schemes = await _schemeProvider.GetAllSchemesAsync(); 258 | 259 | var providers = schemes 260 | .Where(x => x.DisplayName != null || 261 | (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) 262 | ) 263 | .Select(x => new ExternalProvider 264 | { 265 | DisplayName = x.DisplayName, 266 | AuthenticationScheme = x.Name 267 | }).ToList(); 268 | 269 | var allowLocal = true; 270 | if (context?.ClientId != null) 271 | { 272 | var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); 273 | if (client != null) 274 | { 275 | allowLocal = client.EnableLocalLogin; 276 | 277 | if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) 278 | { 279 | providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); 280 | } 281 | } 282 | } 283 | 284 | return new LoginViewModel 285 | { 286 | AllowRememberLogin = AccountOptions.AllowRememberLogin, 287 | EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, 288 | ReturnUrl = returnUrl, 289 | Username = context?.LoginHint, 290 | ExternalProviders = providers.ToArray() 291 | }; 292 | } 293 | 294 | private async Task BuildLoginViewModelAsync(LoginInputModel model) 295 | { 296 | var vm = await BuildLoginViewModelAsync(model.ReturnUrl); 297 | vm.Username = model.Username; 298 | vm.RememberLogin = model.RememberLogin; 299 | return vm; 300 | } 301 | 302 | private async Task BuildLogoutViewModelAsync(string logoutId) 303 | { 304 | var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; 305 | 306 | if (User?.Identity.IsAuthenticated != true) 307 | { 308 | // if the user is not authenticated, then just show logged out page 309 | vm.ShowLogoutPrompt = false; 310 | return vm; 311 | } 312 | 313 | var context = await _interaction.GetLogoutContextAsync(logoutId); 314 | if (context?.ShowSignoutPrompt == false) 315 | { 316 | // it's safe to automatically sign-out 317 | vm.ShowLogoutPrompt = false; 318 | return vm; 319 | } 320 | 321 | // show the logout prompt. this prevents attacks where the user 322 | // is automatically signed out by another malicious web page. 323 | return vm; 324 | } 325 | 326 | private async Task BuildLoggedOutViewModelAsync(string logoutId) 327 | { 328 | // get context information (client name, post logout redirect URI and iframe for federated signout) 329 | var logout = await _interaction.GetLogoutContextAsync(logoutId); 330 | 331 | var vm = new LoggedOutViewModel 332 | { 333 | AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, 334 | PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, 335 | ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, 336 | SignOutIframeUrl = logout?.SignOutIFrameUrl, 337 | LogoutId = logoutId 338 | }; 339 | 340 | if (User?.Identity.IsAuthenticated == true) 341 | { 342 | var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; 343 | if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) 344 | { 345 | var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); 346 | if (providerSupportsSignout) 347 | { 348 | if (vm.LogoutId == null) 349 | { 350 | // if there's no current logout context, we need to create one 351 | // this captures necessary info from the current logged in user 352 | // before we signout and redirect away to the external IdP for signout 353 | vm.LogoutId = await _interaction.CreateLogoutContextAsync(); 354 | } 355 | 356 | vm.ExternalAuthenticationScheme = idp; 357 | } 358 | } 359 | } 360 | 361 | return vm; 362 | } 363 | } 364 | } -------------------------------------------------------------------------------- /src/IndentityServer/wwwroot/lib/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------