Permissions & Authorization for Users #24
Initial Release; Includes Database and MVC refactoring
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Authorization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Users
|
||||
{
|
||||
internal static class Cache
|
||||
{
|
||||
private static ConcurrentDictionary<string, Tuple<User, AuthorizationToken, DateTime>> _Cache = new ConcurrentDictionary<string, Tuple<User, AuthorizationToken, DateTime>>();
|
||||
private const long CacheTimeoutTicks = 6000000000; // 10 Minutes
|
||||
|
||||
internal static AuthorizationToken GetAuthorization(string UserId, DiscoDataContext Database, bool ForceRefresh)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record = Get(UserId, Database, ForceRefresh);
|
||||
|
||||
if (record == null)
|
||||
return null;
|
||||
else
|
||||
return record.Item2;
|
||||
}
|
||||
internal static AuthorizationToken GetAuthorization(string UserId, bool ForceRefresh)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record = Get(UserId, ForceRefresh);
|
||||
|
||||
if (record == null)
|
||||
return null;
|
||||
else
|
||||
return record.Item2;
|
||||
}
|
||||
internal static AuthorizationToken GetAuthorization(string UserId, DiscoDataContext Database)
|
||||
{
|
||||
return GetAuthorization(UserId, Database, false);
|
||||
}
|
||||
internal static AuthorizationToken GetAuthorization(string UserId)
|
||||
{
|
||||
return GetAuthorization(UserId, false);
|
||||
}
|
||||
|
||||
internal static User GetUser(string UserId, DiscoDataContext Database, bool ForceRefresh)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record = Get(UserId, Database, ForceRefresh);
|
||||
|
||||
if (record == null)
|
||||
return null;
|
||||
else
|
||||
return record.Item1;
|
||||
}
|
||||
internal static User GetUser(string UserId, bool ForceRefresh)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record = Get(UserId, ForceRefresh);
|
||||
|
||||
if (record == null)
|
||||
return null;
|
||||
else
|
||||
return record.Item1;
|
||||
}
|
||||
internal static User GetUser(string UserId, DiscoDataContext Database)
|
||||
{
|
||||
return GetUser(UserId, Database, false);
|
||||
}
|
||||
internal static User GetUser(string UserId)
|
||||
{
|
||||
return GetUser(UserId, false);
|
||||
}
|
||||
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> Get(string UserId, DiscoDataContext Database, bool ForceRefresh)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record = null;
|
||||
|
||||
// Check Cache
|
||||
if (!ForceRefresh)
|
||||
record = TryUserCache(UserId);
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
var importedUser = UserService.ImportUser(Database, UserId);
|
||||
record = SetValue(UserId, importedUser);
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> Get(string UserId, DiscoDataContext Database)
|
||||
{
|
||||
return Get(UserId, Database, false);
|
||||
}
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> Get(string UserId, bool ForceRefresh)
|
||||
{
|
||||
// Check Cache
|
||||
Tuple<User, AuthorizationToken, DateTime> record = null;
|
||||
|
||||
if (!ForceRefresh)
|
||||
record = TryUserCache(UserId);
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
// Load from Repository
|
||||
using (DiscoDataContext database = new DiscoDataContext())
|
||||
{
|
||||
record = Get(UserId, database, true);
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> Get(string UserId)
|
||||
{
|
||||
return Get(UserId, false);
|
||||
}
|
||||
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> TryUserCache(string UserId)
|
||||
{
|
||||
var cache = _Cache;
|
||||
|
||||
string userId = UserId.ToLower();
|
||||
Tuple<User, AuthorizationToken, DateTime> record;
|
||||
if (cache.TryGetValue(userId, out record))
|
||||
{
|
||||
if (record.Item3 > DateTime.Now)
|
||||
return record;
|
||||
else
|
||||
cache.TryRemove(userId, out record);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static Tuple<User, AuthorizationToken, DateTime> SetValue(string UserId, Tuple<User, AuthorizationToken> Record)
|
||||
{
|
||||
var cache = _Cache;
|
||||
|
||||
string userId = UserId.ToLower();
|
||||
Tuple<User, AuthorizationToken, DateTime> record = new Tuple<User, AuthorizationToken, DateTime>(Record.Item1, Record.Item2, DateTime.Now.AddTicks(CacheTimeoutTicks));
|
||||
if (cache.ContainsKey(userId))
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> oldRecord;
|
||||
if (cache.TryGetValue(userId, out oldRecord))
|
||||
{
|
||||
cache.TryUpdate(userId, record, oldRecord);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
cache.TryAdd(userId, record);
|
||||
return record;
|
||||
}
|
||||
|
||||
internal static bool InvalidateRecord(string UserId)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> userRecord;
|
||||
return _Cache.TryRemove(UserId, out userRecord);
|
||||
}
|
||||
|
||||
internal static void CleanStaleCache()
|
||||
{
|
||||
var cache = _Cache;
|
||||
|
||||
var userIds = cache.Keys.ToArray();
|
||||
foreach (string userId in userIds)
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> record;
|
||||
if (cache.TryGetValue(userId, out record))
|
||||
{
|
||||
if (record.Item3 <= DateTime.Now)
|
||||
cache.TryRemove(userId, out record);
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static void FlushCache()
|
||||
{
|
||||
_Cache = new ConcurrentDictionary<string, Tuple<User, AuthorizationToken, DateTime>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Users
|
||||
{
|
||||
public class CacheCleanTask : ScheduledTask
|
||||
{
|
||||
public override string TaskName { get { return "User Cache - Clean Stale Cache"; } }
|
||||
|
||||
public override bool SingleInstanceTask { get { return true; } }
|
||||
public override bool CancelInitiallySupported { get { return false; } }
|
||||
public override bool LogExceptionsOnly { get { return true; } }
|
||||
|
||||
public override void InitalizeScheduledTask(DiscoDataContext Database)
|
||||
{
|
||||
// Run @ every 15mins
|
||||
|
||||
// Next 15min interval
|
||||
DateTime now = DateTime.Now;
|
||||
int mins = (15 - (now.Minute % 15));
|
||||
if (mins < 10)
|
||||
mins += 15;
|
||||
DateTimeOffset startAt = new DateTimeOffset(now).AddMinutes(mins).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
|
||||
|
||||
TriggerBuilder triggerBuilder = TriggerBuilder.Create().StartAt(startAt).
|
||||
WithSchedule(SimpleScheduleBuilder.RepeatMinutelyForever(15));
|
||||
|
||||
this.ScheduleTask(triggerBuilder);
|
||||
}
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
Cache.CleanStaleCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Users
|
||||
{
|
||||
public static class Searching
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Interop.ActiveDirectory;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Authorization.Roles;
|
||||
using Disco.Services.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Entity.Infrastructure;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Disco.Services.Users
|
||||
{
|
||||
public static class UserService
|
||||
{
|
||||
private const string _cacheHttpRequestKey = "Disco_CurrentUserToken";
|
||||
private static Func<string, string[], ActiveDirectoryUserAccount> _GetActiveDirectoryUserAccount;
|
||||
private static Func<string, string[], ActiveDirectoryMachineAccount> _GetActiveDirectoryMachineAccount;
|
||||
|
||||
public static void Initialize(DiscoDataContext Database,
|
||||
Func<string, string[], ActiveDirectoryUserAccount> GetActiveDirectoryUserAccount,
|
||||
Func<string, string[], ActiveDirectoryMachineAccount> GetActiveDirectoryMachineAccount)
|
||||
{
|
||||
_GetActiveDirectoryUserAccount = GetActiveDirectoryUserAccount;
|
||||
_GetActiveDirectoryMachineAccount = GetActiveDirectoryMachineAccount;
|
||||
|
||||
Authorization.Roles.RoleCache.Initialize(Database);
|
||||
}
|
||||
|
||||
public static string CurrentUserId
|
||||
{
|
||||
get
|
||||
{
|
||||
// Check for ASP.NET
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
if (HttpContext.Current.Request.IsAuthenticated)
|
||||
return HttpContext.Current.User.Identity.Name;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// User default User
|
||||
return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tuple<User, AuthorizationToken, DateTime> CurrentUserToken
|
||||
{
|
||||
get
|
||||
{
|
||||
Tuple<User, AuthorizationToken, DateTime> token = null;
|
||||
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
if (HttpContext.Current.Request.IsAuthenticated)
|
||||
token = (Tuple<User, AuthorizationToken, DateTime>)HttpContext.Current.Items[_cacheHttpRequestKey];
|
||||
else
|
||||
return null; // Not Authenticated
|
||||
}
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
var userId = CurrentUserId;
|
||||
|
||||
if (userId != null)
|
||||
{
|
||||
token = Cache.Get(userId);
|
||||
|
||||
if (HttpContext.Current != null && HttpContext.Current.Request.IsAuthenticated)
|
||||
HttpContext.Current.Items[_cacheHttpRequestKey] = token;
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
public static User CurrentUser
|
||||
{
|
||||
get
|
||||
{
|
||||
var token = CurrentUserToken;
|
||||
|
||||
if (token == null)
|
||||
return null;
|
||||
else
|
||||
return token.Item1;
|
||||
}
|
||||
}
|
||||
public static AuthorizationToken CurrentAuthorization
|
||||
{
|
||||
get
|
||||
{
|
||||
var token = CurrentUserToken;
|
||||
|
||||
if (token == null)
|
||||
return null;
|
||||
else
|
||||
return token.Item2;
|
||||
}
|
||||
}
|
||||
|
||||
public static User GetUser(string UserId)
|
||||
{
|
||||
return Cache.GetUser(UserId);
|
||||
}
|
||||
public static User GetUser(string UserId, DiscoDataContext Database)
|
||||
{
|
||||
return Cache.GetUser(UserId, Database);
|
||||
}
|
||||
public static User GetUser(string UserId, DiscoDataContext Database, bool ForceRefresh)
|
||||
{
|
||||
return Cache.GetUser(UserId, Database, ForceRefresh);
|
||||
}
|
||||
|
||||
public static AuthorizationToken GetAuthorization(string UserId)
|
||||
{
|
||||
return Cache.GetAuthorization(UserId);
|
||||
}
|
||||
public static AuthorizationToken GetAuthorization(string UserId, DiscoDataContext Database)
|
||||
{
|
||||
return Cache.GetAuthorization(UserId, Database);
|
||||
}
|
||||
public static AuthorizationToken GetAuthorization(string UserId, DiscoDataContext Database, bool ForceRefresh)
|
||||
{
|
||||
return Cache.GetAuthorization(UserId, Database, ForceRefresh);
|
||||
}
|
||||
|
||||
public static bool InvalidateCachedUser(string UserId)
|
||||
{
|
||||
return Cache.InvalidateRecord(UserId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int CreateAuthorizationRole(DiscoDataContext Database, AuthorizationRole Role)
|
||||
{
|
||||
if (Role == null)
|
||||
throw new ArgumentNullException("Role");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Role.ClaimsJson))
|
||||
Role.ClaimsJson = JsonConvert.SerializeObject(new RoleClaims());
|
||||
|
||||
Database.AuthorizationRoles.Add(Role);
|
||||
Database.SaveChanges();
|
||||
|
||||
// Add to Cache
|
||||
RoleCache.AddRole(Role);
|
||||
|
||||
// Flush User Cache
|
||||
Cache.FlushCache();
|
||||
|
||||
return Role.Id;
|
||||
}
|
||||
public static void DeleteAuthorizationRole(DiscoDataContext Database, AuthorizationRole Role)
|
||||
{
|
||||
if (Role == null)
|
||||
throw new ArgumentNullException("Role");
|
||||
|
||||
Database.AuthorizationRoles.Remove(Role);
|
||||
Database.SaveChanges();
|
||||
|
||||
// Remove from Role Cache
|
||||
RoleCache.RemoveRole(Role);
|
||||
|
||||
// Flush User Cache
|
||||
Cache.FlushCache();
|
||||
}
|
||||
public static void UpdateAuthorizationRole(DiscoDataContext Database, AuthorizationRole Role)
|
||||
{
|
||||
if (Role == null)
|
||||
throw new ArgumentNullException("Role");
|
||||
if (Database == null)
|
||||
throw new ArgumentNullException("Database");
|
||||
|
||||
Database.SaveChanges();
|
||||
|
||||
// Update Role Cache
|
||||
RoleCache.UpdateRole(Role);
|
||||
|
||||
// Flush User Cache
|
||||
Cache.FlushCache();
|
||||
}
|
||||
|
||||
internal static Tuple<User, AuthorizationToken> ImportUser(DiscoDataContext Database, string UserId)
|
||||
{
|
||||
if (_GetActiveDirectoryUserAccount == null)
|
||||
throw new InvalidOperationException("UserServer has not been Initialized");
|
||||
if (string.IsNullOrEmpty(UserId))
|
||||
throw new ArgumentNullException("UserId is required", "UserId");
|
||||
|
||||
if (UserId.EndsWith("$"))
|
||||
{
|
||||
// Machine Account
|
||||
var adAccount = _GetActiveDirectoryMachineAccount(UserId, null);
|
||||
|
||||
if (adAccount == null)
|
||||
return null;
|
||||
|
||||
var user = adAccount.ToRepositoryUser();
|
||||
var token = AuthorizationToken.BuildComputerAccountToken(user);
|
||||
|
||||
return new Tuple<User, AuthorizationToken>(user, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User Account
|
||||
|
||||
ActiveDirectoryUserAccount adAccount;
|
||||
try
|
||||
{
|
||||
adAccount = _GetActiveDirectoryUserAccount(UserId, null);
|
||||
|
||||
if (adAccount == null)
|
||||
throw new ArgumentException(string.Format("Invalid Username: '{0}'; User doesn't in AD", UserId), "Username");
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
// If "Server is not operational" then Try Cache
|
||||
if (ex.ErrorCode == -2147016646)
|
||||
SystemLog.LogException("Server is not operational; Primary Domain Controller Down?", ex);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
catch (ActiveDirectoryOperationException ex)
|
||||
{
|
||||
// Try From Cache...
|
||||
SystemLog.LogException("Primary Domain Controller Down?", ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
var user = adAccount.ToRepositoryUser();
|
||||
|
||||
// Update Repository
|
||||
User existingUser = Database.Users.Find(user.Id);
|
||||
if (existingUser == null)
|
||||
Database.Users.Add(user);
|
||||
else
|
||||
{
|
||||
existingUser.UpdateSelf(user);
|
||||
user = existingUser;
|
||||
}
|
||||
Database.SaveChanges();
|
||||
|
||||
var token = AuthorizationToken.BuildToken(user, adAccount.Groups);
|
||||
|
||||
return new Tuple<User, AuthorizationToken>(user, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user