using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using NTDLS.Helpers; using System.Security.Claims; using TightWiki.Exceptions; using TightWiki.Library; using TightWiki.Library.Interfaces; using TightWiki.Models; using TightWiki.Models.DataModels; using TightWiki.Repository; using static TightWiki.Library.Constants; namespace TightWiki { public class SessionState : ISessionState { public IQueryCollection? QueryString { get; set; } #region Authentication. public bool IsAuthenticated { get; set; } public IAccountProfile? Profile { get; set; } public string Role { get; set; } = string.Empty; public Theme UserTheme { get; set; } = new(); #endregion #region Current Page. /// /// Custom page title set by a call to @@Title("...") /// public string? PageTitle { get; set; } public bool ShouldCreatePage { get; set; } public string PageNavigation { get; set; } = string.Empty; public string PageNavigationEscaped { get; set; } = string.Empty; public string PageTags { get; set; } = string.Empty; public ProcessingInstructionCollection PageInstructions { get; set; } = new(); /// /// The "page" here is more of a "mock page", we use the name for various stuff. /// public IPage Page { get; set; } = new Models.DataModels.Page() { Name = GlobalConfiguration.Name }; #endregion public SessionState Hydrate(SignInManager signInManager, PageModel pageModel) { QueryString = pageModel.Request.Query; HydrateSecurityContext(pageModel.HttpContext, signInManager, pageModel.User); return this; } public SessionState Hydrate(SignInManager signInManager, Controller controller) { QueryString = controller.Request.Query; PageNavigation = RouteValue("givenCanonical", "Home"); PageNavigationEscaped = Uri.EscapeDataString(PageNavigation); HydrateSecurityContext(controller.HttpContext, signInManager, controller.User); string RouteValue(string key, string defaultValue = "") { if (controller.RouteData.Values.ContainsKey(key)) { return controller.RouteData.Values[key]?.ToString() ?? defaultValue; } return defaultValue; } return this; } private void HydrateSecurityContext(HttpContext httpContext, SignInManager signInManager, ClaimsPrincipal user) { IsAuthenticated = false; UserTheme = GlobalConfiguration.SystemTheme; if (signInManager.IsSignedIn(user)) { try { string emailAddress = (user.Claims.First(x => x.Type == ClaimTypes.Email)?.Value).EnsureNotNull(); if (user.Identity?.IsAuthenticated == true) { var userId = Guid.Parse((user.Claims.First(x => x.Type == ClaimTypes.NameIdentifier)?.Value).EnsureNotNull()); if (UsersRepository.TryGetBasicProfileByUserId(userId, out var profile)) { Profile = profile; Role = Profile.Role; UserTheme = ConfigurationRepository.GetAllThemes().SingleOrDefault(o => o.Name == Profile.Theme) ?? GlobalConfiguration.SystemTheme; IsAuthenticated = true; } else { //User is signed in, but does not have a profile. //This likely means that the user has authenticated externally, but has yet to complete the signup process. } } } catch (Exception ex) { httpContext.SignOutAsync(); if (user.Identity != null) { httpContext.SignOutAsync(user.Identity.AuthenticationType); } ExceptionRepository.InsertException(ex); } } } /// /// Sets the current context pageId and optionally the revision. /// /// /// /// public void SetPageId(int? pageId, int? revision = null) { Page = new Models.DataModels.Page(); PageInstructions = new(); PageTags = string.Empty; if (pageId != null) { Page = PageRepository.GetLimitedPageInfoByIdAndRevision((int)pageId, revision) ?? throw new Exception("Page not found"); PageInstructions = PageRepository.GetPageProcessingInstructionsByPageId(Page.Id); if (GlobalConfiguration.IncludeWikiTagsInMeta) { PageTags = string.Join(",", PageRepository.GetPageTagsById(Page.Id)?.Select(o => o.Tag) ?? []); } } } #region Permissions. public bool IsMemberOf(string role, string[] roles) => roles.Contains(role); public void RequireAuthorizedPermission() { if (!IsAuthenticated) throw new UnauthorizedException(); } public void RequireEditPermission() { if (!CanEdit) throw new UnauthorizedException(); } public void RequireViewPermission() { if (!CanView) throw new UnauthorizedException(); } public void RequireAdminPermission() { if (!CanAdmin) throw new UnauthorizedException(); } public void RequireModeratePermission() { if (!CanModerate) throw new UnauthorizedException(); } public void RequireCreatePermission() { if (!CanCreate) throw new UnauthorizedException(); } public void RequireDeletePermission() { if (!CanDelete) throw new UnauthorizedException(); } /// /// Is the current user (or anonymous) allowed to view? /// public bool CanView => true; /// /// Is the current user allowed to edit? /// public bool CanEdit { get { if (IsAuthenticated) { if (PageInstructions.Contains(WikiInstruction.Protect)) { return IsMemberOf(Role, [Roles.Administrator, Roles.Moderator]); } return IsMemberOf(Role, [Roles.Administrator, Roles.Contributor, Roles.Moderator]); } return false; } } /// /// Is the current user allowed to perform administrative functions? /// public bool CanAdmin => IsAuthenticated && IsMemberOf(Role, [Roles.Administrator]); /// /// Is the current user allowed to moderate content (such as delete comments, and view moderation tools)? /// public bool CanModerate => IsAuthenticated && IsMemberOf(Role, [Roles.Administrator, Roles.Moderator]); /// /// Is the current user allowed to create pages? /// public bool CanCreate => IsAuthenticated && IsMemberOf(Role, [Roles.Administrator, Roles.Contributor, Roles.Moderator]); /// /// Is the current user allowed to delete unprotected pages? /// public bool CanDelete { get { if (IsAuthenticated) { if (PageInstructions.Contains(WikiInstruction.Protect)) { return false; } return IsMemberOf(Role, [Roles.Administrator, Roles.Moderator]); } return false; } } #endregion public DateTime LocalizeDateTime(DateTime datetime) { return TimeZoneInfo.ConvertTimeFromUtc(datetime, GetPreferredTimeZone()); } public TimeZoneInfo GetPreferredTimeZone() { if (string.IsNullOrEmpty(Profile?.TimeZone)) { return TimeZoneInfo.FindSystemTimeZoneById(GlobalConfiguration.DefaultTimeZone); } return TimeZoneInfo.FindSystemTimeZoneById(Profile.TimeZone); } } }