vnext
parent
46f5c107b8
commit
fa34ed249b
@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using System;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
public class AuthenticationTokenCreateContext : BaseContext
|
||||
{
|
||||
private readonly ISecureDataFormat<AuthenticationTicket> _secureDataFormat;
|
||||
|
||||
public AuthenticationTokenCreateContext(HttpContext context, ISecureDataFormat<AuthenticationTicket> secureDataFormat, AuthenticationTicket ticket) : base(context)
|
||||
{
|
||||
if (secureDataFormat == null)
|
||||
throw new ArgumentNullException(nameof(secureDataFormat));
|
||||
|
||||
if (ticket == null)
|
||||
throw new ArgumentNullException(nameof(ticket));
|
||||
|
||||
_secureDataFormat = secureDataFormat;
|
||||
|
||||
Ticket = ticket;
|
||||
}
|
||||
|
||||
public string Token { get; protected set; }
|
||||
|
||||
public AuthenticationTicket Ticket { get; protected set; }
|
||||
|
||||
public string SerializeTicket()
|
||||
{
|
||||
return _secureDataFormat.Protect(Ticket);
|
||||
}
|
||||
|
||||
public void SetToken(string tokenValue)
|
||||
{
|
||||
if (tokenValue == null)
|
||||
throw new ArgumentNullException(nameof(tokenValue));
|
||||
|
||||
Token = tokenValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
public class AuthenticationTokenProvider : IAuthenticationTokenProvider
|
||||
{
|
||||
public Action<AuthenticationTokenCreateContext> OnCreate { get; set; }
|
||||
public Func<AuthenticationTokenCreateContext, Task> OnCreateAsync { get; set; }
|
||||
public Action<AuthenticationTokenReceiveContext> OnReceive { get; set; }
|
||||
public Func<AuthenticationTokenReceiveContext, Task> OnReceiveAsync { get; set; }
|
||||
|
||||
public virtual void Create(AuthenticationTokenCreateContext context)
|
||||
{
|
||||
if (OnCreateAsync != null && OnCreate == null)
|
||||
{
|
||||
throw new InvalidOperationException("Authentication token did not provide an OnCreate method.");
|
||||
}
|
||||
if (OnCreate != null)
|
||||
{
|
||||
OnCreate.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task CreateAsync(AuthenticationTokenCreateContext context)
|
||||
{
|
||||
if (OnCreateAsync != null && OnCreate == null)
|
||||
{
|
||||
throw new InvalidOperationException("Authentication token did not provide an OnCreate method.");
|
||||
}
|
||||
if (OnCreateAsync != null)
|
||||
{
|
||||
await OnCreateAsync.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
Create(context);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Receive(AuthenticationTokenReceiveContext context)
|
||||
{
|
||||
if (OnReceiveAsync != null && OnReceive == null)
|
||||
{
|
||||
throw new InvalidOperationException("Authentication token did not provide an OnReceive method.");
|
||||
}
|
||||
|
||||
if (OnReceive != null)
|
||||
{
|
||||
OnReceive.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
|
||||
{
|
||||
if (OnReceiveAsync != null && OnReceive == null)
|
||||
{
|
||||
throw new InvalidOperationException("Authentication token did not provide an OnReceive method.");
|
||||
}
|
||||
if (OnReceiveAsync != null)
|
||||
{
|
||||
await OnReceiveAsync.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
Receive(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using System;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
public class AuthenticationTokenReceiveContext : BaseContext
|
||||
{
|
||||
private readonly ISecureDataFormat<AuthenticationTicket> _secureDataFormat;
|
||||
|
||||
public AuthenticationTokenReceiveContext(HttpContext context, ISecureDataFormat<AuthenticationTicket> secureDataFormat, string token) : base(context)
|
||||
{
|
||||
if (secureDataFormat == null)
|
||||
throw new ArgumentNullException(nameof(secureDataFormat));
|
||||
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
|
||||
_secureDataFormat = secureDataFormat;
|
||||
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public string Token { get; protected set; }
|
||||
|
||||
public AuthenticationTicket Ticket { get; protected set; }
|
||||
|
||||
public void DeserializeTicket(string protectedData)
|
||||
{
|
||||
Ticket = _secureDataFormat.Unprotect(protectedData);
|
||||
}
|
||||
|
||||
public void SetTicket(AuthenticationTicket ticket)
|
||||
{
|
||||
if (ticket == null)
|
||||
throw new ArgumentNullException(nameof(ticket));
|
||||
|
||||
Ticket = ticket;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseValidatingClientContext : BaseValidatingContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes base class used for certain event contexts
|
||||
/// </summary>
|
||||
protected BaseValidatingClientContext(HttpContext context, OAuthAuthorizationServerOptions options, string clientId) : base(context, options)
|
||||
{
|
||||
ClientId = clientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "client_id" parameter for the current request. The Authorization Server application is responsible for
|
||||
/// validating this value identifies a registered client.
|
||||
/// </summary>
|
||||
public string ClientId { get; protected set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseValidatingContext<TOptions> : BaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes base class used for certain event contexts
|
||||
/// </summary>
|
||||
protected BaseValidatingContext(HttpContext context, TOptions options) : base(context)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The context options.
|
||||
/// </summary>
|
||||
public TOptions Options { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if application code has called any of the Validate methods on this context.
|
||||
/// </summary>
|
||||
public bool IsValidated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if application code has called any of the SetError methods on this context.
|
||||
/// </summary>
|
||||
public bool HasError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The error argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error" parameter.
|
||||
/// </summary>
|
||||
public string Error { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The optional errorDescription argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error_description" parameter.
|
||||
/// </summary>
|
||||
public string ErrorDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The optional errorUri argument provided when SetError was called on this context. This is eventually
|
||||
/// returned to the client app as the OAuth "error_uri" parameter.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "error_uri is a string value in the protocol")]
|
||||
public string ErrorUri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as validated by the application. IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public virtual bool Validated()
|
||||
{
|
||||
IsValidated = true;
|
||||
HasError = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application. IsValidated and HasError become false as a result of calling.
|
||||
/// </summary>
|
||||
public virtual void Rejected()
|
||||
{
|
||||
IsValidated = false;
|
||||
HasError = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
public void SetError(string error)
|
||||
{
|
||||
SetError(error, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
|
||||
public void SetError(string error, string errorDescription)
|
||||
{
|
||||
SetError(error, errorDescription, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this context as not validated by the application and assigns various error information properties.
|
||||
/// HasError becomes true and IsValidated becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="error">Assigned to the Error property</param>
|
||||
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
|
||||
/// <param name="errorUri">Assigned to the ErrorUri property</param>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "error_uri is a string value in the protocol")]
|
||||
public void SetError(string error, string errorDescription, string errorUri)
|
||||
{
|
||||
Error = error;
|
||||
ErrorDescription = errorDescription;
|
||||
ErrorUri = errorUri;
|
||||
Rejected();
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Base class used for certain event contexts
|
||||
/// </summary>
|
||||
public abstract class BaseValidatingTicketContext<TOptions> : BaseValidatingContext<TOptions> where TOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes base class used for certain event contexts
|
||||
/// </summary>
|
||||
protected BaseValidatingTicketContext(HttpContext context, TOptions options, AuthenticationTicket ticket) : base(context, options)
|
||||
{
|
||||
Ticket = ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the identity and properties for the application to authenticate. If the Validated method
|
||||
/// is invoked with an AuthenticationTicket or ClaimsIdentity argument, that new value is assigned to
|
||||
/// this property in addition to changing IsValidated to true.
|
||||
/// </summary>
|
||||
public AuthenticationTicket Ticket { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the ticket information on this context and marks it as as validated by the application.
|
||||
/// IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="ticket">Assigned to the Ticket property</param>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public bool Validated(AuthenticationTicket ticket)
|
||||
{
|
||||
Ticket = ticket;
|
||||
return Validated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alters the ticket information on this context and marks it as as validated by the application.
|
||||
/// IsValidated becomes true and HasError becomes false as a result of calling.
|
||||
/// </summary>
|
||||
/// <param name="principal">Assigned to the Ticket.Identity property</param>
|
||||
/// <returns>True if the validation has taken effect.</returns>
|
||||
public bool Validated(ClaimsPrincipal principal)
|
||||
{
|
||||
AuthenticationProperties properties = Ticket != null ? Ticket.Properties : new AuthenticationProperties();
|
||||
return Validated(new AuthenticationTicket(principal, properties, Options.AuthenticationScheme));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
internal static class DefaultBehavior
|
||||
{
|
||||
internal static readonly Func<OAuthValidateAuthorizeRequestContext, Task> ValidateAuthorizeRequest = context =>
|
||||
{
|
||||
context.Validated();
|
||||
return Task.FromResult<object>(null);
|
||||
};
|
||||
|
||||
internal static readonly Func<OAuthValidateTokenRequestContext, Task> ValidateTokenRequest = context =>
|
||||
{
|
||||
context.Validated();
|
||||
return Task.FromResult<object>(null);
|
||||
};
|
||||
|
||||
internal static readonly Func<OAuthGrantAuthorizationCodeContext, Task> GrantAuthorizationCode = context =>
|
||||
{
|
||||
if (context.Ticket != null && context.Ticket.Principal != null && context.Ticket.Principal.Identity.IsAuthenticated)
|
||||
{
|
||||
context.Validated();
|
||||
}
|
||||
return Task.FromResult<object>(null);
|
||||
};
|
||||
|
||||
internal static readonly Func<OAuthGrantRefreshTokenContext, Task> GrantRefreshToken = context =>
|
||||
{
|
||||
if (context.Ticket != null && context.Ticket.Principal != null && context.Ticket.Principal.Identity.IsAuthenticated)
|
||||
{
|
||||
context.Validated();
|
||||
}
|
||||
return Task.FromResult<object>(null);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
|
||||
public interface IApplication
|
||||
{
|
||||
string ApplicationID { get; set; }
|
||||
string DisplayName { get; set; }
|
||||
string RedirectUri { get; set; }
|
||||
string LogoutRedirectUri { get; set; }
|
||||
string Secret { get; set; }
|
||||
}
|
||||
public interface IApplicationStore
|
||||
{
|
||||
IApplication FindApplication(string clientId);
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
public interface IAuthenticationTokenProvider
|
||||
{
|
||||
void Create(AuthenticationTokenCreateContext context);
|
||||
Task CreateAsync(AuthenticationTokenCreateContext context);
|
||||
void Receive(AuthenticationTokenReceiveContext context);
|
||||
Task ReceiveAsync(AuthenticationTokenReceiveContext context);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.DataProtection;
|
||||
using Microsoft.AspNet.Http;
|
||||
using System;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Options class provides information needed to control Authorization Server middleware behavior
|
||||
/// </summary>
|
||||
public class OAuthAuthorizationServerOptions : AuthenticationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of authorization server options with default values.
|
||||
/// </summary>
|
||||
public OAuthAuthorizationServerOptions()
|
||||
{
|
||||
AuthenticationScheme = OAuthDefaults.AuthenticationType;
|
||||
AuthorizationCodeExpireTimeSpan = TimeSpan.FromMinutes(5);
|
||||
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20);
|
||||
SystemClock = new SystemClock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The request path where client applications will redirect the user-agent in order to
|
||||
/// obtain user consent to issue a token. Must begin with a leading slash, like "/Authorize".
|
||||
/// </summary>
|
||||
public PathString AuthorizeEndpointPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request path client applications communicate with directly as part of the OAuth protocol.
|
||||
/// Must begin with a leading slash, like "/Token". If the client is issued a client_secret, it must
|
||||
/// be provided to this endpoint.
|
||||
/// </summary>
|
||||
public PathString TokenEndpointPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The object provided by the application to process events raised by the Authorization Server middleware.
|
||||
/// The application may implement the interface fully, or it may create an instance of OAuthAuthorizationServerProvider
|
||||
/// and assign delegates only to the events it wants to process.
|
||||
/// </summary>
|
||||
public IOAuthAuthorizationServerProvider Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data format used to protect and unprotect the information contained in the authorization code.
|
||||
/// If not provided by the application the default data protection provider depends on the host server.
|
||||
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
|
||||
/// servers will use DPAPI data protection.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationTicket> AuthorizationCodeFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data protection provider used to protect token information.
|
||||
/// </summary>
|
||||
public IDataProtector TokenDataProtector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data format used to protect the information contained in the access token.
|
||||
/// If not provided by the application the default data protection provider depends on the host server.
|
||||
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
|
||||
/// servers will use DPAPI data protection. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthBearerAuthenticationOptions.AccessTokenProvider
|
||||
/// or OAuthBearerAuthenticationOptions.AccessTokenFormat property of the resource server.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data format used to protect and unprotect the information contained in the refresh token.
|
||||
/// If not provided by the application the default data protection provider depends on the host server.
|
||||
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
|
||||
/// servers will use DPAPI data protection.
|
||||
/// </summary>
|
||||
public ISecureDataFormat<AuthenticationTicket> RefreshTokenFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The period of time the authorization code remains valid after being issued. The default is five minutes.
|
||||
/// This time span must also take into account clock synchronization between servers in a web farm, so a very
|
||||
/// brief value could result in unexpectedly expired tokens.
|
||||
/// </summary>
|
||||
public TimeSpan AuthorizationCodeExpireTimeSpan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The period of time the access token remains valid after being issued. The default is twenty minutes.
|
||||
/// The client application is expected to refresh or acquire a new access token after the token has expired.
|
||||
/// </summary>
|
||||
public TimeSpan AccessTokenExpireTimeSpan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Produces a single-use authorization code to return to the client application. For the OAuth server to be secure the
|
||||
/// application MUST provide an instance for AuthorizationCodeProvider where the token produced by the OnCreate or OnCreateAsync event
|
||||
/// is considered valid for only one call to OnReceive or OnReceiveAsync.
|
||||
/// </summary>
|
||||
public IAuthenticationTokenProvider AuthorizationCodeProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Produces a bearer token the client application will typically be providing to resource server as the authorization bearer
|
||||
/// http request header. If not provided the token produced on the server's default data protection. If a different access token
|
||||
/// provider or format is assigned, a compatible instance must be assigned to the OAuthBearerAuthenticationOptions.AccessTokenProvider
|
||||
/// or OAuthBearerAuthenticationOptions.AccessTokenFormat property of the resource server.
|
||||
/// </summary>
|
||||
public IAuthenticationTokenProvider AccessTokenProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Produces a refresh token which may be used to produce a new access token when needed. If not provided the authorization server will
|
||||
/// not return refresh tokens from the /Token endpoint.
|
||||
/// </summary>
|
||||
public IAuthenticationTokenProvider RefreshTokenProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the web application is able to render error messages on the /Authorize endpoint. This is only needed for cases where
|
||||
/// the browser is not redirected back to the client application, for example, when the client_id or redirect_uri are incorrect. The
|
||||
/// /Authorize endpoint should expect to see "oauth.Error", "oauth.ErrorDescription", "oauth.ErrorUri" properties added to the owin environment.
|
||||
/// </summary>
|
||||
public bool ApplicationCanDisplayErrors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
|
||||
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
|
||||
/// </summary>
|
||||
public ISystemClock SystemClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to allow authorize and token requests to arrive on http URI addresses, and to allow incoming
|
||||
/// redirect_uri authorize request parameter to have http URI addresses.
|
||||
/// </summary>
|
||||
public bool AllowInsecureHttp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint responsible for Form Post Response Mode
|
||||
/// See also, http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
|
||||
/// </summary>
|
||||
public PathString FormPostEndpoint { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// An event raised after the Authorization Server has processed the request, but before it is passed on to the web application.
|
||||
/// Calling RequestCompleted will prevent the request from passing on to the web application.
|
||||
/// </summary>
|
||||
public class OAuthAuthorizeEndpointContext : BaseOAuthEndpointContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of this context
|
||||
/// </summary>
|
||||
public OAuthAuthorizeEndpointContext(HttpContext context, OAuthAuthorizationServerOptions options, AuthorizeEndpointRequest authorizeRequest) : base(context, options)
|
||||
{
|
||||
AuthorizeRequest = authorizeRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets OAuth authorization request data.
|
||||
/// </summary>
|
||||
public AuthorizeEndpointRequest AuthorizeRequest { get; private set; }
|
||||
|
||||
public bool IsRequestCompleted { get; private set; }
|
||||
|
||||
public void RequestCompleted()
|
||||
{
|
||||
IsRequestCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information when handling an OAuth authorization code grant.
|
||||
/// </summary>
|
||||
public class OAuthGrantAuthorizationCodeContext : BaseValidatingTicketContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthGrantAuthorizationCodeContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="ticket"></param>
|
||||
public OAuthGrantAuthorizationCodeContext(HttpContext context, OAuthAuthorizationServerOptions options, AuthenticationTicket ticket) : base(context, options, ticket) { }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used in handling an OAuth client credentials grant.
|
||||
/// </summary>
|
||||
public class OAuthGrantClientCredentialsContext : BaseValidatingTicketContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthGrantClientCredentialsContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="scope"></param>
|
||||
public OAuthGrantClientCredentialsContext(HttpContext context, OAuthAuthorizationServerOptions options, string clientId, IList<string> scope) : base(context, options, null)
|
||||
{
|
||||
ClientId = clientId;
|
||||
Scope = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth client id.
|
||||
/// </summary>
|
||||
public string ClientId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of scopes allowed by the resource owner.
|
||||
/// </summary>
|
||||
public IList<string> Scope { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used when handling OAuth extension grant types.
|
||||
/// </summary>
|
||||
public class OAuthGrantCustomExtensionContext : BaseValidatingTicketContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthGrantCustomExtensionContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="grantType"></param>
|
||||
/// <param name="parameters"></param>
|
||||
public OAuthGrantCustomExtensionContext(HttpContext context, OAuthAuthorizationServerOptions options, string clientId, string grantType, IReadableStringCollection parameters) : base(context, options, null)
|
||||
{
|
||||
ClientId = clientId;
|
||||
GrantType = grantType;
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OAuth client id.
|
||||
/// </summary>
|
||||
public string ClientId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the OAuth extension grant type.
|
||||
/// </summary>
|
||||
public string GrantType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of additional parameters from the token request.
|
||||
/// </summary>
|
||||
public IReadableStringCollection Parameters { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used when granting an OAuth refresh token.
|
||||
/// </summary>
|
||||
public class OAuthGrantRefreshTokenContext : BaseValidatingTicketContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthGrantRefreshTokenContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="ticket"></param>
|
||||
/// <param name="clientId"></param>
|
||||
public OAuthGrantRefreshTokenContext(HttpContext context, OAuthAuthorizationServerOptions options, AuthenticationTicket ticket, string clientId) : base(context, options, ticket)
|
||||
{
|
||||
ClientId = clientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OAuth client id.
|
||||
/// </summary>
|
||||
public string ClientId { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used in handling an OAuth resource owner grant.
|
||||
/// </summary>
|
||||
public class OAuthGrantResourceOwnerCredentialsContext : BaseValidatingTicketContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthGrantResourceOwnerCredentialsContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="userName"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="scope"></param>
|
||||
public OAuthGrantResourceOwnerCredentialsContext(HttpContext context, OAuthAuthorizationServerOptions options, string clientId, string userName, string password, IList<string> scope) : base(context, options, null)
|
||||
{
|
||||
ClientId = clientId;
|
||||
UserName = userName;
|
||||
Password = password;
|
||||
Scope = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth client id.
|
||||
/// </summary>
|
||||
public string ClientId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource owner username.
|
||||
/// </summary>
|
||||
public string UserName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource owner password.
|
||||
/// </summary>
|
||||
public string Password { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of scopes allowed by the resource owner.
|
||||
/// </summary>
|
||||
public IList<string> Scope { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
using System;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides notification used for determining the OAuth flow type based on the request.
|
||||
/// </summary>
|
||||
public class OAuthMatchContext : BaseControlContext
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthMatchContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
public OAuthMatchContext(HttpContext context, OAuthAuthorizationServerOptions options) : base(context)
|
||||
{
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
|
||||
Options = options;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Members
|
||||
|
||||
public OAuthAuthorizationServerOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the endpoint is an OAuth authorize endpoint.
|
||||
/// </summary>
|
||||
public bool IsAuthorizeEndpoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the endpoint is an OAuth token endpoint.
|
||||
/// </summary>
|
||||
public bool IsTokenEndpoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the endpoint type to authorize endpoint.
|
||||
/// </summary>
|
||||
public void MatchesAuthorizeEndpoint()
|
||||
{
|
||||
IsAuthorizeEndpoint = true;
|
||||
IsTokenEndpoint = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the endpoint type to token endpoint.
|
||||
/// </summary>
|
||||
public void MatchesTokenEndpoint()
|
||||
{
|
||||
IsAuthorizeEndpoint = false;
|
||||
IsTokenEndpoint = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the endpoint type to neither authorize nor token.
|
||||
/// </summary>
|
||||
public void MatchesNothing()
|
||||
{
|
||||
IsAuthorizeEndpoint = false;
|
||||
IsTokenEndpoint = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
using Microsoft.AspNet.Authentication;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Authentication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used when processing an OAuth token request.
|
||||
/// </summary>
|
||||
public class OAuthTokenEndpointContext : BaseOAuthEndpointContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthTokenEndpointContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="ticket"></param>
|
||||
/// <param name="tokenEndpointRequest"></param>
|
||||
public OAuthTokenEndpointContext(HttpContext context, OAuthAuthorizationServerOptions options, AuthenticationTicket ticket, TokenEndpointRequest tokenEndpointRequest) : base(context, options)
|
||||
{
|
||||
if (ticket == null)
|
||||
{
|
||||
throw new ArgumentNullException("ticket");
|
||||
}
|
||||
|
||||
Principal = ticket.Principal;
|
||||
Properties = ticket.Properties;
|
||||
TokenEndpointRequest = tokenEndpointRequest;
|
||||
AdditionalResponseParameters = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
TokenIssued = Principal != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identity of the resource owner.
|
||||
/// </summary>
|
||||
public ClaimsPrincipal Principal { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary containing the state of the authentication session.
|
||||
/// </summary>
|
||||
public AuthenticationProperties Properties { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the token endpoint request.
|
||||
/// </summary>
|
||||
public TokenEndpointRequest TokenEndpointRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the token should be issued.
|
||||
/// </summary>
|
||||
public bool TokenIssued { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables additional values to be appended to the token response.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> AdditionalResponseParameters { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Issues the token.
|
||||
/// </summary>
|
||||
/// <param name="principal"></param>
|
||||
/// <param name="properties"></param>
|
||||
public void Issue(ClaimsPrincipal principal, AuthenticationProperties properties)
|
||||
{
|
||||
Principal = principal;
|
||||
Properties = properties;
|
||||
TokenIssued = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used in validating an OAuth authorization request.
|
||||
/// </summary>
|
||||
public class OAuthValidateAuthorizeRequestContext : BaseValidatingContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthValidateAuthorizeRequestContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="authorizeRequest"></param>
|
||||
/// <param name="clientContext"></param>
|
||||
public OAuthValidateAuthorizeRequestContext(HttpContext context, OAuthAuthorizationServerOptions options, AuthorizeEndpointRequest authorizeRequest, OAuthValidateClientRedirectUriContext clientContext) : base(context, options)
|
||||
{
|
||||
AuthorizeRequest = authorizeRequest;
|
||||
ClientContext = clientContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets OAuth authorization request data.
|
||||
/// </summary>
|
||||
public AuthorizeEndpointRequest AuthorizeRequest { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets data about the OAuth client.
|
||||
/// </summary>
|
||||
public OAuthValidateClientRedirectUriContext ClientContext { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides context information used in validating an OAuth token request.
|
||||
/// </summary>
|
||||
public class OAuthValidateTokenRequestContext : BaseValidatingContext<OAuthAuthorizationServerOptions>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthValidateTokenRequestContext"/> class
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="tokenRequest"></param>
|
||||
/// <param name="clientContext"></param>
|
||||
public OAuthValidateTokenRequestContext(HttpContext context, OAuthAuthorizationServerOptions options, TokenEndpointRequest tokenRequest, BaseValidatingClientContext clientContext) : base(context, options)
|
||||
{
|
||||
TokenRequest = tokenRequest;
|
||||
ClientContext = clientContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the token request data.
|
||||
/// </summary>
|
||||
public TokenEndpointRequest TokenRequest { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the client.
|
||||
/// </summary>
|
||||
public BaseValidatingClientContext ClientContext { get; private set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Data object used by TokenEndpointRequest when the "grant_type" is "authorization_code".
|
||||
/// </summary>
|
||||
public class TokenEndpointRequestAuthorizationCode
|
||||
{
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "code" parameter
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "redirect_uri" parameter. This MUST be provided by the caller
|
||||
/// if the original visit to the Authorize endpoint contained a "redirect_uri" parameter.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")]
|
||||
public string RedirectUri { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Data object used by TokenEndpointRequest when the "grant_type" is "client_credentials".
|
||||
/// </summary>
|
||||
public class TokenEndpointRequestClientCredentials
|
||||
{
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "scope" parameter
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This class is just for passing data through.")]
|
||||
public IList<string> Scope { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Data object used by TokenEndpointRequest when the "grant_type" parameter is "refresh_token".
|
||||
/// </summary>
|
||||
public class TokenEndpointRequestRefreshToken
|
||||
{
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "refresh_token" parameter
|
||||
/// </summary>
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "scope" parameter
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is just a data container object.")]
|
||||
public IList<string> Scope { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OAuth.AspNet.AuthServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Data object used by TokenEndpointRequest when the "grant_type" is "password".
|
||||
/// </summary>
|
||||
public class TokenEndpointRequestResourceOwnerPasswordCredentials
|
||||
{
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "username" parameter
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "password" parameter
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value passed to the Token endpoint in the "scope" parameter
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is just a data class.")]
|
||||
public IList<string> Scope { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using OAuth.AspNet.AuthServer;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods to add Authorization Server capabilities to an OWIN pipeline
|
||||
/// </summary>
|
||||
public static class OAuthAuthorizationServerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds OAuth2 Authorization Server capabilities to an OWIN web application. This middleware
|
||||
/// performs the request processing for the Authorize and Token endpoints defined by the OAuth2 specification.
|
||||
/// See also http://tools.ietf.org/html/rfc6749
|
||||
/// </summary>
|
||||
/// <param name="app">The web application builder</param>
|
||||
/// <param name="options">Options which control the behavior of the Authorization Server.</param>
|
||||
/// <returns>The application builder</returns>
|
||||
public static IApplicationBuilder UseOAuthAuthorizationServer(this IApplicationBuilder app, OAuthAuthorizationServerOptions options)
|
||||
{
|
||||
if (app == null)
|
||||
throw new NullReferenceException($"The extension method {nameof(OAuthAuthorizationServerExtensions.UseOAuthAuthorizationServer)} was called on a null reference to a {nameof(IApplicationBuilder)}");
|
||||
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
|
||||
|
||||
|
||||
return app.UseMiddleware<OAuthAuthorizationServerMiddleware>(options);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds OAuth2 Authorization Server capabilities to an OWIN web application. This middleware
|
||||
/// performs the request processing for the Authorize and Token endpoints defined by the OAuth2 specification.
|
||||
/// See also http://tools.ietf.org/html/rfc6749
|
||||
/// </summary>
|
||||
/// <param name="app">The web application builder</param>
|
||||
/// <param name="configureOptions">Options which control the behavior of the Authorization Server.</param>
|
||||
/// <returns>The application builder</returns>
|
||||
public static IApplicationBuilder UseOAuthAuthorizationServer(this IApplicationBuilder app, Action<OAuthAuthorizationServerOptions> configureOptions)
|
||||
{
|
||||
if (app == null)
|
||||
throw new NullReferenceException($"The extension method {nameof(OAuthAuthorizationServerExtensions.UseOAuthAuthorizationServer)} was called on a null reference to a {nameof(IApplicationBuilder)}");
|
||||
|
||||
if (configureOptions == null)
|
||||
throw new ArgumentNullException(nameof(configureOptions));
|
||||
|
||||
|
||||
var options = new OAuthAuthorizationServerOptions();
|
||||
if (configureOptions != null)
|
||||
|
||||
configureOptions(options);
|
||||
|
||||
return app.UseOAuthAuthorizationServer(options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue