Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
OAuthTokenResource.cs
1using System;
3using System.Diagnostics.CodeAnalysis;
4using System.Text;
5using System.Threading.Tasks;
6using Waher.Content;
7using Waher.Events;
12using Waher.Security;
16
18{
24 {
28 public const string DefaultResourcePath = "/oauth/token";
29
30 private static readonly Cache<string, TokenRef> codes = new Cache<string, TokenRef>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
31 private static readonly Cache<string, TokenFamily> refreshTokens = new Cache<string, TokenFamily>(int.MaxValue, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
32 private static readonly Cache<string, TokenFamily> usedRefreshTokens = new Cache<string, TokenFamily>(int.MaxValue, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
33
40 {
41 }
42
50 {
52 }
53
57 public bool AllowsGET => true;
58
62 public bool AllowsPOST => true;
63
64 internal async Task<string> GenerateTokenCode(IUserWithClaims User, bool Encrypted,
65 string CodeChallenge, string CodeChallengeMethod, string RedirectUri,
66 string Scope)
67 {
68 string Token = await this.CreateToken(User, Encrypted, Scope);
69 string Code = this.GenerateRandomCode();
70
71 codes[Code] = new TokenRef(Token, User, CodeChallenge, CodeChallengeMethod,
72 RedirectUri, 3600, Scope);
73
74 return Code;
75 }
76
81 private string GenerateRandomCode()
82 {
83 string Code;
84
85 do
86 {
88 }
89 while (
90 codes.ContainsKey(Code) ||
91 refreshTokens.ContainsKey(Code) ||
92 usedRefreshTokens.ContainsKey(Code));
93
94 return Code;
95 }
96
97 private class TokenRef
98 {
99 public TokenRef(string Token, IUserWithClaims User, string CodeChallenge,
100 string CodeChallengeMethod, string RedirectUri, int ExpiresIn, string Scope)
101 {
102 this.Token = Token;
103 this.User = User;
104 this.CodeChallenge = CodeChallenge;
105 this.CodeChallengeMethod = CodeChallengeMethod;
106 this.RedirectUri = RedirectUri;
107 this.ExpiresIn = ExpiresIn;
108 this.Scope = Scope;
109 }
110
111 public string Token;
112 public string CodeChallenge;
113 public string CodeChallengeMethod;
114 public string RedirectUri;
115 public string Scope;
116 public IUserWithClaims User;
117 public int ExpiresIn;
118
119 public async Task<bool> Check(string CodeVerifier, HttpResponse Response)
120 {
121 switch (this.CodeChallengeMethod)
122 {
123 case "plain":
124 if (CodeVerifier != this.CodeChallenge)
125 {
126 await Forbidden(Response, "invalid_grant",
127 "Invalid code_verifier.");
128 return false;
129 }
130 break;
131
132 case "S256":
133 string ExpectedCodeChallenge = Base64Url.Encode(
134 Hashes.ComputeSHA256Hash(Encoding.UTF8.GetBytes(CodeVerifier)));
135
136 if (ExpectedCodeChallenge != this.CodeChallenge)
137 {
138 await Forbidden(Response, "invalid_grant",
139 "Invalid code_verifier.");
140 return false;
141 }
142 break;
143
144 default:
145 await BadRequest(Response, "invalid_request",
146 "Unsupported code_challenge_method: " + this.CodeChallengeMethod);
147 return false;
148 }
149
150 return true;
151 }
152 }
153
160 public async Task GET(HttpRequest Request, HttpResponse Response)
161 {
162 if (OAuthAuthorizeResource.HasDuplicateQueryParameters(Request))
163 {
164 await BadRequest(Response, "invalid_request",
165 "Duplicate query parameters.");
166 return;
167 }
168
169 if (!Request.Header.TryGetQueryParameter("code", out string Code))
170 {
171 await BadRequest(Response, "invalid_request", "Missing code.");
172 return;
173 }
174
175 if (!codes.TryGetValue(Code, out TokenRef Ref))
176 {
177 await Forbidden(Response, "invalid_grant", "Invalid code.");
178 return;
179 }
180
181 if (!string.IsNullOrEmpty(Ref.CodeChallenge))
182 {
183 if (!Request.Header.TryGetQueryParameter("code_verifier", out string CodeVerifier))
184 {
185 await BadRequest(Response, "invalid_request", "Missing code_verifier.");
186 return;
187 }
188
189 if (!await Ref.Check(CodeVerifier, Response))
190 return;
191 }
192
193 codes.Remove(Code);
194
195 Response.SetHeader("Cache-Control", "max-age=0, no-cache, no-store");
196 Response.SetHeader("Pragma", "no-cache");
197
198 await Response.Return(this.TokenResponse(Ref.Token, null, Ref.ExpiresIn,
199 Ref.Scope, this.JwtFactory.Issuer, true, Ref.User, Request));
200 }
201
208 public async Task POST(HttpRequest Request, HttpResponse Response)
209 {
210 if (!Request.HasData)
211 {
212 await BadRequest(Response, "invalid_request", "No payload in request.");
213 return;
214 }
215
216 ContentResponse Content = await Request.DecodeDataAsync();
217 if (Content.HasError || !(Content.Decoded is Dictionary<string, string> Form))
218 {
219 await BadRequest(Response, "invalid_request",
220 "Expected URL-encoded WWW form.");
221 return;
222 }
223
224 if (!Form.TryGetValue("grant_type", out string GrantType))
225 {
226 await BadRequest(Response, "invalid_request", "Missing grant_type.");
227 return;
228 }
229
230 if (!Form.TryGetValue("scope", out string Scope))
231 Scope = string.Empty;
232 else if (!IsValidScope(Scope))
233 {
234 await BadRequest(Response, "invalid_scope", "Invalid scope parameter.");
235 return;
236 }
237
238 string ClientId;
239 string Token;
241 TokenFamily? TokenFamily = null;
242 bool IssueRefreshToken = true;
243
244 switch (GrantType)
245 {
246 case "authorization_code":
247 if (!Form.TryGetValue("code", out string Code))
248 {
249 await BadRequest(Response, "invalid_request", "Missing code.");
250 return;
251 }
252
253 if (!codes.TryGetValue(Code, out TokenRef Ref))
254 {
255 await Forbidden(Response, "invalid_grant", "Invalid code.");
256 return;
257 }
258
259 if (!Form.TryGetValue("redirect_uri", out string RedirectUri))
260 {
261 await BadRequest(Response, "invalid_request", "Missing redirect_uri.");
262 return;
263 }
264
265 if (!TryGetClientId(Request, Form, out ClientId))
266 {
267 await BadRequest(Response, "invalid_request", "Missing client_id.");
268 return;
269 }
270
271 if (ClientId != Ref.User.UserName)
272 {
273 await Forbidden(Response, "access_denied", "Access denied");
274 return;
275 }
276
277 if (Ref.RedirectUri != RedirectUri)
278 {
279 await Forbidden(Response, "access_denied", "Access denied");
280 return;
281 }
282
283 if (!string.IsNullOrEmpty(Ref.CodeChallenge))
284 {
285 if (!Form.TryGetValue("code_verifier", out string CodeVerifier))
286 {
287 await BadRequest(Response, "invalid_request", "Missing code_verifier.");
288 return;
289 }
290
291 if (!await Ref.Check(CodeVerifier, Response))
292 return;
293 }
294
295 codes.Remove(Code);
296 Token = Ref.Token;
297 User = Ref.User;
298 Scope = Ref.Scope;
299 break;
300
301 case "client_credentials":
302 case "password":
303 string ClientSecret = string.Empty;
304 string InvalidGrantCode;
305 bool HasCredentials;
306
307 if (GrantType == "password")
308 {
309 InvalidGrantCode = "invalid_grant";
310 HasCredentials = Form.TryGetValue("username", out ClientId) &&
311 Form.TryGetValue("password", out ClientSecret);
312 }
313 else
314 {
315 InvalidGrantCode = "invalid_client";
316 IssueRefreshToken = false;
317
318 if (Request.User is null)
319 {
320 HasCredentials = Form.TryGetValue("client_id", out ClientId) &&
321 Form.TryGetValue("client_secret", out ClientSecret);
322 }
323 else
324 {
325 if (Form.ContainsKey("client_id") ||
326 Form.ContainsKey("client_secret"))
327 {
328 await BadRequest(Response, "invalid_request",
329 "Invalid request parameters.");
330 return;
331 }
332
333 if (!(Request.User is IUserWithClaims UserWithClaims))
334 {
336 this.ResourceName, Request.RemoteEndPoint));
337 return;
338 }
339
340 User = UserWithClaims;
341 Token = await this.CreateToken(UserWithClaims, Request.Encrypted, Scope);
342 break;
343 }
344 }
345
346 if (HasCredentials)
347 {
348 if (!Request.Encrypted && (Request.Server.OpenHttpsPorts?.Length ?? 0) > 0)
349 {
350 await Forbidden(Response, "invalid_request",
351 "Request must be performed over an encrypted connection.");
352 return;
353 }
354
355 if (Request.Encrypted && Request.CipherStrength < 128)
356 {
357 await Forbidden(Response, "invalid_request",
358 "Cipher strength too weak.");
359 return;
360 }
361
362 this.InitAuthentication();
363
364 LoginResult? LoginResult = await DoLogin(ClientId, ClientSecret,
365 this.Users!, Request, this.Realm ?? string.Empty);
366
367 if (LoginResult is null)
368 {
369 await Forbidden(Response, InvalidGrantCode,
370 "User cannot authenticate via this interface.");
371 return;
372 }
373
374 switch (LoginResult.Type)
375 {
376 case LoginResultType.Success:
377 Request.User = LoginResult.User;
378 break;
379
380 case LoginResultType.InvalidCredentials:
381 default:
382 await Forbidden(Response, InvalidGrantCode,
383 "Invalid client_id or client_secret.");
384 return;
385
386 case LoginResultType.NoPassword:
387 await Forbidden(Response, InvalidGrantCode,
388 "No or empty client_secret.");
389 return;
390
391 case LoginResultType.TemporarilyBlocked:
392 await Forbidden(Response, InvalidGrantCode,
393 "Temporarily blocked. Try again after: " +
394 LoginResult.Next?.ToString());
395 return;
396
397 case LoginResultType.PermanentlyBlocked:
398 await Forbidden(Response, InvalidGrantCode,
399 "Permanently blocked.");
400 return;
401 }
402
403 if (!(Request.User is IUserWithClaims UserWithClaims))
404 {
406 this.ResourceName, Request.RemoteEndPoint));
407 return;
408 }
409
410 User = UserWithClaims;
411 Token = await this.CreateToken(UserWithClaims, Request.Encrypted, Scope);
412 }
413 else
414 {
415 await BadRequest(Response, "invalid_request",
416 "Missing credentials.");
417 return;
418 }
419
420 if (!HasScopePrivileges(Scope, User, out string? MissingPrivilege))
421 {
422 await Forbidden(Response, "access_denied",
423 "User lacks privilege: " + MissingPrivilege);
424 return;
425 }
426 break;
427
428 case "refresh_token":
429 if (!Form.TryGetValue("refresh_token", out string RefreshToken))
430 {
431 await BadRequest(Response, "invalid_request",
432 "Missing refresh_token.");
433 return;
434 }
435
436 if (!refreshTokens.TryGetValue(RefreshToken, out TokenFamily))
437 {
438 if (usedRefreshTokens.TryGetValue(RefreshToken, out TokenFamily))
439 {
440 string Message = "Attempt to reuse refresh token. Has the token leaked? Deprecating all associated tokens.";
441
442 LoginAuditor.Fail(Message, TokenFamily.User.UserName,
443 Request.RemoteEndPoint, "OAUTH");
444
445 Log.Alert(Message, TokenFamily.User.UserName,
446 Request.RemoteEndPoint, "TokenLeakage",
447 await LoginAuditor.Annotate(Request.RemoteEndPoint));
448
449 foreach (string Token2 in TokenFamily.Tokens)
450 {
451 if (JwtToken.TryParse(Token2, out JwtToken ParsedToken))
452 JwtFactory.Deprecate(ParsedToken);
453 }
454
455 usedRefreshTokens.Remove(RefreshToken);
456 }
457
458 await Forbidden(Response, "access_denied",
459 "Invalid refresh_token.");
460 return;
461 }
462
463 if (!TryGetClientId(Request, Form, out ClientId))
464 {
465 await BadRequest(Response, "invalid_request", "Missing client_id.");
466 return;
467 }
468
469 if (!TokenFamily.CanUseRefreshToken(ClientId, Request))
470 {
471 await Forbidden(Response, "access_denied", "Access denied");
472 return;
473 }
474
475 if (string.IsNullOrEmpty(Scope))
476 Scope = (TokenFamily.Scopes?.Length ?? 0) == 0 ? string.Empty : string.Join(' ', TokenFamily.Scopes);
477 else
478 {
479 string[] NewScopes = Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
480
481 foreach (string Scope2 in NewScopes)
482 {
483 if (Array.IndexOf(TokenFamily.Scopes, Scope2) < 0)
484 {
485 await Forbidden(Response, "invalid_scope",
486 "Not permitted to escalate scope.");
487 return;
488 }
489 }
490
491 TokenFamily.Scopes = NewScopes;
492 }
493
494 refreshTokens.Remove(RefreshToken);
495 usedRefreshTokens.Add(RefreshToken, TokenFamily);
496
497 User = TokenFamily.User;
498 Token = await this.CreateToken(User, Request.Encrypted, Scope);
499 break;
500
503 {
504 await ServiceUnavailable(Response, "server_error",
505 "Device authorization not configured.");
506 return;
507 }
508
509 if (!Form.TryGetValue("device_code", out string DeviceCode))
510 {
511 await BadRequest(Response, "invalid_request", "Missing device_code.");
512 return;
513 }
514
515 if (!TryGetClientId(Request, Form, out ClientId))
516 {
517 await BadRequest(Response, "invalid_request", "Missing client_id.");
518 return;
519 }
520
521 if (!this.Environment.DeviceAuthorizationResource.TryGetDeviceReference(
522 DeviceCode, out OAuthDeviceAuthorizationResource.DeviceRef? DeviceReference))
523 {
524 await Forbidden(Response, "expired_token", "Invalid device_code, or token has expired.");
525 return;
526 }
527
528 if (ClientId != DeviceReference.Device.UserName)
529 {
530 await Forbidden(Response, "access_denied", "Invalid client_id.");
531 return;
532 }
533
534 DateTime TP = DateTime.UtcNow;
535
536 if (DeviceReference.LastPoll.HasValue &&
537 TP.Subtract(DeviceReference.LastPoll.Value).TotalSeconds <
539 {
540 await BadRequest(Response, "slow_down", "Polling too fast. Slow down.");
541 return;
542 }
543
544 DeviceReference.LastPoll = TP;
545
546 if (!DeviceReference.Result.HasValue)
547 {
548 await BadRequest(Response, "authorization_pending", "Authorization has not yet been granted by owner.");
549 return;
550 }
551
552 if (!DeviceReference.Result.Value)
553 {
554 await Forbidden(Response, "access_denied", "Access has been denied by owner.");
555 return;
556 }
557
558 if (!HasScopePrivileges(Scope, DeviceReference.Owner, out MissingPrivilege))
559 {
560 await Forbidden(Response, "access_denied",
561 "Owner lacks privilege: " + MissingPrivilege);
562 return;
563 }
564
565 User = DeviceReference.Device;
566 Scope = DeviceReference.Scope;
567
568 Token = await this.CreateToken(User, Request.Encrypted, Scope);
569
570 DeviceReference.Remove();
571 break;
572
573 default:
574 await BadRequest(Response, "unsupported_grant_type",
575 "Unsupported grant_type: " + GrantType);
576 return;
577 }
578
579 Response.SetHeader("Cache-Control", "max-age=0, no-cache, no-store");
580 Response.SetHeader("Pragma", "no-cache");
581
582 await Response.Return(this.TokenResponse(Token, null, 3600,
583 Scope, this.JwtFactory.Issuer, IssueRefreshToken, User,
584 Request, TokenFamily));
585 }
586
587 private static bool TryGetClientId(HttpRequest Request, Dictionary<string, string> Form,
588 out string ClientId)
589 {
590 if (Form.TryGetValue("client_id", out ClientId))
591 return true;
592
593 string s;
594
595 if (!(Request.Header.Authorization is null) &&
596 (s = Request.Header.Authorization.Value).StartsWith("Basic "))
597 {
598 s = Encoding.UTF8.GetString(Convert.FromBase64String(s[6..]));
599 int i = s.IndexOf(':');
600 if (i > 0)
601 {
602 ClientId = s.Substring(0, i);
603 return true;
604 }
605 }
606
607 ClientId = string.Empty;
608 return false;
609 }
610
611 private async Task<string> CreateToken(IUserWithClaims User, bool Encrypted,
612 string Scope)
613 {
614 return await CreateToken(User, Encrypted, this.JwtFactory, Scope);
615 }
616
617 internal static async Task<string> CreateToken(IUserWithClaims User, bool Encrypted,
618 JwtFactory JwtFactory, string Scope)
619 {
620 if (string.IsNullOrEmpty(Scope))
621 return await User.CreateToken(JwtFactory, Encrypted);
622 else
623 {
624 return await User.CreateToken(JwtFactory, Encrypted,
625 new KeyValuePair<string, object>(JwtClaims.Scope, Scope));
626 }
627 }
628
629 internal static async Task<LoginResult?> DoLogin(string UserName, string Password,
630 IUserSource Users, HttpRequest Request, string Realm)
631 {
632 if (string.IsNullOrEmpty(Password))
633 return new LoginResult();
634
635 if (!(Request.Server.LoginAuditor is null))
636 {
637 DateTime? Next = await Request.Server.LoginAuditor.GetEarliestLoginOpportunity(
638 Request.RemoteEndPoint, "OAUTH");
639
640 if (Next.HasValue)
641 return new LoginResult(Next.Value);
642 }
643
644 IUser User = await Users.TryGetUser(UserName);
645 if (User is null)
646 {
647 LoginAuditor.Fail("Login attempt using invalid user name.", UserName, Request.RemoteEndPoint, "OAUTH",
648 new KeyValuePair<string, object>("UserName", UserName));
649 return new LoginResult(User);
650 }
651
652 string PasswordHash = BasicAuthentication.ComputePasswordHash(UserName,
653 Realm, Password, User.PasswordHashType, out byte? HashBytes);
654
655 string ExpectedHash = User.PasswordHash;
656 if (HashBytes.HasValue)
657 ExpectedHash = DigestAuthentication.EnsureHex(ExpectedHash, HashBytes.Value);
658
659 if (PasswordHash == ExpectedHash)
660 {
661 LoginAuditor.Success("Login successful.", UserName, Request.RemoteEndPoint, "HTTP");
662 return new LoginResult(User);
663 }
664 else
665 {
666 LoginAuditor.Fail("Login attempt failed.", UserName, Request.RemoteEndPoint, "HTTP");
667 return new LoginResult(null);
668 }
669 }
670
671 internal Dictionary<string, object> TokenResponse(string Token,
672 string? State, int ExpiresIn, string Scope, string? Issuer,
673 bool IssueRefreshToken, IUserWithClaims User, HttpRequest Request)
674 {
675 return this.TokenResponse(Token, State, ExpiresIn, Scope, Issuer,
676 IssueRefreshToken, User, Request, null);
677 }
678
679 private Dictionary<string, object> TokenResponse(string Token,
680 string? State, int ExpiresIn, string Scope, string? Issuer,
681 bool IssueRefreshToken, IUserWithClaims User, HttpRequest Request,
682 TokenFamily? TokenFamily)
683 {
684 Dictionary<string, object> Result = new Dictionary<string, object>()
685 {
686 { "access_token", Token },
687 { "token_type", "Bearer" },
688 { "expires_in", ExpiresIn }
689 };
690
691 if (!string.IsNullOrEmpty(State))
692 Result["state"] = State;
693
694 if (!string.IsNullOrEmpty(Issuer))
695 Result["iss"] = Issuer;
696
697 string[] Scopes;
698
699 if (string.IsNullOrEmpty(Scope))
700 Scopes = Array.Empty<string>();
701 else
702 {
703 Result["scope"] = Scope;
704 Scopes = Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
705 }
706
707 if (IssueRefreshToken)
708 {
709 if (TokenFamily is null)
710 TokenFamily = new TokenFamily(Token, Scopes, User, Request);
711 else if (!TokenFamily.CanUseRefreshToken(User.UserName, Request))
712 throw new ForbiddenException();
713 else
714 TokenFamily.Add(Token);
715
716 string RefreshToken = this.GenerateRandomCode();
717 refreshTokens[RefreshToken] = TokenFamily;
718
719 Result["refresh_token"] = RefreshToken;
720 }
721
722 return Result;
723 }
724
725 private class TokenFamily
726 {
727 private readonly ChunkedList<string> tokens;
728
729 public IEnumerable<string> Tokens => this.tokens;
730 public IUserWithClaims User { get; }
731 public HttpRequest FirstRequest { get; }
732 public string[] Scopes { get; set; }
733 public bool HasRemoteCertificate { get; }
734 public string RemoteEndpoint { get; }
735 public string RemoteCertificateSerialNumber { get; }
736
737 public TokenFamily(string Token, string[] Scopes, IUserWithClaims User,
738 HttpRequest FirstRequest)
739 {
740 this.User = User;
741 this.tokens = new ChunkedList<string>() { Token };
742 this.FirstRequest = FirstRequest;
743 this.Scopes = Scopes;
744 this.HasRemoteCertificate = !(this.FirstRequest.RemoteCertificate is null);
745 this.RemoteEndpoint = this.FirstRequest.RemoteEndPoint.RemovePortNumber();
746 this.RemoteCertificateSerialNumber =
747 this.FirstRequest.RemoteCertificate?.GetSerialNumberString()
748 ?? string.Empty;
749 }
750
751 public void Add(string Token) => this.tokens.Add(Token);
752
753 public bool CanUseRefreshToken(string ClientId, HttpRequest Request)
754 {
755 if (ClientId != this.User.UserName)
756 return false;
757
758 if (this.HasRemoteCertificate)
759 {
760 if (Request.RemoteCertificate is null)
761 return false;
762
763 if (Request.RemoteCertificate.GetSerialNumberString() !=
764 this.RemoteCertificateSerialNumber)
765 {
766 return false;
767 }
768 }
769 else
770 {
771 if (this.RemoteEndpoint != Request.RemoteEndPoint.RemovePortNumber())
772 return false;
773 }
774
775 return true;
776 }
777 }
778
785 public async Task<KeyValuePair<OAuthTokenType?, JwtToken?>> TryGetTokenType(string Token)
786 {
787 if (refreshTokens.ContainsKey(Token))
788 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.RefreshToken, null);
789 else if (usedRefreshTokens.ContainsKey(Token))
790 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.ExpiredRefreshToken, null);
791 else if (codes.ContainsKey(Token))
792 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.AccessCode, null);
793 else if (JwtToken.TryParse(Token, out JwtToken ParsedToken))
794 {
795 if (this.Environment.JwtFactory.IsValid(ParsedToken, out Reason Reason))
796 {
797 if (!string.IsNullOrEmpty(ParsedToken.Subject) &&
798 !(this.Users is null) &&
799 await this.Users.TryGetUser(ParsedToken.Subject) is null)
800 {
801 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.ExpiredAccessToken, null);
802 }
803 else
804 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.AccessToken, ParsedToken);
805 }
806 else if (Reason == Reason.Expired || Reason == Reason.Deprecated)
807 {
808 return new KeyValuePair<OAuthTokenType?, JwtToken?>(OAuthTokenType.ExpiredAccessToken, null);
809 }
810 else
811 return new KeyValuePair<OAuthTokenType?, JwtToken?>(null, null);
812 }
813 else
814 return new KeyValuePair<OAuthTokenType?, JwtToken?>(null, null);
815 }
816 }
817}
Static class that does BASE64URL encoding (using URL and filename safe alphabet), as defined in RFC46...
Definition: Base64Url.cs:11
static string Encode(byte[] Data)
Converts a binary block of data to a Base64URL-encoded string.
Definition: Base64Url.cs:48
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Alert(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an alert event.
Definition: Log.cs:1237
Basic authentication mechanism, as defined in RFC 2617: https://tools.ietf.org/html/rfc2617
static string ComputePasswordHash(string UserName, string Realm, string Password, string PasswordHashType)
Computs a password hash.
Digest authentication mechanism, as defined in RFC 2617: https://tools.ietf.org/html/rfc2617
static string EnsureHex(string s, byte NrBytes)
Tries to make sure s is a hexadecimal string. If it is a base64-encoded string, it is converted to a...
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
static ForbiddenException AccessDenied(string ObjectId, string ActorId)
Returns a ForbiddenException object, and logs a entry in the event log about the event.
HttpFieldAuthorization Authorization
Authorization HTTP Field header. (RFC 2616, §14.8)
bool TryGetQueryParameter(string QueryParameter, out string Value)
Tries to get the value of an individual query parameter, if available.
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:243
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
bool Encrypted
If the connection is encrypted or not.
Definition: HttpRequest.cs:298
IUser User
Authenticated user, if available, or null if not available.
Definition: HttpRequest.cs:203
int CipherStrength
Cipher strength
Definition: HttpRequest.cs:304
X509Certificate RemoteCertificate
Remote client certificate, if any, associated with the request.
Definition: HttpRequest.cs:288
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
HttpServer Server
HTTP Server receiving the request.
Definition: HttpRequest.cs:118
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
Task Return(Exception ex)
Returns an error to the client.
ILoginAuditor LoginAuditor
Reference to login-auditor to help remove malicious users from the server.
Definition: HttpServer.cs:1535
int[] OpenHttpsPorts
HTTPS Ports successfully opened.
Definition: HttpServer.cs:773
OAuthDeviceAuthorizationResource DeviceAuthorizationResource
Registered device authorization resource
void Register(OAuthAuthorizeResource? AuthorizeResource)
Registers an authorization resource.
static string GenerateRandomCode(int NrBytes)
Generates a random unique code.
bool HasDeviceAuthorizationResource
If the environment has a registered device authorization resource
JwtFactory JwtFactory
Registered JWT factory
OAUTH authorize resource, as defined in RFC 6749. https://datatracker.ietf.org/doc/html/rfc6749
OAUTH device authorization resource, as defined in RFC 8628. https://datatracker.ietf....
const int MinimumIntervalSeconds
Minimum time between polling requests, in seconds. The device should not poll more frequently than th...
const string GrantType
Grant Type for device authorization flow.
Abstract base class for OAUTH resources.
OAuth2Environment Environment
OAUTH2 environment, used to access clients, tokens, and other resources.
string? Realm
Realm name, if any, used for authentication. Null if no realm is defined.
static Task ServiceUnavailable(HttpResponse Response, string ErrorCode, string ErrorDescription)
Returns a Service Unavailable error back to the client.
static bool IsValidScope(string Scope)
Checks if a scope value is valid, according to the OAUTH2 specification.
bool InitAuthentication()
Initializes authentication schemes, if not already initialized.
bool Encrypted
If TLS-encryption is enabled.
static Task BadRequest(HttpResponse Response, string ErrorCode, string ErrorDescription)
Returns a Bad Request error back to the client.
static Task Forbidden(HttpResponse Response, string ErrorCode, string ErrorDescription)
Returns a Forbidden error back to the client.
static bool HasScopePrivileges(string Scopes, IUser User, [NotNullWhen(false)] out string? MissingPrivilege)
Checks if a user has the privileges associated with a set of scopes.
OAUTH token resource, as defined in RFC 6749. https://datatracker.ietf.org/doc/html/rfc6749
bool AllowsGET
If the GET method is allowed.
const string DefaultResourcePath
Default token resource path: /oauth/token
OAuthTokenResource(OAuth2Environment Environment, string ResourceName)
OAUTH token resource, as defined in RFC 6749.
bool AllowsPOST
If the POST method is allowed.
async Task GET(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
async Task< KeyValuePair< OAuthTokenType?, JwtToken?> > TryGetTokenType(string Token)
Tries to get the type of a token.
async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
OAuthTokenResource(OAuth2Environment Environment)
OAUTH token resource, as defined in RFC 6749.
Implements an in-memory cache.
Definition: Cache.cs:17
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:469
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string Scope
Scope Values
Definition: JwtClaims.cs:149
A factory that can create and validate JWT tokens.
Definition: JwtFactory.cs:66
static void Deprecate(JwtToken Token)
Deprecates a token.
Definition: JwtFactory.cs:467
bool IsValid(JwtToken Token)
Checks if a token is valid and signed by the factory.
Definition: JwtFactory.cs:279
string Issuer
Issuer identifier of the token factory, if available.
Definition: JwtFactory.cs:253
Contains information about a Java Web Token (JWT). JWT is defined in RFC 7519: https://tools....
Definition: JwtToken.cs:22
static bool TryParse(string Token, out JwtToken ParsedToken)
Tries to parse a JWT token.
Definition: JwtToken.cs:68
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async Task< KeyValuePair< string, object >[]> Annotate(string RemoteEndPoint, params KeyValuePair< string, object >[] Tags)
Annotates a remote endpoint.
static async void Success(string Message, string UserName, string RemoteEndPoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a successful login attempt.
static void Fail(string Message, string UserName, string RemoteEndPoint, string Protocol)
Handles a failed login attempt.
Login state information relating to a remote endpoint
Contains information about a login attempt.
Definition: LoginResult.cs:40
IUser User
User object corresponding to the successfully logged in user.
Definition: LoginResult.cs:80
DateTime? Next
Time when a new login can be attempted.
Definition: LoginResult.cs:85
LoginResultType Type
Type of login result.
Definition: LoginResult.cs:90
Corresponds to a user in the system.
Definition: User.cs:24
string UserName
User Name
Definition: User.cs:60
async Task< string > CreateToken(JwtFactory Factory, bool Encrypted, params KeyValuePair< string, object >[] AdditionalClaims)
Creates a JWT Token referencing the user object.
Definition: User.cs:330
string PasswordHashType
Type of password hash. The empty stream means a clear-text password.
Definition: User.cs:157
User()
Corresponds to a user in the system.
Definition: User.cs:42
string PasswordHash
Password Hash
Definition: User.cs:149
Maintains the collection of all users in the system.
Definition: Users.cs:24
async Task< IUser > TryGetUser(string UserName)
Tries to get a user with a given user name.
Definition: Users.cs:54
GET Interface for HTTP resources.
POST Interface for HTTP resources.
Task< DateTime?> GetEarliestLoginOpportunity(string RemoteEndPoint, string Protocol)
Checks when a remote endpoint can login.
Basic interface for a user.
Definition: IUser.cs:7
string UserName
User Name.
Definition: IUser.cs:12
Interface for data sources containing users.
Definition: IUserSource.cs:9
A User that can participate in distributed operations, where the user is identified using a JWT token...
OAuthTokenType
Type of OAuth token.
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15
LoginResultType
Result of login attempt
Definition: LoginResult.cs:9