tesses.http/Tesses.Http/BasicAuthRequestHandler.cs

138 lines
5.1 KiB
C#

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using Newtonsoft.Json;
using System.Reflection;
using System.Linq;
using HeyRed.Mime;
namespace Tesses.Http
{/// <summary>
/// Check username and password are correct or if request can be anonymous
/// </summary>
/// <param name="username">Username, can and will be "" on first request for resource</param>
/// <param name="password">Password, can and will be "" on first request for resource</param>
/// <returns>true for authorized, false for unauthorized</returns>
public delegate bool Authenticate(string username, string password);
/// <summary>
/// Check username and password are correct or if request can be anonymous
/// </summary>
/// <param name="context">Server Context</param>
/// <param name="username">Username, can and will be "" on first request for resource</param>
/// <param name="password">Password, can and will be "" on first request for resource</param>
/// <returns>true for authorized, false for unauthorized</returns>
public delegate bool AuthenticateWithContext(ServerContext context,string username,string password);
/// <summary>
/// Protect server with password
/// </summary>
public class BasicAuthRequestHandler : IRequestHandler
{
/// <summary>
/// Construct server for user authorization
/// </summary>
/// <param name="auth">callback for authorization</param>
/// <param name="inner">server to protect</param>
/// <param name="realm">realm parameter in WWW-Auhenticate Header</param>
public BasicAuthRequestHandler(Authenticate auth,IRequestHandler inner,string realm="SampleRealm")
{
Authenticate = auth;
InnerServer = inner;
Realm = realm;
}
/// <summary>
/// Construct server for user authorization (With ServerContext in callback)
/// </summary>
/// <param name="auth">callback for authorization</param>
/// <param name="inner">server to protect</param>
/// <param name="realm">realm parameter in WWW-Auhenticate Header</param>
public BasicAuthRequestHandler(AuthenticateWithContext auth,IRequestHandler inner,string realm = "SampleRealm")
{
AuthenticateWithContext=auth;
InnerServer=inner;
Realm = realm;
}
public async Task Handle(ServerContext ctx)
{
if(await Authorize(ctx))
{
await RequestHandler.Guaranteed(ctx,InnerServer).Handle(ctx);
}
}
/// <summary>
/// Server to protect
/// </summary>
public IRequestHandler InnerServer { get; set; }
/// <summary>
/// Authentication callback without ServerContext
/// </summary>
public Authenticate Authenticate { get; set; }
/// <summary>
/// Authentication callback with ServerContext
/// </summary>
public AuthenticateWithContext AuthenticateWithContext {get;set;}
/// <summary>
/// Realm parameter in WWW-Authenticate header
/// </summary>
public string Realm { get; set; }
private bool ValidAuth(ServerContext ctx)
{
string auth;
if(Authenticate == null && AuthenticateWithContext == null) return true;
if (ctx.Request.Headers.TryGetFirst("Authorization", out auth))
{
string[] authorization = auth.Split(' ');
//authorization_basic
if (authorization[0] == "Basic")
{
string[] userPass = Encoding.UTF8.GetString(Convert.FromBase64String(authorization[1])).Split(new char[] { ':' },2);
//return userPass.Equals($"{config.UserName}:{config.Password}", StringComparison.Ordinal);
if(Authenticate != null)
return Authenticate(userPass[0], userPass[1]);
if(AuthenticateWithContext != null)
return AuthenticateWithContext(ctx,userPass[0],userPass[2]);
}
}else{
if(Authenticate != null)
return Authenticate("", "");
if(AuthenticateWithContext != null)
return AuthenticateWithContext(ctx,"","");
}
return false;
}
private async Task<bool> Authorize(ServerContext ctx)
{
if (Authenticate == null && AuthenticateWithContext == null)
return true;
if (ValidAuth(ctx))
return true;
ctx.Response.Headers.Add("WWW-Authenticate", $"Basic realm=\"{Realm}\"");
ctx.Response.StatusLine = 401;
await UnauthorizedPage(ctx);
return false;
}
protected virtual async Task UnauthorizedPage(ServerContext ctx)
{
ctx.Response.SendStatusCodeHtml();
await Task.CompletedTask;
}
}
}