forked from gitter-badger/IdentityServer4
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathClient.cs
More file actions
366 lines (305 loc) · 13.8 KB
/
Client.cs
File metadata and controls
366 lines (305 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using System.Collections.Generic;
using System.Security.Claims;
using System.Linq;
using System;
using IdentityServer4.Extensions;
using IdentityModel;
using System.Collections;
namespace IdentityServer4.Models
{
/// <summary>
/// Models an OpenID Connect or OAuth2 client
/// </summary>
public class Client
{
// setting grant types should be atomic
private ICollection<string> _allowedGrantTypes = new GrantTypeValidatingHashSet(GrantTypes.Implicit);
/// <summary>
/// Specifies if client is enabled (defaults to true)
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Unique ID of the client
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Gets or sets the protocol type.
/// </summary>
/// <value>
/// The protocol type.
/// </value>
public string ProtocolType { get; set; } = IdentityServerConstants.ProtocolTypes.OpenIdConnect;
/// <summary>
/// Client secrets - only relevant for flows that require a secret
/// </summary>
public ICollection<Secret> ClientSecrets { get; set; } = new HashSet<Secret>();
/// <summary>
/// If set to false, no client secret is needed to request tokens at the token endpoint (defaults to true)
/// </summary>
public bool RequireClientSecret { get; set; } = true;
/// <summary>
/// Client display name (used for logging and consent screen)
/// </summary>
public string ClientName { get; set; }
/// <summary>
/// URI to further information about client (used on consent screen)
/// </summary>
public string ClientUri { get; set; }
/// <summary>
/// URI to client logo (used on consent screen)
/// </summary>
public string LogoUri { get; set; }
/// <summary>
/// Specifies whether a consent screen is required (defaults to true)
/// </summary>
public bool RequireConsent { get; set; } = true;
/// <summary>
/// Specifies whether user can choose to store consent decisions (defaults to true)
/// </summary>
public bool AllowRememberConsent { get; set; } = true;
/// <summary>
/// Specifies the allowed grant types (legal combinations of AuthorizationCode, Implicit, Hybrid, ResourceOwner, ClientCredentials). Defaults to Implicit.
/// </summary>
public ICollection<string> AllowedGrantTypes
{
get { return _allowedGrantTypes; }
set
{
ValidateGrantTypes(value);
_allowedGrantTypes = new GrantTypeValidatingHashSet(value);
}
}
/// <summary>
/// Specifies whether a proof key is required for authorization code based token requests
/// </summary>
public bool RequirePkce { get; set; } = false;
/// <summary>
/// Specifies whether a proof key can be sent using plain method (not recommended and default to false)
/// </summary>
public bool AllowPlainTextPkce { get; set; } = false;
/// <summary>
/// Controls whether access tokens are transmitted via the browser for this client (defaults to false).
/// This can prevent accidental leakage of access tokens when multiple response types are allowed.
/// </summary>
/// <value>
/// <c>true</c> if access tokens can be transmitted via the browser; otherwise, <c>false</c>.
/// </value>
public bool AllowAccessTokensViaBrowser { get; set; } = false;
/// <summary>
/// Specifies allowed URIs to return tokens or authorization codes to
/// </summary>
public ICollection<string> RedirectUris { get; set; } = new HashSet<string>();
/// <summary>
/// Specifies allowed URIs to redirect to after logout
/// </summary>
public ICollection<string> PostLogoutRedirectUris { get; set; } = new HashSet<string>();
/// <summary>
/// Specifies logout URI at client for HTTP based logout.
/// </summary>
public string LogoutUri { get; set; }
/// <summary>
/// Specifies is the user's session id should be sent to the LogoutUri. Defaults to true.
/// </summary>
public bool LogoutSessionRequired { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether [allow offline access].
/// </summary>
public bool AllowOfflineAccess { get; set; } = false;
/// <summary>
/// Specifies the api scopes that the client is allowed to request. If empty, the client can't access any scope
/// </summary>
public ICollection<string> AllowedScopes { get; set; } = new HashSet<string>();
/// <summary>
/// When requesting both an id token and access token, should the user claims always be added to the id token instead of requring the client to use the userinfo endpoint.
/// </summary>
public bool AlwaysIncludeUserClaimsInIdToken { get; set; } = false;
/// <summary>
/// Lifetime of identity token in seconds (defaults to 300 seconds / 5 minutes)
/// </summary>
public int IdentityTokenLifetime { get; set; } = 300;
/// <summary>
/// Lifetime of access token in seconds (defaults to 3600 seconds / 1 hour)
/// </summary>
public int AccessTokenLifetime { get; set; } = 3600;
/// <summary>
/// Lifetime of authorization code in seconds (defaults to 300 seconds / 5 minutes)
/// </summary>
public int AuthorizationCodeLifetime { get; set; } = 300;
/// <summary>
/// Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days
/// </summary>
public int AbsoluteRefreshTokenLifetime { get; set; } = 2592000;
/// <summary>
/// Sliding lifetime of a refresh token in seconds. Defaults to 1296000 seconds / 15 days
/// </summary>
public int SlidingRefreshTokenLifetime { get; set; } = 1296000;
/// <summary>
/// Lifetime of a user consent in seconds. Defaults to null (no expiration)
/// </summary>
public int? ConsentLifetime { get; set; } = null;
/// <summary>
/// ReUse: the refresh token handle will stay the same when refreshing tokens
/// OneTime: the refresh token handle will be updated when refreshing tokens
/// </summary>
public TokenUsage RefreshTokenUsage { get; set; } = TokenUsage.OneTimeOnly;
/// <summary>
/// Gets or sets a value indicating whether the access token (and its claims) should be updated on a refresh token request.
/// </summary>
/// <value>
/// <c>true</c> if the token should be updated; otherwise, <c>false</c>.
/// </value>
public bool UpdateAccessTokenClaimsOnRefresh { get; set; } = false;
/// <summary>
/// Absolute: the refresh token will expire on a fixed point in time (specified by the AbsoluteRefreshTokenLifetime)
/// Sliding: when refreshing the token, the lifetime of the refresh token will be renewed (by the amount specified in SlidingRefreshTokenLifetime). The lifetime will not exceed AbsoluteRefreshTokenLifetime.
/// </summary>
public TokenExpiration RefreshTokenExpiration { get; set; } = TokenExpiration.Absolute;
/// <summary>
/// Specifies whether the access token is a reference token or a self contained JWT token (defaults to Jwt).
/// </summary>
public AccessTokenType AccessTokenType { get; set; } = AccessTokenType.Jwt;
/// <summary>
/// Gets or sets a value indicating whether the local login is allowed for this client. Defaults to <c>true</c>.
/// </summary>
/// <value>
/// <c>true</c> if local logins are enabled; otherwise, <c>false</c>.
/// </value>
public bool EnableLocalLogin { get; set; } = true;
/// <summary>
/// Specifies which external IdPs can be used with this client (if list is empty all IdPs are allowed). Defaults to empty.
/// </summary>
public ICollection<string> IdentityProviderRestrictions { get; set; } = new HashSet<string>();
/// <summary>
/// Gets or sets a value indicating whether JWT access tokens should include an identifier
/// </summary>
/// <value>
/// <c>true</c> to add an id; otherwise, <c>false</c>.
/// </value>
public bool IncludeJwtId { get; set; } = false;
/// <summary>
/// Allows settings claims for the client (will be included in the access token).
/// </summary>
/// <value>
/// The claims.
/// </value>
public ICollection<Claim> Claims { get; set; } = new HashSet<Claim>(new ClaimComparer());
/// <summary>
/// Gets or sets a value indicating whether client claims should be always included in the access tokens - or only for client credentials flow.
/// </summary>
/// <value>
/// <c>true</c> if claims should always be sent; otherwise, <c>false</c>.
/// </value>
public bool AlwaysSendClientClaims { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether all client claims should be prefixed.
/// </summary>
/// <value>
/// <c>true</c> if client claims should be prefixed; otherwise, <c>false</c>.
/// </value>
public bool PrefixClientClaims { get; set; } = true;
/// <summary>
/// Gets or sets the allowed CORS origins for JavaScript clients.
/// </summary>
/// <value>
/// The allowed CORS origins.
/// </value>
public ICollection<string> AllowedCorsOrigins { get; set; } = new HashSet<string>();
/// <summary>
/// Validates the grant types.
/// </summary>
/// <param name="grantTypes">The grant types.</param>
/// <exception cref="System.InvalidOperationException">
/// Grant types list is empty
/// or
/// Grant types cannot contain spaces
/// or
/// Grant types list contains duplicate values
/// </exception>
public static void ValidateGrantTypes(IEnumerable<string> grantTypes)
{
// must set at least one grant type
if (grantTypes.IsNullOrEmpty())
{
throw new InvalidOperationException("Grant types list is empty");
}
// spaces are not allowed in grant types
foreach (var type in grantTypes)
{
if (type.Contains(' '))
{
throw new InvalidOperationException("Grant types cannot contain spaces");
}
}
// single grant type, seems to be fine
if (grantTypes.Count() == 1) return;
// don't allow duplicate grant types
if (grantTypes.Count() != grantTypes.Distinct().Count())
{
throw new InvalidOperationException("Grant types list contains duplicate values");
}
// would allow response_type downgrade attack from code to token
DisallowGrantTypeCombination(GrantType.Implicit, GrantType.AuthorizationCode, grantTypes);
DisallowGrantTypeCombination(GrantType.Implicit, GrantType.Hybrid, grantTypes);
DisallowGrantTypeCombination(GrantType.AuthorizationCode, GrantType.Hybrid, grantTypes);
}
private static void DisallowGrantTypeCombination(string value1, string value2, IEnumerable<string> grantTypes)
{
if (grantTypes.Contains(value1, StringComparer.Ordinal) &&
grantTypes.Contains(value2, StringComparer.Ordinal))
{
throw new InvalidOperationException($"Grant types list cannot contain both {value1} and {value2}");
}
}
internal class GrantTypeValidatingHashSet : ICollection<string>
{
private readonly ICollection<string> _inner;
public GrantTypeValidatingHashSet(IEnumerable<string> values)
{
_inner = new HashSet<string>(values);
}
ICollection<string> Clone()
{
return new HashSet<string>(this);
}
ICollection<string> CloneWith(params string[] values)
{
var clone = Clone();
foreach (var item in values) clone.Add(item);
return clone;
}
public int Count => _inner.Count;
public bool IsReadOnly => _inner.IsReadOnly;
public void Add(string item)
{
Client.ValidateGrantTypes(CloneWith(item));
_inner.Add(item);
}
public void Clear()
{
_inner.Clear();
}
public bool Contains(string item)
{
return _inner.Contains(item);
}
public void CopyTo(string[] array, int arrayIndex)
{
_inner.CopyTo(array, arrayIndex);
}
public IEnumerator<string> GetEnumerator()
{
return _inner.GetEnumerator();
}
public bool Remove(string item)
{
return _inner.Remove(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return _inner.GetEnumerator();
}
}
}
}