Add simple authentication feature

This commit is contained in:
khanhna
2021-02-26 20:49:34 +07:00
parent 195c58bbbc
commit 965148bdd6
20 changed files with 443 additions and 91 deletions

View File

@@ -1,9 +1,4 @@
using System; namespace SilkierQuartz.Example
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SilkierQuartz.Example
{ {
public class AppSettings public class AppSettings
{ {

View File

@@ -1,9 +1,4 @@
using System; using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace SilkierQuartz.Example.Pages namespace SilkierQuartz.Example.Pages

View File

@@ -22,7 +22,7 @@
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-dark" href= "/SilkierQuartz">SilkierQuartz</a> <a class="nav-link text-dark" href= "/SilkierQuartz/Authenticate/Login">SilkierQuartz</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>

View File

@@ -1,15 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SilkierQuartz.Example.Jobs;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Quartz; using Quartz;
using SilkierQuartz.Example.Jobs;
using System.Collections.Generic;
namespace SilkierQuartz.Example namespace SilkierQuartz.Example
{ {
@@ -50,6 +46,8 @@ namespace SilkierQuartz.Example
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.AddSilkierQuartzAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseSilkierQuartz( app.UseSilkierQuartz(
new SilkierQuartzOptions() new SilkierQuartzOptions()
@@ -59,18 +57,22 @@ namespace SilkierQuartz.Example
DefaultDateFormat = "yyyy-MM-dd", DefaultDateFormat = "yyyy-MM-dd",
DefaultTimeFormat = "HH:mm:ss", DefaultTimeFormat = "HH:mm:ss",
CronExpressionOptions = new CronExpressionDescriptor.Options() CronExpressionOptions = new CronExpressionDescriptor.Options()
{ {
DayOfWeekStartIndexZero = false //Quartz uses 1-7 as the range DayOfWeekStartIndexZero = false //Quartz uses 1-7 as the range
} },
AccountName = "admin",
AccountPassword = "password",
IsAuthenticationPersist = false
} }
); );
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapRazorPages(); endpoints.MapRazorPages();
}); });
//How to compatible old code to SilkierQuartz //How to compatible old code to SilkierQuartz
//<2F><><EFBFBD>ɵ<EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD>Ĺ滮Job<6F>Ĵ<EFBFBD><C4B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD>ݵ<EFBFBD>ʾ<EFBFBD><CABE> //<2F><><EFBFBD>ɵ<EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD>Ĺ滮Job<6F>Ĵ<EFBFBD><C4B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD>ݵ<EFBFBD>ʾ<EFBFBD><CABE>
// app.SchedulerJobs(); // app.SchedulerJobs();
#region <EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD> SilkierQuartzAttribe <EFBFBD><EFBFBD><EFBFBD>ԵĽ<EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD>IJob<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD>UseQuartzJob<EFBFBD><EFBFBD>IJob<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ConfigureServices<EFBFBD><EFBFBD><EFBFBD><EFBFBD>AddQuartzJob #region <EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD> SilkierQuartzAttribe <EFBFBD><EFBFBD><EFBFBD>ԵĽ<EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD>IJob<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD>UseQuartzJob<EFBFBD><EFBFBD>IJob<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ConfigureServices<EFBFBD><EFBFBD><EFBFBD><EFBFBD>AddQuartzJob

View File

@@ -1,14 +1,11 @@
 
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Quartz; using Quartz;
using Quartz.Impl; using Quartz.Impl;
using SilkierQuartz; using SilkierQuartz.Middlewares;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
@@ -34,9 +31,9 @@ namespace SilkierQuartz
/// <param name="app"></param> /// <param name="app"></param>
/// <param name="schedName"></param> /// <param name="schedName"></param>
/// <returns></returns> /// <returns></returns>
public static IScheduler GetScheduler(this IApplicationBuilder app,string schedName) public static IScheduler GetScheduler(this IApplicationBuilder app, string schedName)
{ {
return app.ApplicationServices.GetRequiredService<ISchedulerFactory>().GetScheduler(schedName ).Result; return app.ApplicationServices.GetRequiredService<ISchedulerFactory>().GetScheduler(schedName).Result;
} }
/// <summary> /// <summary>
/// Returns handles to all known Schedulers (made by any SchedulerFactory within this app domain. /// Returns handles to all known Schedulers (made by any SchedulerFactory within this app domain.
@@ -73,7 +70,7 @@ namespace SilkierQuartz
{ {
options.Scheduler = null; options.Scheduler = null;
} }
if (options.Scheduler==null) if (options.Scheduler == null)
{ {
options.Scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; options.Scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
} }
@@ -90,46 +87,54 @@ namespace SilkierQuartz
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllerRoute(nameof(SilkierQuartz), $"{options.VirtualPathRoot}/{{controller=Scheduler}}/{{action=Index}}"); endpoints.MapControllerRoute(nameof(SilkierQuartz), $"{options.VirtualPathRoot}/{{controller=Scheduler}}/{{action=Index}}");
SilkierQuartzAuthenticateConfig.VirtualPathRoot = options.VirtualPathRoot;
SilkierQuartzAuthenticateConfig.VirtualPathRootUrlEncode = options.VirtualPathRoot.Replace("/", "%2F");
SilkierQuartzAuthenticateConfig.UserName = options.AccountName;
SilkierQuartzAuthenticateConfig.UserPassword = options.AccountPassword;
SilkierQuartzAuthenticateConfig.IsPersist = options.IsAuthenticationPersist;
endpoints.MapControllerRoute($"{nameof(SilkierQuartz)}Authenticate",
$"{options.VirtualPathRoot}{{controller=Authenticate}}/{{action=Login}}");
}); });
var types = GetSilkierQuartzJobs(); var types = GetSilkierQuartzJobs();
types.ForEach(t => types.ForEach(t =>
{ {
var so = t.GetCustomAttribute<SilkierQuartzAttribute>(); var so = t.GetCustomAttribute<SilkierQuartzAttribute>();
app.UseQuartzJob( t,() => app.UseQuartzJob(t, () =>
{ {
var tb = TriggerBuilder.Create(); var tb = TriggerBuilder.Create();
tb.WithSimpleSchedule(x => tb.WithSimpleSchedule(x =>
{ {
x.WithInterval(so.WithInterval); x.WithInterval(so.WithInterval);
if (so.RepeatCount>0) if (so.RepeatCount > 0)
{ {
x.WithRepeatCount(so.RepeatCount); x.WithRepeatCount(so.RepeatCount);
} }
else else
{ {
x.RepeatForever(); x.RepeatForever();
} }
}); });
if (so.StartAt== DateTimeOffset.MinValue) if (so.StartAt == DateTimeOffset.MinValue)
{ {
tb.StartNow(); tb.StartNow();
} }
else else
{ {
tb.StartAt(so.StartAt); tb.StartAt(so.StartAt);
} }
var tk = new TriggerKey(!string.IsNullOrEmpty(so.TriggerName) ? so.TriggerName : $"{t.Name}'s Trigger"); var tk = new TriggerKey(!string.IsNullOrEmpty(so.TriggerName) ? so.TriggerName : $"{t.Name}'s Trigger");
if (!string.IsNullOrEmpty(so.TriggerGroup)) if (!string.IsNullOrEmpty(so.TriggerGroup))
{ {
so.TriggerGroup = so.TriggerGroup; so.TriggerGroup = so.TriggerGroup;
} }
tb.WithIdentity(tk); tb.WithIdentity(tk);
tb.WithDescription(so.TriggerDescription ?? $"{t.Name}'s Trigger,full name is {t.FullName}"); tb.WithDescription(so.TriggerDescription ?? $"{t.Name}'s Trigger,full name is {t.FullName}");
if (so.Priority > 0) tb.WithPriority(so.Priority); if (so.Priority > 0) tb.WithPriority(so.Priority);
return tb; return tb;
}); });
}); });
@@ -160,23 +165,48 @@ namespace SilkierQuartz
=> services.AddSilkierQuartz(stdSchedulerFactoryOptions); => services.AddSilkierQuartz(stdSchedulerFactoryOptions);
public static IServiceCollection AddSilkierQuartz(this IServiceCollection services, Action<NameValueCollection> stdSchedulerFactoryOptions = null,Func<List<Assembly>> jobsasmlist=null) public static IServiceCollection AddSilkierQuartz(this IServiceCollection services, Action<NameValueCollection> stdSchedulerFactoryOptions = null, Func<List<Assembly>> jobsasmlist = null)
{ {
services.AddControllers() services.AddControllers()
.AddApplicationPart(Assembly.GetExecutingAssembly()) .AddApplicationPart(Assembly.GetExecutingAssembly())
.AddNewtonsoftJson(); .AddNewtonsoftJson();
services.AddAuthentication(SilkierQuartzAuthenticateConfig.AuthScheme).AddCookie(
SilkierQuartzAuthenticateConfig.AuthScheme,
cfg =>
{
cfg.Cookie.Name = SilkierQuartzAuthenticateConfig.AuthScheme;
cfg.LoginPath = $"{SilkierQuartzAuthenticateConfig.VirtualPathRoot}/Authenticate/Login";
cfg.AccessDeniedPath = $"{SilkierQuartzAuthenticateConfig.VirtualPathRoot}/Authenticate/Login";
if (SilkierQuartzAuthenticateConfig.IsPersist)
{
cfg.ExpireTimeSpan = TimeSpan.FromDays(7);
cfg.SlidingExpiration = true;
}
});
services.AddAuthorization(opts =>
{
opts.AddPolicy(SilkierQuartzAuthenticateConfig.AuthScheme, authBuilder =>
{
authBuilder.RequireAuthenticatedUser();
authBuilder.RequireClaim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim,
SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaimValue);
});
});
services.UseQuartzHostedService(stdSchedulerFactoryOptions); services.UseQuartzHostedService(stdSchedulerFactoryOptions);
var types = GetSilkierQuartzJobs(jobsasmlist?.Invoke()); var types = GetSilkierQuartzJobs(jobsasmlist?.Invoke());
types.ForEach(t => types.ForEach(t =>
{ {
var so = t.GetCustomAttribute<SilkierQuartzAttribute>(); var so = t.GetCustomAttribute<SilkierQuartzAttribute>();
services.AddQuartzJob(t, so.Identity??t.Name, so.Desciption??t.FullName); services.AddQuartzJob(t, so.Identity ?? t.Name, so.Desciption ?? t.FullName);
}); });
return services; return services;
} }
private static List<Type> _silkierQuartzJobs = null; private static List<Type> _silkierQuartzJobs = null;
private static List<Type> GetSilkierQuartzJobs(List<Assembly> lists=null) private static List<Type> GetSilkierQuartzJobs(List<Assembly> lists = null)
{ {
if (_silkierQuartzJobs == null) if (_silkierQuartzJobs == null)
{ {
@@ -200,6 +230,21 @@ namespace SilkierQuartz
} }
return _silkierQuartzJobs; return _silkierQuartzJobs;
} }
/// <summary>
/// Adds the <see cref="SilkierQuartzAuthenticationMiddleware"/> to the specified <see cref="IApplicationBuilder"/>, which enables simple authentication for silkier Quartz.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public static IApplicationBuilder AddSilkierQuartzAuthentication(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<SilkierQuartzAuthenticationMiddleware>();
}
} }
} }

View File

@@ -0,0 +1,110 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SilkierQuartz.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
namespace SilkierQuartz.Controllers
{
[AllowAnonymous]
public class AuthenticateController : PageControllerBase
{
[HttpGet]
public async Task<IActionResult> Login([FromServices] IAuthenticationSchemeProvider schemes)
{
var silkierScheme = await schemes.GetSchemeAsync(SilkierQuartzAuthenticateConfig.AuthScheme);
if (string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserName) ||
string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserPassword))
{
foreach (var userClaim in HttpContext.User.Claims)
{
Debug.WriteLine($"{userClaim.Type} - {userClaim.Value}");
}
if (HttpContext.User == null || !HttpContext.User.Identity.IsAuthenticated ||
!HttpContext.User.HasClaim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim,
SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaimValue))
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserName) ? "SilkierQuartzAdmin" : SilkierQuartzAuthenticateConfig.UserName ),
new Claim(ClaimTypes.Name, string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserPassword) ? "SilkierQuartzPassword" : SilkierQuartzAuthenticateConfig.UserPassword),
new Claim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim, SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaimValue)
};
var authProperties = new AuthenticationProperties()
{
IsPersistent = SilkierQuartzAuthenticateConfig.IsPersist
};
var userIdentity = new ClaimsIdentity(claims, SilkierQuartzAuthenticateConfig.AuthScheme);
await HttpContext.SignInAsync(SilkierQuartzAuthenticateConfig.AuthScheme, new ClaimsPrincipal(userIdentity),
authProperties);
return RedirectToAction(nameof(SchedulerController.Index), nameof(Scheduler));
}
else
{
return RedirectToAction(nameof(SchedulerController.Index), nameof(Scheduler));
}
}
else
{
if (HttpContext.User == null || !HttpContext.User.Identity.IsAuthenticated ||
!HttpContext.User.HasClaim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim, "Authorized"))
{
ViewBag.IsLoginError = false;
return View(new AuthenticateViewModel());
}
else
{
return RedirectToAction(nameof(SchedulerController.Index), nameof(Scheduler));
}
}
}
[HttpPost]
public async Task<IActionResult> Login(AuthenticateViewModel request)
{
if (string.Compare(request.UserName, SilkierQuartzAuthenticateConfig.UserName,
StringComparison.InvariantCulture) != 0 ||
string.Compare(request.Password, SilkierQuartzAuthenticateConfig.UserPassword,
StringComparison.InvariantCulture) != 0)
{
request.IsLoginError = true;
return View(request);
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserName) ? "SilkierQuartzAdmin" : SilkierQuartzAuthenticateConfig.UserName ),
new Claim(ClaimTypes.Name, string.IsNullOrEmpty(SilkierQuartzAuthenticateConfig.UserPassword) ? "SilkierQuartzPassword" : SilkierQuartzAuthenticateConfig.UserPassword),
new Claim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim, "Authorized")
};
var authProperties = new AuthenticationProperties()
{
IsPersistent = request.IsPersist
};
var userIdentity = new ClaimsIdentity(claims, SilkierQuartzAuthenticateConfig.AuthScheme);
await HttpContext.SignInAsync(SilkierQuartzAuthenticateConfig.AuthScheme, new ClaimsPrincipal(userIdentity),
authProperties);
return RedirectToAction(nameof(SchedulerController.Index), nameof(Scheduler));
}
[HttpGet]
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(SilkierQuartzAuthenticateConfig.AuthScheme);
return RedirectToAction(nameof(Login));
}
}
}

View File

@@ -1,14 +1,16 @@
using Quartz; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Quartz;
using SilkierQuartz.Helpers; using SilkierQuartz.Helpers;
using SilkierQuartz.Models; using SilkierQuartz.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class CalendarsController : PageControllerBase public class CalendarsController : PageControllerBase
{ {
[HttpGet] [HttpGet]
@@ -23,7 +25,7 @@ namespace SilkierQuartz.Controllers
var cal = await Scheduler.GetCalendar(name); var cal = await Scheduler.GetCalendar(name);
list.Add(new CalendarListItem() { Name = name, Description = cal.Description, Type = cal.GetType() }); list.Add(new CalendarListItem() { Name = name, Description = cal.Description, Type = cal.GetType() });
} }
return View(list); return View(list);
} }
@@ -81,7 +83,7 @@ namespace SilkierQuartz.Controllers
errors.ForEach(x => x.SegmentIndex = i); errors.ForEach(x => x.SegmentIndex = i);
result.Errors.AddRange(errors); result.Errors.AddRange(errors);
} }
if (result.Success) if (result.Success)
{ {
string name = chain[0].Name; string name = chain[0].Name;
@@ -107,7 +109,7 @@ namespace SilkierQuartz.Controllers
current = newCal; current = newCal;
existing = existing?.CalendarBase; existing = existing?.CalendarBase;
} }
if (root == null) if (root == null)
{ {
result.Errors.Add(new ValidationError() { Field = nameof(CalendarViewModel.Type), Reason = "Cannot create calendar.", SegmentIndex = 0 }); result.Errors.Add(new ValidationError() { Field = nameof(CalendarViewModel.Type), Reason = "Cannot create calendar.", SegmentIndex = 0 });

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SilkierQuartz.Helpers; using SilkierQuartz.Helpers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -6,6 +7,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class ExecutionsController : PageControllerBase public class ExecutionsController : PageControllerBase
{ {
[HttpGet] [HttpGet]

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Quartz.Plugins.RecentHistory; using Quartz.Plugins.RecentHistory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -7,6 +8,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class HistoryController : PageControllerBase public class HistoryController : PageControllerBase
{ {
[HttpGet] [HttpGet]

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using SilkierQuartz.Helpers; using SilkierQuartz.Helpers;
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class JobDataMapController : PageControllerBase public class JobDataMapController : PageControllerBase
{ {
[HttpPost, JsonErrorResponse] [HttpPost, JsonErrorResponse]

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Quartz; using Quartz;
using Quartz.Impl.Matchers; using Quartz.Impl.Matchers;
using Quartz.Plugins.RecentHistory; using Quartz.Plugins.RecentHistory;
@@ -11,6 +12,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class JobsController : PageControllerBase public class JobsController : PageControllerBase
{ {
[HttpGet] [HttpGet]

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@@ -14,6 +15,7 @@ using System.Text.Json;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public abstract partial class PageControllerBase : ControllerBase public abstract partial class PageControllerBase : ControllerBase
{ {
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings() private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings()

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Quartz; using Quartz;
using Quartz.Impl.Matchers; using Quartz.Impl.Matchers;
using Quartz.Plugins.RecentHistory; using Quartz.Plugins.RecentHistory;
@@ -12,6 +13,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class SchedulerController : PageControllerBase public class SchedulerController : PageControllerBase
{ {
[HttpGet] [HttpGet]

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Quartz; using Quartz;
using Quartz.Impl.Matchers; using Quartz.Impl.Matchers;
using Quartz.Plugins.RecentHistory; using Quartz.Plugins.RecentHistory;
@@ -11,6 +12,7 @@ using System.Threading.Tasks;
namespace SilkierQuartz.Controllers namespace SilkierQuartz.Controllers
{ {
[Authorize(SilkierQuartzAuthenticateConfig.AuthScheme)]
public class TriggersController : PageControllerBase public class TriggersController : PageControllerBase
{ {
[HttpGet] [HttpGet]

View File

@@ -0,0 +1,108 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
namespace SilkierQuartz.Middlewares
{
/// <summary>
/// Middleware that performs authentication.
/// </summary>
public class SilkierQuartzAuthenticationMiddleware
{
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of <see cref="Microsoft.AspNetCore.Authentication.AuthenticationMiddleware"/>.
/// </summary>
/// <param name="next">The next item in the middleware pipeline.</param>
/// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
public SilkierQuartzAuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
}
/// <summary>
/// Gets or sets the <see cref="IAuthenticationSchemeProvider"/>.
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// Invokes the middleware performing authentication.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
public async Task Invoke(HttpContext context)
{
var relativePath = GetRelativeUrlPath(context);
if (relativePath.StartsWith(SilkierQuartzAuthenticateConfig.VirtualPathRoot) ||
relativePath.StartsWith("?ReturnUrl") &&
relativePath.Contains(SilkierQuartzAuthenticateConfig.VirtualPathRootUrlEncode))
{
await DetailProcess(context, SilkierQuartzAuthenticateConfig.AuthScheme);
}
await _next(context);
}
public async Task DetailProcess(HttpContext httpContext, string authSchemeName = null)
{
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase
});
// Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler handler &&
await handler.HandleRequestAsync())
{
return;
}
}
var authScheme = string.IsNullOrEmpty(authSchemeName)
? await Schemes.GetDefaultAuthenticateSchemeAsync()
: await Schemes.GetSchemeAsync(authSchemeName);
if (authScheme != null)
{
var result = await httpContext.AuthenticateAsync(authScheme.Name);
if (result.Principal == null || !result.Principal.HasClaim(SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaim,
SilkierQuartzAuthenticateConfig.SilkierQuartzSpecificClaimValue))
{
return;
}
if (result?.Principal != null)
{
httpContext.User = result.Principal;
}
}
}
public string GetRelativeUrlPath(HttpContext httpContext)
{
/*
In some cases, like when running integration tests with WebApplicationFactory<T>
the RawTarget returns an empty string instead of null, in that case we can't use
?? as fallback.
*/
if (httpContext == null)
{
return string.Empty;
}
var requestPath = httpContext.Features.Get<IHttpRequestFeature>()?.RawTarget;
if (string.IsNullOrEmpty(requestPath))
{
requestPath = httpContext.Request.Path.ToString();
}
return requestPath;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace SilkierQuartz.Models
{
public class AuthenticateViewModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
public bool IsPersist { get; set; }
public bool IsLoginError { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
namespace SilkierQuartz
{
internal class SilkierQuartzAuthenticateConfig
{
internal static string VirtualPathRoot = string.Empty;
internal static string VirtualPathRootUrlEncode = string.Empty;
internal static string UserName;
internal static string UserPassword;
internal static bool IsPersist;
internal const string AuthScheme = "SilkierQuartzAuth";
internal const string SilkierQuartzSpecificClaim = "SilkierQuartzManage";
internal const string SilkierQuartzSpecificClaimValue = "Authorized";
}
}

View File

@@ -23,6 +23,12 @@ namespace SilkierQuartz
public IScheduler Scheduler { get; set; } public IScheduler Scheduler { get; set; }
public string AccountName { get; set; }
public string AccountPassword { get; set; }
public bool IsAuthenticationPersist { get; set; }
/// <summary> /// <summary>
/// Supported value types in job data map. /// Supported value types in job data map.
/// </summary> /// </summary>

View File

@@ -0,0 +1,44 @@
{{!<Layout}}
{{ViewBag Title='Login'}}
<div class="ui inverted page dimmer" id="dimmer"><div class="ui loader"></div></div>
<form class="ui form" method="post" enctype="multipart/form-data">
<div class="ui clearing basic segment" style="padding: 0px" id="header">
<h1 class="ui left floated header">
Login
</h1>
</div>
{{#with Model}}
<div class="ui segment" style="width: 700px; height: 250px;">
<div>
<div class="field accept-error">
<label>Name</label>
<input type="text" placeholder="User Name" value="{{UserName}}" name="userName" />
</div>
<div class="field accept-error">
<label>Password</label>
<input type="password" placeholder="User Name" value="{{Password}}" name="password" />
</div>
<div class="field accept-error">
<div class="ui checkbox">
<input type="checkbox" value="False" {{IsPersist}} />
<label>Remember Me</label>
</div>
</div>
</div>
<div style="float: left; margin-top: 15px;">
<div class="ui buttons">
<button type="submit" class="ui primary button">Login</button>
</div>
</div>
</div>
{{#if IsLoginError}}
<div class="ui negative message">
<p>User Name or Password is incorrect!</p>
</div>
{{/if}}
{{/with}}
</form>
<script src="Content/Scripts/post-validation.js"></script>

View File

@@ -41,25 +41,26 @@
</a> </a>
</div> </div>
{{MenuItemActionLink text='Overview' controller='Scheduler'}} {{MenuItemActionLink title='Overview' controller='Scheduler'}}
{{MenuItemActionLink 'Jobs'}} {{MenuItemActionLink 'Jobs'}}
{{MenuItemActionLink 'Triggers'}} {{MenuItemActionLink 'Triggers'}}
{{MenuItemActionLink 'Executions'}} {{MenuItemActionLink 'Executions'}}
{{MenuItemActionLink 'History'}} {{MenuItemActionLink 'History'}}
{{MenuItemActionLink 'Calendars'}} {{MenuItemActionLink 'Calendars'}}
{{MenuItemActionLink title='Logout' controller='Authenticate/Logout'}}
<!-- <!--
<div class="right menu"> <div class="right menu">
<div class="ui dropdown item"> <div class="ui dropdown item">
<i class="user circle large icon"></i> <i class="user circle large icon"></i>
domain\user domain\user
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<a class="item center" href="#">Logout</a> <a class="item center" href="#">Logout</a>
</div>
</div>
</div> </div>
--> </div>
</div>
-->
</div> </div>
</div> </div>