using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Oqtane.Models; using Oqtane.Shared; using Oqtane.Security; using System.Linq; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Repository; using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Options; namespace Oqtane.Controllers { [Route(ControllerRoutes.ApiRoute)] public class SettingController : Controller { private readonly ISettingRepository _settings; private readonly IPageModuleRepository _pageModules; private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; private readonly IOptions _cookieOptions; private readonly IOptionsSnapshot _cookieOptionsSnapshot; private readonly IOptionsMonitorCache _cookieOptionsMonitorCache; private readonly IOptions _oidcOptions; private readonly IOptionsSnapshot _oidcOptionsSnapshot; private readonly IOptionsMonitorCache _oidcOptionsMonitorCache; private readonly IOptions _oauthOptions; private readonly IOptionsSnapshot _oauthOptionsSnapshot; private readonly IOptionsMonitorCache _oauthOptionsMonitorCache; private readonly IOptions _identityOptions; private readonly IOptionsSnapshot _identityOptionsSnapshot; private readonly IOptionsMonitorCache _identityOptionsMonitorCache; private readonly ILogManager _logger; private readonly Alias _alias; private readonly string _visitorCookie; public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IOptions cookieOptions, IOptionsSnapshot cookieOptionsSnapshot, IOptionsMonitorCache cookieOptionsMonitorCache, IOptions oidcOptions, IOptionsSnapshot oidcOptionsSnapshot, IOptionsMonitorCache oidcOptionsMonitorCache, IOptions oauthOptions, IOptionsSnapshot oauthOptionsSnapshot, IOptionsMonitorCache oauthOptionsMonitorCache, IOptions identityOptions, IOptionsSnapshot identityOptionsSnapshot, IOptionsMonitorCache identityOptionsMonitorCache, ILogManager logger) { _settings = settings; _pageModules = pageModules; _userPermissions = userPermissions; _syncManager = syncManager; _cookieOptions = cookieOptions; _cookieOptionsSnapshot = cookieOptionsSnapshot; _cookieOptionsMonitorCache = cookieOptionsMonitorCache; _oidcOptions = oidcOptions; _oidcOptionsSnapshot = oidcOptionsSnapshot; _oidcOptionsMonitorCache = oidcOptionsMonitorCache; _oauthOptions = oauthOptions; _oauthOptionsSnapshot = oauthOptionsSnapshot; _oauthOptionsMonitorCache = oauthOptionsMonitorCache; _identityOptions = identityOptions; _identityOptionsSnapshot = identityOptionsSnapshot; _identityOptionsMonitorCache = identityOptionsMonitorCache; _logger = logger; _alias = tenantManager.GetAlias(); _visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString(); } // GET: api/ [HttpGet] public IEnumerable Get(string entityName, int entityId) { List settings = new List(); if (IsAuthorized(entityName, entityId, PermissionNames.View)) { settings = _settings.GetSettings(entityName, entityId).ToList(); if (FilterPrivate(entityName, entityId)) { settings = settings.Where(item => !item.IsPrivate).ToList(); } } else { // suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies or private browsing sessions if (entityName != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } return settings; } // GET api//5/xxx [HttpGet("{id}/{entityName}")] public Setting Get(int id, string entityName) { Setting setting = _settings.GetSetting(entityName, id); if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.View)) { if (FilterPrivate(entityName, id) && setting.IsPrivate) { setting = null; } return setting; } else { if (setting != null && entityName != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } else { HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; } return null; } } // POST api/ [HttpPost] public Setting Post([FromBody] Setting setting) { if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { setting = _settings.AddSetting(setting); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting); } else { if (setting.EntityName != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } setting = null; } return setting; } // PUT api//5 [HttpPut("{id}")] public Setting Put(int id, [FromBody] Setting setting) { if (ModelState.IsValid && setting.SettingId == id && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { setting = _settings.UpdateSetting(setting); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting); } else { if (setting.EntityName != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } setting = null; } return setting; } // PUT api//site/1/settingname/x/false [HttpPut("{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}")] public void Put(string entityName, int entityId, string settingName, string settingValue, bool isPrivate) { if (IsAuthorized(entityName, entityId, PermissionNames.Edit)) { Setting setting = _settings.GetSetting(entityName, entityId, settingName); if (setting == null) { setting = new Setting(); setting.EntityName = entityName; setting.EntityId = entityId; setting.SettingName = settingName; setting.SettingValue = settingValue; setting.IsPrivate = isPrivate; setting = _settings.AddSetting(setting); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting); } else { if (setting.SettingValue != settingValue || setting.IsPrivate != isPrivate) { setting.SettingValue = settingValue; setting.IsPrivate = isPrivate; setting = _settings.UpdateSetting(setting); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting); } } } else { _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Add Or Update Setting {EntityName} {EntityId} {SettingName}", entityName, entityId, settingName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } // DELETE api//site/1/settingname [HttpDelete("{entityName}/{entityId}/{settingName}")] public void Delete(string entityName, int entityId, string settingName) { Setting setting = _settings.GetSetting(entityName, entityId, settingName); if (setting != null && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { _settings.DeleteSetting(setting.EntityName, setting.SettingId); AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting); } else { if (entityName != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For Entity {EntityName} Id {EntityId} Name {SettingName}", entityName, entityId, settingName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } } // DELETE api//clear [HttpDelete("clear")] [Authorize(Roles = RoleNames.Admin)] public void Clear() { (_cookieOptions as SiteOptionsManager).Reset(); (_cookieOptionsSnapshot as SiteOptionsManager).Reset(); _cookieOptionsMonitorCache.Clear(); (_oidcOptions as SiteOptionsManager).Reset(); (_oidcOptionsSnapshot as SiteOptionsManager).Reset(); _oidcOptionsMonitorCache.Clear(); (_oauthOptions as SiteOptionsManager).Reset(); (_oauthOptionsSnapshot as SiteOptionsManager).Reset(); _oauthOptionsMonitorCache.Clear(); (_identityOptions as SiteOptionsManager).Reset(); (_identityOptionsSnapshot as SiteOptionsManager).Reset(); _identityOptionsMonitorCache.Clear(); _logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared"); } private bool IsAuthorized(string entityName, int entityId, string permissionName) { bool authorized = false; if (entityName == EntityNames.PageModule) { entityName = EntityNames.Module; entityId = _pageModules.GetPageModule(entityId).ModuleId; } switch (entityName) { case EntityNames.Tenant: case EntityNames.ModuleDefinition: case EntityNames.Host: case EntityNames.Job: case EntityNames.Theme: if (permissionName == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Host); } else { authorized = true; } break; case EntityNames.Site: if (permissionName == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Admin); } else { authorized = true; } break; case EntityNames.Page: case EntityNames.Module: case EntityNames.Folder: authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); break; case EntityNames.User: authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); break; case EntityNames.Visitor: authorized = User.IsInRole(RoleNames.Admin); if (!authorized) { // a visitor may have cookies disabled if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId)) { authorized = (visitorId == entityId); } } break; default: // custom entity authorized = true; if (permissionName == PermissionNames.Edit) { authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName) || _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin); } break; } return authorized; } private bool FilterPrivate(string entityName, int entityId) { bool filter = false; switch (entityName) { case EntityNames.Tenant: case EntityNames.ModuleDefinition: case EntityNames.Host: case EntityNames.Job: case EntityNames.Theme: filter = !User.IsInRole(RoleNames.Host); break; case EntityNames.Site: filter = !User.IsInRole(RoleNames.Admin); break; case EntityNames.Page: case EntityNames.Module: case EntityNames.Folder: filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); break; case EntityNames.User: filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId; break; case EntityNames.Visitor: if (!User.IsInRole(RoleNames.Admin)) { filter = true; if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId)) { filter = (visitorId != entityId); } } break; default: // custom entity filter = !User.IsInRole(RoleNames.Admin) && !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); break; } return filter; } private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action) { _syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action); switch (EntityName) { case EntityNames.Module: case EntityNames.Page: case EntityNames.Site: _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); break; case EntityNames.User: _syncManager.AddSyncEvent(_alias, EntityName, EntityId, SyncEventActions.Update); break; } } } }