Skip to content

Commit b0bf59c

Browse files
committedDec 21, 2016
update models and ui for rtm
1 parent 02b7d3c commit b0bf59c

40 files changed

+404
-120
lines changed
 

‎NuGet.config

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,5 @@
44
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
55
<add key="Identity* Dev Feed" value="https://www.myget.org/F/identity/" />
66
</packageSources>
7-
<disabledPackageSources>
8-
<add key="Identity* Dev Feed" value="true" />
9-
</disabledPackageSources>
7+
<disabledPackageSources />
108
</configuration>

‎src/Host/Configuration/Clients.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public static IEnumerable<Client> Get()
160160

161161
RedirectUris = { "http://localhost:44077/signin-oidc" },
162162
LogoutUri = "http://localhost:44077/signout-oidc",
163-
PostLogoutRedirectUris = { "http://localhost:44077/" },
163+
PostLogoutRedirectUris = { "http://localhost:44077/signout-callback-oidc" },
164164

165165
AllowedScopes =
166166
{
@@ -208,7 +208,7 @@ public static IEnumerable<Client> Get()
208208

209209
RedirectUris = { "http://localhost:21402/signin-oidc" },
210210
LogoutUri = "http://localhost:21402/signout-oidc",
211-
PostLogoutRedirectUris = { "http://localhost:21402/" },
211+
PostLogoutRedirectUris = { "http://localhost:21402/signout-callback-oidc" },
212212

213213
AllowOfflineAccess = true,
214214

@@ -267,7 +267,7 @@ public static IEnumerable<Client> Get()
267267
IdentityServerConstants.StandardScopes.OpenId,
268268
IdentityServerConstants.StandardScopes.Profile,
269269
IdentityServerConstants.StandardScopes.Email,
270-
"api1", "api2.read_only", "api2.full_access"
270+
"api1", "api2.read_only"
271271
},
272272
},
273273
};

‎src/Host/Configuration/Resources.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static IEnumerable<ApiResource> GetApiResources()
2929
return new[]
3030
{
3131
// simple version with ctor
32-
new ApiResource("api1", "Your API 1")
32+
new ApiResource("api1", "Some API 1")
3333
{
3434
// this is needed for introspection when using reference tokens
3535
ApiSecrets = { new Secret("secret".Sha256()) }
@@ -39,8 +39,6 @@ public static IEnumerable<ApiResource> GetApiResources()
3939
new ApiResource
4040
{
4141
Name = "api2",
42-
DisplayName = "Your API 2",
43-
Description = "Something interesting",
4442

4543
ApiSecrets =
4644
{
@@ -50,6 +48,7 @@ public static IEnumerable<ApiResource> GetApiResources()
5048
UserClaims =
5149
{
5250
JwtClaimTypes.Name,
51+
JwtClaimTypes.Email
5352
},
5453

5554
Scopes =
@@ -58,7 +57,6 @@ public static IEnumerable<ApiResource> GetApiResources()
5857
{
5958
Name = "api2.full_access",
6059
DisplayName = "Full access to API 2",
61-
UserClaims = { "email" }
6260
},
6361
new Scope
6462
{

‎src/Host/Configuration/Users.cs

-52
This file was deleted.

‎src/Host/Controllers/AccountController.cs

+63-32
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33

44

55
using IdentityModel;
6+
using IdentityServer4.Models;
7+
using IdentityServer4.Quickstart.UI.Filters;
68
using IdentityServer4.Quickstart.UI.Models;
9+
using IdentityServer4.Quickstart.UI.Users;
710
using IdentityServer4.Services;
8-
using IdentityServer4.Services.InMemory;
11+
using IdentityServer4.Stores;
912
using Microsoft.AspNetCore.Http.Authentication;
1013
using Microsoft.AspNetCore.Mvc;
1114
using System;
1215
using System.Collections.Generic;
1316
using System.Linq;
1417
using System.Security.Claims;
15-
using System.Text.Encodings.Web;
18+
using System.Security.Principal;
1619
using System.Threading.Tasks;
17-
using IdentityServer4.Models;
18-
using IdentityServer4.Stores;
19-
using Host.Filters;
2020

2121
namespace IdentityServer4.Quickstart.UI.Controllers
2222
{
@@ -28,16 +28,20 @@ namespace IdentityServer4.Quickstart.UI.Controllers
2828
[SecurityHeaders]
2929
public class AccountController : Controller
3030
{
31-
private readonly InMemoryUserLoginService _loginService;
31+
private readonly TestUserStore _users;
32+
3233
private readonly IIdentityServerInteractionService _interaction;
3334
private readonly IClientStore _clientStore;
3435

36+
// if you want to support Windows authentication, specify the scheme you want to use
37+
private readonly string _windowsAuthenticationScheme = "Negotiate";
38+
3539
public AccountController(
36-
InMemoryUserLoginService loginService,
40+
TestUserStore users,
3741
IIdentityServerInteractionService interaction,
3842
IClientStore clientStore)
3943
{
40-
_loginService = loginService;
44+
_users = users;
4145
_interaction = interaction;
4246
_clientStore = clientStore;
4347
}
@@ -52,15 +56,15 @@ public async Task<IActionResult> Login(string returnUrl)
5256
if (context?.IdP != null)
5357
{
5458
// if IdP is passed, then bypass showing the login screen
55-
return ExternalLogin(context.IdP, returnUrl);
59+
return await ExternalLogin(context.IdP, returnUrl);
5660
}
5761

5862
var vm = await BuildLoginViewModelAsync(returnUrl, context);
5963

6064
if (vm.EnableLocalLogin == false && vm.ExternalProviders.Count() == 1)
6165
{
6266
// only one option for logging in
63-
return ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
67+
return await ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
6468
}
6569

6670
return View(vm);
@@ -76,10 +80,10 @@ public async Task<IActionResult> Login(LoginInputModel model)
7680
if (ModelState.IsValid)
7781
{
7882
// validate username/password against in-memory store
79-
if (_loginService.ValidateCredentials(model.Username, model.Password))
83+
if (_users.ValidateCredentials(model.Username, model.Password))
8084
{
8185
// issue authentication cookie with subject ID and username
82-
var user = _loginService.FindByUsername(model.Username);
86+
var user = _users.FindByUsername(model.Username);
8387

8488
AuthenticationProperties props = null;
8589
// only set explicit expiration here if persistent.
@@ -93,7 +97,7 @@ public async Task<IActionResult> Login(LoginInputModel model)
9397
};
9498
};
9599

96-
await HttpContext.Authentication.SignInAsync(user.Subject, user.Username, props);
100+
await HttpContext.Authentication.SignInAsync(user.SubjectId, user.Username, props);
97101

98102
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
99103
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
@@ -114,13 +118,26 @@ public async Task<IActionResult> Login(LoginInputModel model)
114118

115119
async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
116120
{
117-
var providers = HttpContext.Authentication.GetAuthenticationSchemes()
121+
var schemes = HttpContext.Authentication.GetAuthenticationSchemes();
122+
123+
var providers = schemes
118124
.Where(x => x.DisplayName != null)
119125
.Select(x => new ExternalProvider
120126
{
121127
DisplayName = x.DisplayName,
122128
AuthenticationScheme = x.AuthenticationScheme
129+
}).ToList();
130+
131+
// add Windows provider if present
132+
var windows = schemes.FirstOrDefault(s => s.AuthenticationScheme == _windowsAuthenticationScheme);
133+
if (windows != null)
134+
{
135+
providers.Add(new ExternalProvider
136+
{
137+
AuthenticationScheme = _windowsAuthenticationScheme,
138+
DisplayName = "Windows"
123139
});
140+
}
124141

125142
var allowLocal = true;
126143
if (context?.ClientId != null)
@@ -132,7 +149,7 @@ async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, Authorizat
132149

133150
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
134151
{
135-
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme));
152+
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
136153
}
137154
}
138155
}
@@ -202,13 +219,16 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
202219
model.LogoutId = await _interaction.CreateLogoutContextAsync();
203220
}
204221

205-
string url = "/Account/Logout?logoutId=" + model.LogoutId;
222+
string url = Url.Action("Logout", new { logoutId = model.LogoutId });
206223
try
207224
{
208225
// hack: try/catch to handle social providers that throw
209226
await HttpContext.Authentication.SignOutAsync(idp, new AuthenticationProperties { RedirectUri = url });
210227
}
211-
catch(NotSupportedException)
228+
catch(NotSupportedException) // this is for the external providers that don't have signout
229+
{
230+
}
231+
catch(InvalidOperationException) // this is for Windows/Negotiate
212232
{
213233
}
214234
}
@@ -217,7 +237,7 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
217237
await HttpContext.Authentication.SignOutAsync();
218238

219239
// set this so UI rendering sees an anonymous user
220-
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
240+
ViewData["signed-out"] = true;
221241

222242
// get context information (client name, post logout redirect URI and iframe for federated signout)
223243
var logout = await _interaction.GetLogoutContextAsync(model.LogoutId);
@@ -236,21 +256,32 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
236256
/// initiate roundtrip to external authentication provider
237257
/// </summary>
238258
[HttpGet]
239-
public IActionResult ExternalLogin(string provider, string returnUrl)
259+
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
240260
{
241-
if (returnUrl != null)
261+
returnUrl = Url.Action("ExternalLoginCallback", new { returnUrl = returnUrl });
262+
263+
if (provider == _windowsAuthenticationScheme && HttpContext.User is WindowsPrincipal)
242264
{
243-
returnUrl = UrlEncoder.Default.Encode(returnUrl);
244-
}
245-
returnUrl = "/account/externallogincallback?returnUrl=" + returnUrl;
265+
var props = new AuthenticationProperties();
266+
props.Items.Add("scheme", _windowsAuthenticationScheme);
267+
268+
var id = new ClaimsIdentity(provider);
269+
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, HttpContext.User.Identity.Name));
270+
id.AddClaim(new Claim(ClaimTypes.Name, HttpContext.User.Identity.Name));
246271

247-
// start challenge and roundtrip the return URL
248-
var props = new AuthenticationProperties
272+
await HttpContext.Authentication.SignInAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme, new ClaimsPrincipal(id), props);
273+
return Redirect(returnUrl);
274+
}
275+
else
249276
{
250-
RedirectUri = returnUrl,
251-
Items = { { "scheme", provider } }
252-
};
253-
return new ChallengeResult(provider, props);
277+
// start challenge and roundtrip the return URL
278+
var props = new AuthenticationProperties
279+
{
280+
RedirectUri = returnUrl,
281+
Items = { { "scheme", provider } }
282+
};
283+
return new ChallengeResult(provider, props);
284+
}
254285
}
255286

256287
/// <summary>
@@ -289,12 +320,12 @@ public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
289320
var userId = userIdClaim.Value;
290321

291322
// check if the external user is already provisioned
292-
var user = _loginService.FindByExternalProvider(provider, userId);
323+
var user = _users.FindByExternalProvider(provider, userId);
293324
if (user == null)
294325
{
295326
// this sample simply auto-provisions new external user
296327
// another common approach is to start a registrations workflow first
297-
user = _loginService.AutoProvisionUser(provider, userId, claims);
328+
user = _users.AutoProvisionUser(provider, userId, claims);
298329
}
299330

300331
var additionalClaims = new List<Claim>();
@@ -307,7 +338,7 @@ public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
307338
}
308339

309340
// issue authentication cookie for user
310-
await HttpContext.Authentication.SignInAsync(user.Subject, user.Username, provider, additionalClaims.ToArray());
341+
await HttpContext.Authentication.SignInAsync(user.SubjectId, user.Username, provider, additionalClaims.ToArray());
311342

312343
// delete temporary cookie used during external authentication
313344
await HttpContext.Authentication.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);

‎src/Host/Controllers/ConsentController.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
33

44

5+
using IdentityServer4.Models;
6+
using IdentityServer4.Quickstart.UI.Filters;
7+
using IdentityServer4.Quickstart.UI.Models;
58
using IdentityServer4.Services;
9+
using IdentityServer4.Stores;
610
using Microsoft.AspNetCore.Mvc;
711
using Microsoft.Extensions.Logging;
812
using System.Linq;
913
using System.Threading.Tasks;
10-
using IdentityServer4.Models;
11-
using IdentityServer4.Stores;
12-
using IdentityServer4.Quickstart.UI.Models;
13-
using Host.Filters;
1414

1515
namespace IdentityServer4.Quickstart.UI.Controllers
1616
{

‎src/Host/Controllers/HomeController.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
33

44

5-
using Host.Filters;
5+
using IdentityServer4.Quickstart.UI.Filters;
66
using IdentityServer4.Quickstart.UI.Models;
77
using IdentityServer4.Services;
88
using Microsoft.AspNetCore.Mvc;

‎src/Host/Filters/SecurityHeadersAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using Microsoft.AspNetCore.Mvc;
66
using Microsoft.AspNetCore.Mvc.Filters;
77

8-
namespace Host.Filters
8+
namespace IdentityServer4.Quickstart.UI.Filters
99
{
1010
public class SecurityHeadersAttribute : ActionFilterAttribute
1111
{

‎src/Host/Migrations/IdentityServer/ConfigurationDb/20161207131859_Config.Designer.cs ‎src/Host/Migrations/IdentityServer/ConfigurationDb/20161221000731_Config.Designer.cs

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/Host/Migrations/IdentityServer/ConfigurationDb/20161207131859_Config.cs ‎src/Host/Migrations/IdentityServer/ConfigurationDb/20161221000731_Config.cs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
3838
AllowOfflineAccess = table.Column<bool>(nullable: false),
3939
AllowPlainTextPkce = table.Column<bool>(nullable: false),
4040
AllowRememberConsent = table.Column<bool>(nullable: false),
41+
AlwaysIncludeUserClaimsInIdToken = table.Column<bool>(nullable: false),
4142
AlwaysSendClientClaims = table.Column<bool>(nullable: false),
4243
AuthorizationCodeLifetime = table.Column<int>(nullable: false),
4344
ClientId = table.Column<string>(maxLength: 200, nullable: false),

‎src/Host/Migrations/IdentityServer/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ partial class ConfigurationDbContextModelSnapshot : ModelSnapshot
1313
protected override void BuildModel(ModelBuilder modelBuilder)
1414
{
1515
modelBuilder
16-
.HasAnnotation("ProductVersion", "1.0.1")
16+
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
1717
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
1818

1919
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
@@ -158,6 +158,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
158158

159159
b.Property<bool>("AllowRememberConsent");
160160

161+
b.Property<bool>("AlwaysIncludeUserClaimsInIdToken");
162+
161163
b.Property<bool>("AlwaysSendClientClaims");
162164

163165
b.Property<int>("AuthorizationCodeLifetime");

‎src/Host/Migrations/IdentityServer/PersistedGrantDb/20161207131854_Grants.Designer.cs ‎src/Host/Migrations/IdentityServer/PersistedGrantDb/20161221000717_Grants.Designer.cs

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/Host/Migrations/IdentityServer/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot
1313
protected override void BuildModel(ModelBuilder modelBuilder)
1414
{
1515
modelBuilder
16-
.HasAnnotation("ProductVersion", "1.0.1")
16+
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
1717
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
1818

1919
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

‎src/Host/Startup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public void ConfigureServices(IServiceCollection services)
3030

3131
services.AddIdentityServer()
3232
.AddTemporarySigningCredential()
33-
.AddInMemoryUsers(Users.Get())
3433
.AddSecretParser<ClientAssertionSecretParser>()
3534
.AddSecretValidator<PrivateKeyJwtSecretValidator>()
35+
.AddTestUsers()
3636

3737
.AddConfigurationStore(builder =>
3838
builder.UseSqlServer(connectionString,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.Quickstart.UI.Users;
6+
using System.Collections.Generic;
7+
8+
namespace Microsoft.Extensions.DependencyInjection
9+
{
10+
public static class IdentityServerBuilderExtensions
11+
{
12+
public static IIdentityServerBuilder AddTestUsers(this IIdentityServerBuilder builder, List<TestUser> users = null)
13+
{
14+
builder.Services.AddSingleton(new TestUserStore(users ?? TestUsers.Users));
15+
builder.AddProfileService<TestUserProfileService>();
16+
builder.AddResourceOwnerValidator<TestUserResourceOwnerPasswordValidator>();
17+
18+
return builder;
19+
}
20+
}
21+
}

‎src/Host/Users/TestUser.cs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 System.Collections.Generic;
7+
using System.Security.Claims;
8+
9+
namespace IdentityServer4.Quickstart.UI.Users
10+
{
11+
/// <summary>
12+
/// In-memory user object for testing. Not intended for modeling users in production.
13+
/// </summary>
14+
public class TestUser
15+
{
16+
/// <summary>
17+
/// Gets or sets the subject identifier.
18+
/// </summary>
19+
public string SubjectId { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the username.
23+
/// </summary>
24+
public string Username { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the password.
28+
/// </summary>
29+
public string Password { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the provider name.
33+
/// </summary>
34+
public string ProviderName { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the provider subject identifier.
38+
/// </summary>
39+
public string ProviderSubjectId { get; set; }
40+
41+
/// <summary>
42+
/// Gets or sets the claims.
43+
/// </summary>
44+
public ICollection<Claim> Claims { get; set; } = new HashSet<Claim>(new ClaimComparer());
45+
}
46+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.Extensions;
6+
using IdentityServer4.Models;
7+
using IdentityServer4.Services;
8+
using System.Threading.Tasks;
9+
10+
namespace IdentityServer4.Quickstart.UI.Users
11+
{
12+
public class TestUserProfileService : IProfileService
13+
{
14+
private readonly TestUserStore _users;
15+
16+
public TestUserProfileService(TestUserStore users)
17+
{
18+
_users = users;
19+
}
20+
21+
public Task GetProfileDataAsync(ProfileDataRequestContext context)
22+
{
23+
var user = _users.FindBySubjectId(context.Subject.GetSubjectId());
24+
25+
context.AddFilteredClaims(user.Claims);
26+
27+
return Task.FromResult(0);
28+
}
29+
30+
public Task IsActiveAsync(IsActiveContext context)
31+
{
32+
context.IsActive = true;
33+
34+
return Task.FromResult(0);
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.Validation;
7+
using System.Threading.Tasks;
8+
9+
namespace IdentityServer4.Quickstart.UI.Users
10+
{
11+
public class TestUserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
12+
{
13+
private readonly TestUserStore _users;
14+
15+
public TestUserResourceOwnerPasswordValidator(TestUserStore users)
16+
{
17+
_users = users;
18+
}
19+
20+
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
21+
{
22+
if (_users.ValidateCredentials(context.UserName, context.Password))
23+
{
24+
var user = _users.FindByUsername(context.UserName);
25+
context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password, user.Claims);
26+
}
27+
28+
return Task.FromResult(0);
29+
}
30+
}
31+
}

‎src/Host/Users/TestUserStore.cs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 System.Collections.Generic;
7+
using System.IdentityModel.Tokens.Jwt;
8+
using System.Linq;
9+
using System.Security.Claims;
10+
using System;
11+
12+
namespace IdentityServer4.Quickstart.UI.Users
13+
{
14+
public class TestUserStore
15+
{
16+
private readonly List<TestUser> _users;
17+
18+
public TestUserStore(List<TestUser> users)
19+
{
20+
_users = users;
21+
}
22+
23+
public bool ValidateCredentials(string username, string password)
24+
{
25+
var user = FindByUsername(username);
26+
if (user != null)
27+
{
28+
return user.Password.Equals(password);
29+
}
30+
31+
return false;
32+
}
33+
34+
public TestUser FindBySubjectId(string subjectId)
35+
{
36+
return _users.FirstOrDefault(x => x.SubjectId == subjectId);
37+
}
38+
39+
public TestUser FindByUsername(string username)
40+
{
41+
return _users.FirstOrDefault(x => x.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
42+
}
43+
44+
public TestUser FindByExternalProvider(string provider, string userId)
45+
{
46+
return _users.FirstOrDefault(x =>
47+
x.ProviderName == provider &&
48+
x.ProviderSubjectId == userId);
49+
}
50+
51+
public TestUser AutoProvisionUser(string provider, string userId, List<Claim> claims)
52+
{
53+
// create a list of claims that we want to transfer into our store
54+
var filtered = new List<Claim>();
55+
56+
foreach (var claim in claims)
57+
{
58+
// if the external system sends a display name - translate that to the standard OIDC name claim
59+
if (claim.Type == ClaimTypes.Name)
60+
{
61+
filtered.Add(new Claim(JwtClaimTypes.Name, claim.Value));
62+
}
63+
// if the JWT handler has an outbound mapping to an OIDC claim use that
64+
else if (JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.ContainsKey(claim.Type))
65+
{
66+
filtered.Add(new Claim(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[claim.Type], claim.Value));
67+
}
68+
// copy the claim as-is
69+
else
70+
{
71+
filtered.Add(claim);
72+
}
73+
}
74+
75+
// if no display name was provided, try to construct by first and/or last name
76+
if (!filtered.Any(x => x.Type == JwtClaimTypes.Name))
77+
{
78+
var first = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value;
79+
var last = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value;
80+
if (first != null && last != null)
81+
{
82+
filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last));
83+
}
84+
else if (first != null)
85+
{
86+
filtered.Add(new Claim(JwtClaimTypes.Name, first));
87+
}
88+
else if (last != null)
89+
{
90+
filtered.Add(new Claim(JwtClaimTypes.Name, last));
91+
}
92+
}
93+
94+
// create a new unique subject id
95+
var sub = CryptoRandom.CreateUniqueId();
96+
97+
// check if a display name is available, otherwise fallback to subject id
98+
var name = filtered.FirstOrDefault(c => c.Type == JwtClaimTypes.Name)?.Value ?? sub;
99+
100+
// create new user
101+
var user = new TestUser()
102+
{
103+
SubjectId = sub,
104+
Username = name,
105+
ProviderName = provider,
106+
ProviderSubjectId = userId,
107+
Claims = filtered
108+
};
109+
110+
// add user to in-memory store
111+
_users.Add(user);
112+
113+
return user;
114+
}
115+
}
116+
}

‎src/Host/Users/TestUsers.cs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 System.Collections.Generic;
7+
using System.Security.Claims;
8+
9+
namespace IdentityServer4.Quickstart.UI.Users
10+
{
11+
public class TestUsers
12+
{
13+
public static List<TestUser> Users = new List<TestUser>
14+
{
15+
new TestUser{SubjectId = "818727", Username = "alice", Password = "alice",
16+
Claims =
17+
{
18+
new Claim(JwtClaimTypes.Name, "Alice Smith"),
19+
new Claim(JwtClaimTypes.GivenName, "Alice"),
20+
new Claim(JwtClaimTypes.FamilyName, "Smith"),
21+
new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
22+
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
23+
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
24+
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServerConstants.ClaimValueTypes.Json)
25+
}
26+
},
27+
new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob",
28+
Claims =
29+
{
30+
new Claim(JwtClaimTypes.Name, "Bob Smith"),
31+
new Claim(JwtClaimTypes.GivenName, "Bob"),
32+
new Claim(JwtClaimTypes.FamilyName, "Smith"),
33+
new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
34+
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
35+
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
36+
new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServerConstants.ClaimValueTypes.Json),
37+
new Claim("location", "somewhere"),
38+
}
39+
},
40+
};
41+
}
42+
}

‎src/Host/Views/Account/LoggedOut.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.LoggedOutViewModel
1+
@model LoggedOutViewModel
22

33
<div class="page-header logged-out">
44
<h1>

‎src/Host/Views/Account/Login.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.LoginViewModel
1+
@model LoginViewModel
22

33
<div class="login-page">
44
<div class="page-header">

‎src/Host/Views/Account/Logout.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.LogoutViewModel
1+
@model LogoutViewModel
22

33
<div class="logout-page">
44
<div class="page-header">

‎src/Host/Views/Consent/Index.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.ConsentViewModel
1+
@model ConsentViewModel
22

33
<div class="page-consent">
44
<div class="row page-header">

‎src/Host/Views/Consent/_ScopeListItem.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.ScopeViewModel
1+
@model ScopeViewModel
22

33
<li class="list-group-item">
44
<label>

‎src/Host/Views/Shared/Error.cshtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@model IdentityServer4.Quickstart.UI.Models.ErrorViewModel
1+
@model ErrorViewModel
22

33
@{
44
var error = Model?.Error?.Error;

‎src/Host/Views/Shared/_Layout.cshtml

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
<!DOCTYPE html>
1+
@using IdentityServer4.Extensions
2+
@{
3+
string name = null;
4+
if (!true.Equals(ViewData["signed-out"]))
5+
{
6+
var user = await Context.GetIdentityServerUserAsync();
7+
name = user?.FindFirst("name")?.Value;
8+
}
9+
}
10+
<!DOCTYPE html>
211
<html>
312
<head>
413
<meta charset="utf-8" />
@@ -27,11 +36,11 @@
2736
</a>
2837
</div>
2938

30-
@if (User.Identity.IsAuthenticated)
39+
@if (name != null)
3140
{
3241
<ul class="nav navbar-nav">
3342
<li class="dropdown">
34-
<a href="#" class="dropdown-toggle" data-toggle="dropdown">@User.Identity.Name <b class="caret"></b></a>
43+
<a href="#" class="dropdown-toggle" data-toggle="dropdown">@name <b class="caret"></b></a>
3544
<ul class="dropdown-menu">
3645
<li><a asp-action="Logout" asp-controller="Account">Logout</a></li>
3746
</ul>

‎src/Host/Views/_ViewImports.cshtml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
1+
@using IdentityServer4.Quickstart.UI.Models
2+
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

‎src/IdentityServer4.EntityFramework/Entities/Client.cs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class Client
2121
public string LogoUri { get; set; }
2222
public bool RequireConsent { get; set; } = true;
2323
public bool AllowRememberConsent { get; set; } = true;
24+
public bool AlwaysIncludeUserClaimsInIdToken { get; set; }
2425
public List<ClientGrantType> AllowedGrantTypes { get; set; }
2526
public bool RequirePkce { get; set; }
2627
public bool AllowPlainTextPkce { get; set; }

‎src/IdentityServer4.EntityFramework/Entities/PersistedGrant.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class PersistedGrant
1313
public string SubjectId { get; set; }
1414
public string ClientId { get; set; }
1515
public DateTime CreationTime { get; set; }
16-
public DateTime Expiration { get; set; }
16+
public DateTime? Expiration { get; set; }
1717
public string Data { get; set; }
1818
}
1919
}

‎src/IdentityServer4.EntityFramework/project.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212

1313
"dependencies": {
14-
"IdentityServer4": "1.0.0-rc5",
14+
"IdentityServer4": "1.0.0-rc5-build00613",
1515
"Microsoft.EntityFrameworkCore": "1.1.0",
1616
"Microsoft.EntityFrameworkCore.Relational": "1.1.0",
1717
"AutoMapper": "5.1.1"

‎test/IdentityServer4.EntityFramework.IntegrationTests/Stores/PersistedGrantStoreTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public void Store_should_update_record_if_key_already_exists(DbContextOptions<Pe
213213
context.SaveChanges();
214214
}
215215

216-
var newDate = persistedGrant.Expiration.AddHours(1);
216+
var newDate = persistedGrant.Expiration.Value.AddHours(1);
217217
using (var context = new PersistedGrantDbContext(options, StoreOptions))
218218
{
219219
var store = new PersistedGrantStore(context, FakeLogger<PersistedGrantStore>.Create());

0 commit comments

Comments
 (0)
Please sign in to comment.