4using System.Threading.Tasks;
66 await
BadRequest(Response,
"invalid_request",
"Missing payload.");
70 ParsedRegistrationRequest? Parsed = await this.ParseRegistrationRequest(
71 Request, Response,
false);
76 RegistrationRequest RegistrationRequest = Parsed.Request;
78 IRegistration? Registration = await Parsed.DynamicUserSource.RegisterUser(
81 if (Registration is
null)
83 await
Forbidden(Response,
"access_denied",
84 "Not permitted to register new client.");
88 DateTime TP = DateTime.UtcNow;
96 RemoteEndPoint = RegistrationRequest.RemoteEndPoint,
97 RedirectUris = RegistrationRequest.RedirectUris,
98 GrantTypes = RegistrationRequest.GrantTypes,
99 ResponseTypes = RegistrationRequest.ResponseTypes,
100 TokenEndpointAuthMethod = RegistrationRequest.TokenEndpointAuthMethod,
101 ClientName = RegistrationRequest.ClientName,
102 SoftwareId = RegistrationRequest.SoftwareId,
103 SoftwareVersion = RegistrationRequest.SoftwareVersion,
104 ClientUri = RegistrationRequest.ClientUri?.ToString(),
105 LogoUri = RegistrationRequest.LogoUri?.ToString(),
106 TosUri = RegistrationRequest.TosUri?.ToString(),
107 PolicyUri = RegistrationRequest.PolicyUri?.ToString(),
108 JwksUri = RegistrationRequest.JwksUri?.ToString(),
109 Scopes = RegistrationRequest.Scopes,
110 Contacts = RegistrationRequest.Contacts,
111 Jwks = RegistrationRequest.Jwks,
112 MetaData = RegistrationRequest.MetaData
116 await AddRedirectUrls(Registration.
ClientId, RegistrationRequest.RedirectUris);
118 Dictionary<string, object> ResponseObj = this.RegistrationResponse(Request,
119 Response, Parsed, ClientInfo, Registration);
121 Response.StatusCode = 201;
122 Response.StatusMessage =
"Created";
124 await Response.
Return(ResponseObj);
127 internal Dictionary<string, object> RegistrationResponse(
HttpRequest Request,
128 HttpResponse Response, ParsedRegistrationRequest? Parsed,
131 Dictionary<string, object> ResponseObj =
new Dictionary<string, object>();
133 if (!(Parsed is
null))
135 foreach (KeyValuePair<string, object> P
in Parsed.RequestObj)
136 ResponseObj[P.Key] = P.Value;
139 ResponseObj[
"client_id"] = ClientInfo.
ClientId!;
140 ResponseObj[
"client_id_issued_at"] = (long)ClientInfo.
Created.Subtract(
JSON.
UnixEpoch).TotalSeconds;
144 string RegistrationClientUri = Request.
Header.
GetURL(
false,
false).
148 ResponseObj[
"registration_access_token"] = ClientInfo.
AccessToken!;
149 ResponseObj[
"registration_client_uri"] = RegistrationClientUri;
152 if (!(Registration is
null) && (Parsed?.ReturnClientSecret ??
false))
153 ResponseObj[
"client_secret"] = Registration.
ClientSecret;
155 if ((!(Registration is
null) && (Parsed?.ReturnClientSecret ??
false)) ||
158 ResponseObj[
"client_secret_expires_at"] = ClientInfo.ClientSecretExpiresAt.HasValue ?
162 Response.
SetHeader(
"Cache-Control",
"max-age=0, no-cache, no-store");
163 Response.
SetHeader(
"Pragma",
"no-cache");
168 internal static async Task AddRedirectUrls(
string ClientId,
string[]? RedirectUris)
170 if (!(RedirectUris is
null))
172 foreach (
string RedirectUri
in RedirectUris)
185 internal async Task<ParsedRegistrationRequest?> ParseRegistrationRequest(
HttpRequest Request,
190 !(Decoded.
Decoded is Dictionary<string, object> RequestObj))
192 await
BadRequest(Response,
"invalid_request",
"Invalid form.");
196 string[]? RedirectUris =
null;
197 string[]? GrantTypes =
null;
198 string[]? ResponseTypes =
null;
199 string? TokenEndpointAuthMethod =
null;
200 string? ClientName =
null;
201 string? SoftwareId =
null;
202 string? SoftwareVersion =
null;
203 string? ClientId =
null;
204 string? ClientSecret =
null;
205 Uri? ClientUri =
null;
208 Uri? PolicyUri =
null;
210 string[]? Scopes =
null;
211 string[]? Contacts =
null;
212 Dictionary<string, object?>? Jwks =
null;
213 Dictionary<string, object?>? MetaData =
null;
214 bool ReturnClientSecret =
false;
216 foreach (KeyValuePair<string, object> P
in RequestObj)
220 case "redirect_uris":
221 RedirectUris = ToStrings(P.Value);
223 if (!(RedirectUris is
null))
225 foreach (
string RedirectUri
in RedirectUris)
227 if (!Uri.TryCreate(RedirectUri, UriKind.Absolute, out Uri Parsed) ||
228 !
string.IsNullOrEmpty(Parsed.Fragment) ||
229 !
string.IsNullOrEmpty(Parsed.Query))
231 await
BadRequest(Response,
"invalid_redirect_uri",
"Invalid redirection URI.");
238 if (!(UriObj is
null))
240 if (ClientId is
null &&
241 RequestObj.TryGetValue(
"client_id", out
object Obj) &&
242 Obj is
string ClientId3)
244 ClientId = ClientId3;
249 await
BadRequest(Response,
"invalid_client_metadata",
"URI already registered.");
258 GrantTypes = ToStrings(P.Value);
261 case "response_types":
262 ResponseTypes = ToStrings(P.Value);
266 Contacts = ToStrings(P.Value);
269 case "token_endpoint_auth_method":
270 TokenEndpointAuthMethod = P.Value?.ToString();
272 switch (TokenEndpointAuthMethod)
274 case "client_secret_post":
275 case "client_secret_basic":
276 ReturnClientSecret =
true;
282 ClientName = P.Value?.ToString();
286 SoftwareId = P.Value?.ToString();
289 case "software_version":
290 SoftwareVersion = P.Value?.ToString();
294 if (!Uri.TryCreate(P.Value?.ToString(), UriKind.Absolute, out ClientUri))
296 await
BadRequest(Response,
"invalid_request",
"Invalid client_uri");
300 if (!await IsValidUri(ClientUri,
303 await
BadRequest(Response,
"invalid_request",
"Invalid client_uri");
309 if (!Uri.TryCreate(P.Value?.ToString(), UriKind.Absolute, out LogoUri))
311 await
BadRequest(Response,
"invalid_request",
"Invalid logo_uri");
315 if (!await IsValidUri(LogoUri,
318 await
BadRequest(Response,
"invalid_request",
"Invalid logo_uri");
324 if (!Uri.TryCreate(P.Value?.ToString(), UriKind.Absolute, out TosUri))
326 await
BadRequest(Response,
"invalid_request",
"Invalid tos_uri");
330 if (!await IsValidUri(TosUri,
333 await
BadRequest(Response,
"invalid_request",
"Invalid tos_uri");
339 if (!Uri.TryCreate(P.Value?.ToString(), UriKind.Absolute, out PolicyUri))
341 await
BadRequest(Response,
"invalid_request",
"Invalid policy_uri");
345 if (!await IsValidUri(PolicyUri,
348 await
BadRequest(Response,
"invalid_request",
"Invalid policy_uri");
354 if (!Uri.TryCreate(P.Value?.ToString(), UriKind.Absolute, out JwksUri))
356 await
BadRequest(Response,
"invalid_request",
"Invalid jwks_uri");
362 await
BadRequest(Response,
"invalid_request",
"Invalid jwks_uri");
368 string Scope = P.Value?.ToString() ??
string.Empty;
371 await
BadRequest(Response,
"invalid_scope",
"Invalid scope parameter.");
375 Scopes = Scope.Split(
' ', StringSplitOptions.RemoveEmptyEntries);
379 if (P.Value is Dictionary<string, object?> Jwks2)
383 await
BadRequest(Response,
"invalid_request",
"Invalid jwks");
389 if (!PermitClientCredentials || !(P.Value is
string ClientId2))
391 await
BadRequest(Response,
"invalid_request",
"Invalid request parameter: " + P.Key);
395 ClientId = ClientId2;
398 case "client_secret":
399 if (!PermitClientCredentials || !(P.Value is
string ClientSecret2))
401 await
BadRequest(Response,
"invalid_request",
"Invalid request parameter: " + P.Key);
405 ClientSecret = ClientSecret2;
408 case "client_id_issued_at":
409 case "client_secret_expires_at":
410 await
BadRequest(Response,
"invalid_request",
"Invalid request parameter: " + P.Key);
414 if (P.Key.EndsWith(
"_uri") && (
415 !(P.Value is
string s) ||
416 !Uri.TryCreate(s, UriKind.Absolute, out Uri MetaUri) ||
417 !await IsValidUri(MetaUri,
"*/*")))
419 await
BadRequest(Response,
"invalid_request",
"Invalid URI: " + P.Key);
423 MetaData ??=
new Dictionary<string, object?>();
424 MetaData[P.Key] = P.Value;
429 if (!(GrantTypes is
null) &&
430 Array.IndexOf(GrantTypes,
"implicit") >= 0 &&
431 !(ResponseTypes is
null) &&
432 Array.IndexOf(ResponseTypes,
"token") < 0)
434 await
BadRequest(Response,
"invalid_client_metadata",
435 "Implicit grant_type requires token response_type.");
442 "Client registration service not available.");
446 RegistrationRequest RegistrationRequest =
new RegistrationRequest(
447 Request.
RemoteEndPoint.RemovePortNumber(), RedirectUris, GrantTypes,
448 ResponseTypes, TokenEndpointAuthMethod, ClientName, SoftwareId,
449 SoftwareVersion, ClientUri, LogoUri, TosUri, PolicyUri, JwksUri,
450 Scopes, Contacts, Jwks, MetaData, ClientId, ClientSecret);
452 return new ParsedRegistrationRequest(RegistrationRequest, RequestObj,
453 DynamicUserSource, ReturnClientSecret);
456 private static async Task<bool> IsValidUri(Uri Uri,
string Accept)
461 new KeyValuePair<string, string>(
"Accept", Accept));
471 internal class ParsedRegistrationRequest
473 public ParsedRegistrationRequest(RegistrationRequest Request,
475 bool ReturnClientSecret)
477 this.Request = Request;
478 this.RequestObj = RequestObj;
479 this.DynamicUserSource = DynamicUserSource;
480 this.ReturnClientSecret = ReturnClientSecret;
483 public RegistrationRequest Request;
484 public Dictionary<string, object> RequestObj;
486 public bool ReturnClientSecret;
491 public RegistrationRequest(
string RemoteEndPoint,
string[]? RedirectUris,
492 string[]? GrantTypes,
string[]? ResponseTypes,
493 string? TokenEndpointAuthMethod,
string? ClientName,
string? SoftwareId,
494 string? SoftwareVersion, Uri? ClientUri, Uri? LogoUri, Uri? TosUri,
495 Uri? PolicyUri, Uri? JwksUri,
string[]? Scopes,
string[]? Contacts,
496 Dictionary<string, object?>? Jwks, Dictionary<string, object?>? MetaData,
497 string? ClientId,
string? ClientSecret)
499 this.RemoteEndPoint = RemoteEndPoint;
500 this.PublicClient = TokenEndpointAuthMethod ==
"none";
501 this.ConfidentialClient = !this.PublicClient;
502 this.RedirectUris = RedirectUris;
503 this.GrantTypes = GrantTypes;
504 this.ResponseTypes = ResponseTypes;
505 this.TokenEndpointAuthMethod = TokenEndpointAuthMethod;
506 this.ClientName = ClientName;
507 this.SoftwareId = SoftwareId;
508 this.SoftwareVersion = SoftwareVersion;
509 this.ClientUri = ClientUri;
510 this.LogoUri = LogoUri;
511 this.TosUri = TosUri;
512 this.PolicyUri = PolicyUri;
513 this.JwksUri = JwksUri;
514 this.Scopes = Scopes;
515 this.Contacts = Contacts;
517 this.MetaData = MetaData;
518 this.ClientId = ClientId;
519 this.ClientSecret = ClientSecret;
522 public string RemoteEndPoint {
get; }
523 public bool PublicClient {
get; }
524 public bool ConfidentialClient {
get; }
525 public string[]? RedirectUris {
get; }
526 public string[]? GrantTypes {
get; }
527 public string[]? ResponseTypes {
get; }
528 public string? TokenEndpointAuthMethod {
get; }
529 public string? ClientName {
get; }
530 public string? SoftwareId {
get; }
531 public string? SoftwareVersion {
get; }
532 public string? ClientId {
get; }
533 public string? ClientSecret {
get; }
534 public Uri? ClientUri {
get; }
535 public Uri? LogoUri {
get; }
536 public Uri? TosUri {
get; }
537 public Uri? PolicyUri {
get; }
538 public Uri? JwksUri {
get; }
539 public string[]? Scopes {
get; }
540 public string[]? Contacts {
get; }
541 public Dictionary<string, object?>? Jwks {
get; }
542 public Dictionary<string, object?>? MetaData {
get; }
545 private static string[]? ToStrings(
object? Value)
549 else if (Value is
string[]
Strings)
551 else if (Value is IEnumerable Items)
555 foreach (
object Item
in Items)
557 if (Item is
string s)
560 Result.
Add(Item.ToString());
566 return new string[] { Value.ToString() };
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
static readonly string[] HtmlContentTypes
HTML content types.
static readonly string[] ImageContentTypes
Image content types.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > GetAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a resource, given its URI.
Helps with common JSON-related tasks.
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
const string DefaultContentType
application/json
Represents an HTTP request.
HttpRequestHeader Header
Request header.
string RemoteEndPoint
Remote end-point.
bool HasData
If the request has data.
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
string ResourceName
Name of resource.
Represets a response of an HTTP client request.
void SetHeader(string FieldName, string Value)
Sets a custom header field value.
Task Return(Exception ex)
Returns an error to the client.
Contains information about a redirect URI using by an OAuth client.
string ClientId
OAuth 2.0 client identifier string.
Manages the OAuth 2 environment.
bool HasManagementResource
If the environment has a registered client management resource
void Register(OAuthAuthorizeResource? AuthorizeResource)
Registers an authorization resource.
static string GenerateRandomCode(int NrBytes)
Generates a random unique code.
OAUTH client management resource, as defined in RFCs 7591. https://datatracker.ietf....
const string DefaultResourcePath
OAUTH client management resource, as defined in RFCs 7591.
OAUTH dynamic registration resource, as defined in RFCs 7591 and 7592. https://datatracker....
async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
OAuthRegistrationResource(OAuth2Environment Environment)
OAUTH dynamic registration resource, as defined in RFCs 7591 and 7592.
const string DefaultResourcePath
Default registration resource path: /oauth/register
bool AllowsPOST
If the POST method is allowed.
OAuthRegistrationResource(OAuth2Environment Environment, string ResourceName)
OAUTH dynamic registration resource, as defined in RFCs 7591 and 7592.
Abstract base class for OAUTH resources.
IUserSource? Users
Data source for users, used to authenticate clients.
OAuth2Environment Environment
OAUTH2 environment, used to access clients, tokens, and other resources.
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.
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 interface for database persistence. In order to work, a database provider has to be assigned t...
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
This filter selects objects that have a named field equal to a given value.
A chunked list is a linked list of chunks of objects of type T .
void Add(T Item)
Adds an item to the collection.
T[] ToArray()
Returns an array containing all elements of the collection.
Static class managing binary representations of strings.
POST Interface for HTTP resources.
A dynamic user source, supporting registering new users.
Dynamic client registration, as defined in RFC 7591.
DateTime? ClientSecretExpiresAt
Time at which the client secret will expire
string ClientId
OAuth 2.0 client identifier string.
string ClientSecret
OAuth 2.0 client secret string.
Dynamic client registration request, as defined in RFC 7591.