Merge pull request #2543 from sbwalker/dev
add support for API permissions at the UI layer - including ability to delegate user, role, profile management
This commit is contained in:
		| @ -49,7 +49,6 @@ namespace Microsoft.Extensions.DependencyInjection | ||||
|             services.AddScoped<IUrlMappingService, UrlMappingService>(); | ||||
|             services.AddScoped<IVisitorService, VisitorService>(); | ||||
|             services.AddScoped<ISyncService, SyncService>(); | ||||
|             services.AddScoped<IApiService, ApiService>(); | ||||
|  | ||||
|             return services; | ||||
|         } | ||||
|  | ||||
| @ -1,75 +0,0 @@ | ||||
| @namespace Oqtane.Modules.Admin.Apis | ||||
| @inherits ModuleBase | ||||
| @inject IApiService ApiService | ||||
| @inject NavigationManager NavigationManager | ||||
| @inject IStringLocalizer<Edit> Localizer | ||||
| @inject IStringLocalizer<SharedResources> SharedLocalizer | ||||
|  | ||||
| <div class="container"> | ||||
| 	<div class="row mb-1 align-items-center"> | ||||
| 		<Label Class="col-sm-3" For="entityname" HelpText="The name of the entity" ResourceKey="EntityName">Entity: </Label> | ||||
| 		<div class="col-sm-9"> | ||||
| 			<input id="entityname" class="form-control" @bind="@_entityname" readonly /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<br /> | ||||
| 	<div class="row mb-1 align-items-center"> | ||||
| 		@if (_permissions != null) | ||||
| 		{ | ||||
| 			<PermissionGrid EntityName="@_entityname" PermissionNames="@_permissionnames" Permissions="@_permissions" @ref="_permissionGrid" /> | ||||
| 		} | ||||
|     </div> | ||||
| </div> | ||||
| <button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button> | ||||
| <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> | ||||
|  | ||||
| @code { | ||||
| 	private string _entityname; | ||||
| 	private string _permissionnames; | ||||
| 	private string _permissions; | ||||
|  | ||||
| #pragma warning disable 649 | ||||
| 	private PermissionGrid _permissionGrid; | ||||
| #pragma warning restore 649 | ||||
|  | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|  | ||||
| 	protected override async Task OnInitializedAsync() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			_entityname = PageState.QueryString["entity"]; | ||||
| 			var api = await ApiService.GetApiAsync(PageState.Site.SiteId, _entityname); | ||||
| 			if (api != null) | ||||
| 			{ | ||||
| 				var apis = await ApiService.GetApisAsync(PageState.Site.SiteId); | ||||
| 				_permissionnames = apis.SingleOrDefault(item => item.EntityName == _entityname).Permissions; | ||||
| 				_permissions = api.Permissions; | ||||
| 			} | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Loading API {EntityName} {Error}", _entityname, ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private async Task SaveModuleDefinition() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			var api = new Api(); | ||||
| 			api.SiteId = PageState.Site.SiteId; | ||||
| 			api.EntityName = _entityname; | ||||
| 			api.Permissions = _permissionGrid.GetPermissions(); | ||||
| 			await ApiService.UpdateApiAsync(api); | ||||
| 			await logger.LogInformation("API Saved {Api}", api); | ||||
| 			NavigationManager.NavigateTo(NavigateUrl()); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Saving Api {EntityName} {Error}", _entityname, ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| @namespace Oqtane.Modules.Admin.Apis | ||||
| @inherits ModuleBase | ||||
| @inject IApiService ApiService | ||||
| @inject IStringLocalizer<Index> Localizer | ||||
| @inject IStringLocalizer<SharedResources> SharedLocalizer | ||||
|  | ||||
| @if (_apis == null) | ||||
| { | ||||
|     <p><em>@SharedLocalizer["Loading"]</em></p> | ||||
| } | ||||
| else | ||||
| { | ||||
|     <Pager Items="@_apis"> | ||||
|         <Header> | ||||
|             <th style="width: 1px;"> </th> | ||||
|             <th>@Localizer["Entity"]</th> | ||||
| 			<th>@Localizer["Permissions"]</th> | ||||
| 		</Header> | ||||
|         <Row> | ||||
| 			<td><ActionLink Action="Edit" Parameters="@($"entity=" + context.EntityName)" ResourceKey="Edit" /></td> | ||||
|             <td>@context.EntityName</td> | ||||
| 			<td>@context.Permissions</td> | ||||
| 		</Row> | ||||
|     </Pager> | ||||
| } | ||||
|  | ||||
| @code { | ||||
|     private List<Api> _apis; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|  | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|         _apis = await ApiService.GetApisAsync(PageState.Site.SiteId); | ||||
|     } | ||||
| } | ||||
| @ -20,13 +20,16 @@ | ||||
| </div> | ||||
|  | ||||
| @code { | ||||
|     private List<Page> _pages; | ||||
| 	private List<Page> _pages; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; | ||||
|  | ||||
|     protected override void OnInitialized() | ||||
|     { | ||||
|         var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); | ||||
|         _pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList(); | ||||
| 	protected override void OnInitialized() | ||||
| 	{ | ||||
| 		var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); | ||||
| 		if (admin != null) | ||||
| 		{ | ||||
| 			_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList(); | ||||
| 		} | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -131,7 +131,7 @@ else | ||||
| 					moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); | ||||
|  | ||||
| 					var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); | ||||
| 					SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName); | ||||
| 					settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName); | ||||
| 					await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); | ||||
|  | ||||
| 					GetLocation(); | ||||
|  | ||||
| @ -157,7 +157,8 @@ | ||||
|             </TabPanel> | ||||
|         } | ||||
|     </TabStrip> | ||||
|     <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> | ||||
| 	<br /> | ||||
| 	<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> | ||||
|     <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> | ||||
| </form> | ||||
|  | ||||
|  | ||||
| @ -148,7 +148,8 @@ | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </Section> | ||||
|                 <br /><br /> | ||||
|                 <br /> | ||||
| 				<br /> | ||||
|                 <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> | ||||
|             } | ||||
|         </TabPanel> | ||||
| @ -189,7 +190,8 @@ | ||||
|             <br /> | ||||
|         } | ||||
|     </TabStrip> | ||||
|     <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> | ||||
| 	<br /> | ||||
| 	<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> | ||||
|     <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> | ||||
| </form> | ||||
|  | ||||
|  | ||||
							
								
								
									
										19
									
								
								Oqtane.Client/Modules/Admin/Profiles/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Oqtane.Client/Modules/Admin/Profiles/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| using Oqtane.Documentation; | ||||
| using Oqtane.Models; | ||||
| using Oqtane.Shared; | ||||
|  | ||||
| namespace Oqtane.Modules.Admin.Profiles | ||||
| { | ||||
|     [PrivateApi("Mark this as private, since it's not very useful in the public docs")] | ||||
|     public class ModuleInfo : IModule | ||||
|     { | ||||
|         public ModuleDefinition ModuleDefinition => new ModuleDefinition | ||||
|         { | ||||
|             Name = "Profiles", | ||||
|             Description = "Manage Profiles", | ||||
|             Categories = "Admin", | ||||
|             Version = Constants.Version, | ||||
|             PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit},{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}" | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -42,7 +42,7 @@ | ||||
|     private string _description = string.Empty; | ||||
|     private string _isautoassigned = "False"; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
|     private async Task SaveRole() | ||||
|     { | ||||
|  | ||||
| @ -49,7 +49,7 @@ | ||||
|     private string _modifiedby; | ||||
|     private DateTime _modifiedon; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
| } | ||||
| else | ||||
| { | ||||
|     <ActionLink Action="Add" Text="Add Role" ResourceKey="AddRole" /> | ||||
| 	<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" /> | ||||
|  | ||||
|     <Pager Items="@_roles"> | ||||
|         <Header> | ||||
| @ -20,9 +20,9 @@ else | ||||
|             <th>@SharedLocalizer["Name"]</th> | ||||
|         </Header> | ||||
|         <Row> | ||||
|             <td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td> | ||||
|             <td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td> | ||||
|             <td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td> | ||||
| 			<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td> | ||||
|             <td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td> | ||||
| 			<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td> | ||||
|             <td>@context.Name</td> | ||||
|         </Row> | ||||
|     </Pager> | ||||
| @ -31,7 +31,7 @@ else | ||||
| @code { | ||||
|     private List<Role> _roles; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; | ||||
|  | ||||
|     protected override async Task OnParametersSetAsync() | ||||
|     { | ||||
|  | ||||
							
								
								
									
										19
									
								
								Oqtane.Client/Modules/Admin/Roles/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Oqtane.Client/Modules/Admin/Roles/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| using Oqtane.Documentation; | ||||
| using Oqtane.Models; | ||||
| using Oqtane.Shared; | ||||
|  | ||||
| namespace Oqtane.Modules.Admin.Roles | ||||
| { | ||||
|     [PrivateApi("Mark this as private, since it's not very useful in the public docs")] | ||||
|     public class ModuleInfo : IModule | ||||
|     { | ||||
|         public ModuleDefinition ModuleDefinition => new ModuleDefinition | ||||
|         { | ||||
|             Name = "Roles", | ||||
|             Description = "Manage Roles", | ||||
|             Categories = "Admin", | ||||
|             Version = Constants.Version, | ||||
|             PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit},{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin},{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}" | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -23,13 +23,7 @@ else | ||||
|             <div class="row mb-1 align-items-center"> | ||||
|                 <Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label> | ||||
|                 <div class="col-sm-9"> | ||||
|                     <select id="user" class="form-select" @bind="@userid" required> | ||||
|                         <option value="-1"><@Localizer["User.Select"]></option> | ||||
|                         @foreach (UserRole userrole in users) | ||||
|                         { | ||||
|                             <option value="@(userrole.UserId)">@userrole.User.DisplayName</option> | ||||
|                         } | ||||
|                     </select> | ||||
| 					<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="row mb-1 align-items-center"> | ||||
| @ -64,7 +58,7 @@ else | ||||
|                             <td>@context.EffectiveDate</td> | ||||
|                             <td>@context.ExpiryDate</td> | ||||
|                             <td> | ||||
|                                 <ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || PageState.User.Username == UserNames.Host)" ResourceKey="DeleteUserRole" /> | ||||
| 								<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" /> | ||||
|                             </td> | ||||
|                         </Row> | ||||
|                     </Pager> | ||||
| @ -75,81 +69,96 @@ else | ||||
| } | ||||
|  | ||||
| @code { | ||||
|     private ElementReference form; | ||||
|     private bool validated = false; | ||||
| 	private ElementReference form; | ||||
| 	private bool validated = false; | ||||
|  | ||||
|     private int roleid; | ||||
|     private string name = string.Empty; | ||||
|     private List<UserRole> users; | ||||
|     private int userid = -1; | ||||
|     private DateTime? effectivedate = null; | ||||
|     private DateTime? expirydate = null; | ||||
|     private List<UserRole> userroles; | ||||
| 	private int roleid; | ||||
| 	private string name = string.Empty; | ||||
| 	private AutoComplete user; | ||||
| 	private DateTime? effectivedate = null; | ||||
| 	private DateTime? expirydate = null; | ||||
| 	private List<UserRole> userroles; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             roleid = Int32.Parse(PageState.QueryString["id"]); | ||||
|             Role role = await RoleService.GetRoleAsync(roleid); | ||||
|             name = role.Name; | ||||
|             users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); | ||||
|             await GetUserRoles(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await logger.LogError(ex, "Error Loading Users {Error}", ex.Message); | ||||
|             AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); | ||||
|         } | ||||
|     } | ||||
| 	protected override async Task OnInitializedAsync() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			roleid = Int32.Parse(PageState.QueryString["id"]); | ||||
| 			Role role = await RoleService.GetRoleAsync(roleid); | ||||
| 			name = role.Name; | ||||
| 			await GetUserRoles(); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Loading Users {Error}", ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|     private async Task GetUserRoles() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message); | ||||
|             AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error); | ||||
|         } | ||||
|     } | ||||
| 	private async Task<Dictionary<string, string>> GetUsers(string filter) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); | ||||
| 			return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) | ||||
| 				.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Loading Users {filter} {Error}", filter, ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); | ||||
| 		} | ||||
| 		return new Dictionary<string, string>(); | ||||
| 	} | ||||
|  | ||||
|     private async Task SaveUserRole() | ||||
|     { | ||||
|         validated = true; | ||||
|         var interop = new Interop(JSRuntime); | ||||
|         if (await interop.FormValid(form)) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (userid != -1) | ||||
|                 { | ||||
|                     var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); | ||||
|                     if (userrole != null) | ||||
|                     { | ||||
|                         userrole.EffectiveDate = effectivedate; | ||||
|                         userrole.ExpiryDate = expirydate; | ||||
|                         await UserRoleService.UpdateUserRoleAsync(userrole); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         userrole = new UserRole(); | ||||
|                         userrole.UserId = userid; | ||||
|                         userrole.RoleId = roleid; | ||||
|                         userrole.EffectiveDate = effectivedate; | ||||
|                         userrole.ExpiryDate = expirydate; | ||||
| 	private async Task GetUserRoles() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|                         await UserRoleService.AddUserRoleAsync(userrole); | ||||
|                     } | ||||
| 	private async Task SaveUserRole() | ||||
| 	{ | ||||
| 		validated = true; | ||||
| 		var interop = new Interop(JSRuntime); | ||||
| 		if (await interop.FormValid(form)) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid)) | ||||
| 				{ | ||||
| 					var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); | ||||
| 					if (userrole != null) | ||||
| 					{ | ||||
| 						userrole.EffectiveDate = effectivedate; | ||||
| 						userrole.ExpiryDate = expirydate; | ||||
| 						await UserRoleService.UpdateUserRoleAsync(userrole); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						userrole = new UserRole(); | ||||
| 						userrole.UserId = userid; | ||||
| 						userrole.RoleId = roleid; | ||||
| 						userrole.EffectiveDate = effectivedate; | ||||
| 						userrole.ExpiryDate = expirydate; | ||||
|  | ||||
|                     await logger.LogInformation("User Assigned To Role {UserRole}", userrole); | ||||
|                     AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); | ||||
|                     await GetUserRoles(); | ||||
|                     StateHasChanged(); | ||||
| 						await UserRoleService.AddUserRoleAsync(userrole); | ||||
| 					} | ||||
|  | ||||
| 					await logger.LogInformation("User Assigned To Role {UserRole}", userrole); | ||||
| 					AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); | ||||
| 					await GetUserRoles(); | ||||
| 					user.Clear(); | ||||
| 					StateHasChanged(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|  | ||||
| @ -602,12 +602,12 @@ | ||||
| 			try | ||||
| 			{ | ||||
| 				var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); | ||||
| 				SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); | ||||
| 				SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); | ||||
| 				SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); | ||||
| 				SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); | ||||
| 				SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); | ||||
| 				SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); | ||||
| 				settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); | ||||
| 				await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); | ||||
| 				await logger.LogInformation("Site SMTP Settings Saved"); | ||||
|  | ||||
|  | ||||
| @ -305,7 +305,14 @@ else | ||||
| 	} | ||||
|  | ||||
| 	private string GetProfileValue(string SettingName, string DefaultValue) | ||||
| 		=> SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 	{ | ||||
| 		string value = SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 		if (value.Contains("]")) | ||||
| 		{ | ||||
| 			value = value.Substring(value.IndexOf("]") + 1); | ||||
| 		} | ||||
| 		return value; | ||||
| 	} | ||||
|  | ||||
| 	private async Task Save() | ||||
| 	{ | ||||
|  | ||||
| @ -104,7 +104,7 @@ | ||||
|     private Dictionary<string, string> settings; | ||||
|     private string category = string.Empty; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
| @ -121,8 +121,15 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private string GetProfileValue(string SettingName, string DefaultValue) | ||||
|         => SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 	private string GetProfileValue(string SettingName, string DefaultValue) | ||||
| 	{ | ||||
| 		string value = SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 		if (value.Contains("]")) | ||||
| 		{ | ||||
| 			value = value.Substring(value.IndexOf("]") + 1); | ||||
| 		} | ||||
| 		return value; | ||||
| 	} | ||||
|  | ||||
|     private async Task SaveUser() | ||||
|     { | ||||
|  | ||||
| @ -174,7 +174,7 @@ else | ||||
| 	private string deletedby; | ||||
| 	private DateTime? deletedon; | ||||
|  | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
| 	protected override async Task OnParametersSetAsync() | ||||
| 	{ | ||||
| @ -223,7 +223,14 @@ else | ||||
| 	} | ||||
|  | ||||
| 	private string GetProfileValue(string SettingName, string DefaultValue) | ||||
| 		=> SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 	{ | ||||
| 		string value = SettingService.GetSetting(settings, SettingName, DefaultValue); | ||||
| 		if (value.Contains("]")) | ||||
| 		{ | ||||
| 			value = value.Substring(value.IndexOf("]") + 1); | ||||
| 		} | ||||
| 		return value; | ||||
| 	} | ||||
|  | ||||
| 	private async Task SaveUser() | ||||
| 	{ | ||||
|  | ||||
| @ -20,7 +20,7 @@ else | ||||
| 			<div class="container"> | ||||
| 				<div class="row mb-1 align-items-center"> | ||||
| 					<div class="col-sm-4"> | ||||
| 						<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /> | ||||
| 						<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" /> | ||||
| 					</div> | ||||
| 					<div class="col-sm-4"> | ||||
| 						<input class="form-control" @bind="@_search" /> | ||||
| @ -41,21 +41,21 @@ else | ||||
| 				</Header> | ||||
| 				<Row> | ||||
| 					<td> | ||||
| 						<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" /> | ||||
| 						<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" /> | ||||
| 					</td> | ||||
| 					<td> | ||||
| 						<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" /> | ||||
| 						<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" /> | ||||
| 					</td> | ||||
| 					<td> | ||||
| 						<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" /> | ||||
| 						<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" /> | ||||
| 					</td> | ||||
| 					<td>@context.User.Username</td> | ||||
| 					<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td> | ||||
| 					<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td> | ||||
| 					<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td> | ||||
| 				</Row> | ||||
| 			</Pager> | ||||
| 	</TabPanel> | ||||
|         <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> | ||||
| 		</TabPanel> | ||||
|         <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin"> | ||||
| 			<div class="container"> | ||||
| 				<Section Name="User" Heading="User Settings" ResourceKey="UserSettings"> | ||||
| 					<div class="row mb-1 align-items-center"> | ||||
| @ -406,7 +406,7 @@ else | ||||
| 	private string _lifetime; | ||||
| 	private string _token; | ||||
|  | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
| 	public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; | ||||
|  | ||||
| 	protected override async Task OnInitializedAsync() | ||||
| 	{ | ||||
| @ -456,7 +456,8 @@ else | ||||
| 			_togglesecret = SharedLocalizer["ShowPassword"]; | ||||
| 			_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name); | ||||
| 			_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", ""); | ||||
| 			_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");		} | ||||
| 			_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private async Task LoadUsersAsync(bool load) | ||||
| @ -522,7 +523,7 @@ else | ||||
| 	private async Task UpdateUserSettingsAsync() | ||||
| 	{ | ||||
| 		Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); | ||||
| 		SettingService.SetSetting(settings, settingSearch, _search); | ||||
| 		settings = SettingService.SetSetting(settings, settingSearch, _search); | ||||
| 		await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); | ||||
| 	} | ||||
|  | ||||
|  | ||||
							
								
								
									
										19
									
								
								Oqtane.Client/Modules/Admin/Users/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Oqtane.Client/Modules/Admin/Users/ModuleInfo.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| using Oqtane.Documentation; | ||||
| using Oqtane.Models; | ||||
| using Oqtane.Shared; | ||||
|  | ||||
| namespace Oqtane.Modules.Admin.Users | ||||
| { | ||||
|     [PrivateApi("Mark this as private, since it's not very useful in the public docs")] | ||||
|     public class ModuleInfo : IModule | ||||
|     { | ||||
|         public ModuleDefinition ModuleDefinition => new ModuleDefinition | ||||
|         { | ||||
|             Name = "Users", | ||||
|             Description = "Manage Users", | ||||
|             Categories = "Admin", | ||||
|             Version = Constants.Version, | ||||
|             PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit},{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin},{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}" | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -63,7 +63,7 @@ else | ||||
|                 <td>@context.EffectiveDate</td> | ||||
|                 <td>@context.ExpiryDate</td> | ||||
|                 <td> | ||||
|                     <ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" /> | ||||
|                     <ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" /> | ||||
|                 </td> | ||||
|             </Row> | ||||
|         </Pager> | ||||
| @ -79,7 +79,7 @@ else | ||||
|     private string expirydate = string.Empty; | ||||
|     private List<UserRole> userroles; | ||||
|  | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; | ||||
|     public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; | ||||
|  | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|  | ||||
| @ -17,8 +17,8 @@ | ||||
|                         <th scope="col">@Localizer["Role"]</th> | ||||
|                         @foreach (PermissionString permission in _permissions) | ||||
|                         { | ||||
|                             <th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th> | ||||
|                         } | ||||
| 							<th style="text-align: center; width: 1px;">@((MarkupString)GetPermissionName(permission).Replace(" ", "<br />"))</th> | ||||
| 						} | ||||
|                     </tr> | ||||
|                     @foreach (Role role in _roles) | ||||
|                     { | ||||
| @ -28,7 +28,7 @@ | ||||
|                             { | ||||
|                                 var p = permission; | ||||
|                                 <td style="text-align: center;"> | ||||
|                                     <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" /> | ||||
| 									<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, role.Name)" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, role.Name))" /> | ||||
|                                 </td> | ||||
|                             } | ||||
|                         </tr> | ||||
| @ -66,7 +66,7 @@ | ||||
|                                 { | ||||
|                                     var p = permission; | ||||
|                                     <td style="text-align: center; width: 1px;"> | ||||
|                                         <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" /> | ||||
| 										<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, "")" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, userid))" /> | ||||
|                                     </td> | ||||
|                                 } | ||||
|                             </tr> | ||||
| @ -129,10 +129,25 @@ | ||||
|  | ||||
| 		_permissions = new List<PermissionString>(); | ||||
|  | ||||
| 		foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) | ||||
| 		foreach (string permissionname in _permissionnames.Split(',', StringSplitOptions.RemoveEmptyEntries)) | ||||
| 		{ | ||||
| 			// initialize with admin role | ||||
| 			_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin }); | ||||
| 			// permission names can be in the form of "EntityName:PermissionName:Roles" | ||||
| 			if (permissionname.Contains(":")) | ||||
| 			{ | ||||
| 				var segments = permissionname.Split(':'); | ||||
| 				if (segments.Length == 3) | ||||
| 				{ | ||||
| 					if (!segments[2].Contains(RoleNames.Admin)) | ||||
| 					{ | ||||
| 						segments[2] = RoleNames.Admin + ";" + segments[2]; // ensure admin access | ||||
| 					} | ||||
| 					_permissions.Add(new PermissionString { EntityName = segments[0], PermissionName = segments[1], Permissions = segments[2] }); | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				_permissions.Add(new PermissionString { EntityName = EntityName, PermissionName = permissionname, Permissions = RoleNames.Admin }); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (!string.IsNullOrEmpty(Permissions)) | ||||
| @ -140,14 +155,15 @@ | ||||
| 			// populate permissions | ||||
| 			foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) | ||||
| 			{ | ||||
| 				if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null) | ||||
| 				int index = _permissions.FindIndex(item => item.EntityName == permissionstring.EntityName && item.PermissionName == permissionstring.PermissionName); | ||||
| 				if (index != -1) | ||||
| 				{ | ||||
| 					_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions; | ||||
| 					_permissions[index].Permissions = permissionstring.Permissions; | ||||
| 				} | ||||
|  | ||||
| 				if (permissionstring.Permissions.Contains("[")) | ||||
| 				{ | ||||
| 					foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries)) | ||||
| 					foreach (string user in permissionstring.Permissions.Split('[', StringSplitOptions.RemoveEmptyEntries)) | ||||
| 					{ | ||||
| 						if (user.Contains("]")) | ||||
| 						{ | ||||
| @ -163,6 +179,16 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private string GetPermissionName(PermissionString permission) | ||||
| 	{ | ||||
| 		var permissionname = Localizer[permission.PermissionName].ToString(); | ||||
| 		if (!string.IsNullOrEmpty(EntityName)) | ||||
| 		{ | ||||
| 			permissionname += " " + Localizer[permission.EntityName].ToString(); | ||||
| 		} | ||||
| 		return permissionname; | ||||
| 	} | ||||
|  | ||||
| 	private bool? GetPermissionValue(string permissions, string securityKey) | ||||
| 	{ | ||||
| 		if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";")) | ||||
| @ -182,8 +208,24 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private bool GetPermissionDisabled(string roleName) | ||||
| 		=> (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false; | ||||
| 	private bool GetPermissionDisabled(string entityName, string permissionName, string roleName) | ||||
| 	{ | ||||
| 		if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (entityName != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) | ||||
| 			{ | ||||
| 				return true; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private async Task<Dictionary<string, string>> GetUsers(string filter) | ||||
| 	{ | ||||
| @ -209,14 +251,15 @@ | ||||
| 		_user.Clear(); | ||||
| 	} | ||||
|  | ||||
| 	private void PermissionChanged(bool? value, string permissionName, string securityId) | ||||
| 	private void PermissionChanged(bool? value, string entityName, string permissionName, string securityId) | ||||
| 	{ | ||||
| 		var selected = value; | ||||
| 		var permission = _permissions.Find(item => item.PermissionName == permissionName); | ||||
| 		if (permission != null) | ||||
| 		int index = _permissions.FindIndex(item => item.EntityName == entityName && item.PermissionName == permissionName); | ||||
| 		if (index != -1) | ||||
| 		{ | ||||
| 			var ids = permission.Permissions.Split(';').ToList(); | ||||
| 			var permission = _permissions[index]; | ||||
|  | ||||
| 			var ids = permission.Permissions.Split(';').ToList(); | ||||
| 			ids.Remove(securityId); // remove grant permission | ||||
| 			ids.Remove("!" + securityId); // remove deny permission | ||||
|  | ||||
| @ -232,7 +275,7 @@ | ||||
| 					break; // permission not specified | ||||
| 			} | ||||
|  | ||||
| 			_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray()); | ||||
| 			_permissions[index].Permissions = string.Join(";", ids.ToArray()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -245,9 +288,9 @@ | ||||
| 	private void ValidatePermissions() | ||||
| 	{ | ||||
| 		PermissionString permission; | ||||
| 		for (int i = 0; i < _permissions.Count; i++) | ||||
| 		for (int index = 0; index < _permissions.Count; index++) | ||||
| 		{ | ||||
| 			permission = _permissions[i]; | ||||
| 			permission = _permissions[index]; | ||||
| 			List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); | ||||
| 			ids.Remove("!" + RoleNames.Everyone); // remove deny all users | ||||
| 			ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated | ||||
| @ -263,7 +306,7 @@ | ||||
| 				} | ||||
| 			} | ||||
| 			permission.Permissions = string.Join(";", ids.ToArray()); | ||||
|             _permissions[i] = permission; | ||||
|             _permissions[index] = permission; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -315,15 +315,10 @@ namespace Oqtane.Modules | ||||
|         { | ||||
|             int pageId = ModuleState.PageId; | ||||
|             int moduleId = ModuleState.ModuleId; | ||||
|             int? userId = null; | ||||
|             if (PageState.User != null) | ||||
|             { | ||||
|                 userId = PageState.User.UserId; | ||||
|             } | ||||
|             string category = GetType().AssemblyQualifiedName; | ||||
|             string feature = Utilities.GetTypeNameLastSegment(category, 1); | ||||
|  | ||||
|             await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args); | ||||
|             await LoggingService.Log(alias, pageId, moduleId, PageState.User?.UserId, category, feature, function, level, exception, message, args); | ||||
|         } | ||||
|  | ||||
|         public class Logger | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
| @ -121,7 +121,7 @@ | ||||
|     <value>User: </value> | ||||
|   </data> | ||||
|   <data name="User.Select" xml:space="preserve"> | ||||
|     <value>Select User</value> | ||||
|     <value>Enter User's Name</value> | ||||
|   </data> | ||||
|   <data name="Users" xml:space="preserve"> | ||||
|     <value>Users</value> | ||||
| @ -129,9 +129,6 @@ | ||||
|   <data name="Error.User.Load" xml:space="preserve"> | ||||
|     <value>Error Loading Users</value> | ||||
|   </data> | ||||
|   <data name="Error.User.LoadRole" xml:space="preserve"> | ||||
|     <value>Error Loading User Roles</value> | ||||
|   </data> | ||||
|   <data name="Success.User.AssignedRole" xml:space="preserve"> | ||||
|     <value>User Assigned To Role</value> | ||||
|   </data> | ||||
| @ -151,7 +148,7 @@ | ||||
|     <value>The role you are assigning users to</value> | ||||
|   </data> | ||||
|   <data name="User.HelpText" xml:space="preserve"> | ||||
|     <value>Select a user</value> | ||||
|     <value>Enter the name of a user</value> | ||||
|   </data> | ||||
|   <data name="EffectiveDate.HelpText" xml:space="preserve"> | ||||
|     <value>The date that this role assignment is active</value> | ||||
|  | ||||
| @ -1,32 +0,0 @@ | ||||
| using System.Net.Http; | ||||
| using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | ||||
| using Oqtane.Documentation; | ||||
| using Oqtane.Shared; | ||||
| using Oqtane.Models; | ||||
|  | ||||
| namespace Oqtane.Services | ||||
| { | ||||
|     [PrivateApi("Don't show in the documentation, as everything should use the Interface")] | ||||
|     public class ApiService : ServiceBase, IApiService | ||||
|     { | ||||
|         public ApiService(HttpClient http, SiteState siteState) : base(http, siteState) { } | ||||
|  | ||||
|         private string Apiurl => CreateApiUrl("Api"); | ||||
|  | ||||
|         public async Task<List<Api>> GetApisAsync(int siteId) | ||||
|         { | ||||
|             return await GetJsonAsync<List<Api>>($"{Apiurl}?siteid={siteId}"); | ||||
|         } | ||||
|  | ||||
|         public async Task<Api> GetApiAsync(int siteId, string entityName) | ||||
|         { | ||||
|             return await GetJsonAsync<Api>($"{Apiurl}/{siteId}/{entityName}"); | ||||
|         } | ||||
|  | ||||
|         public async Task UpdateApiAsync(Api api) | ||||
|         { | ||||
|             await PostJsonAsync(Apiurl, api); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Oqtane.Models; | ||||
|  | ||||
| namespace Oqtane.Services | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Service to retrieve and update API information. | ||||
|     /// </summary> | ||||
|     public interface IApiService | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// returns a list of APIs | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         Task<List<Api>> GetApisAsync(int siteId); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// returns a specific API | ||||
|         /// </summary> | ||||
|         /// <returns></returns> | ||||
|         Task<Api> GetApiAsync(int siteId, string entityName); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Updates an API | ||||
|         /// </summary> | ||||
|         /// <param name="api"></param> | ||||
|         /// <returns></returns> | ||||
|         Task UpdateApiAsync(Api api); | ||||
|     } | ||||
| } | ||||
| @ -33,7 +33,7 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) | ||||
| @if (_canViewAdminDashboard || UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) | ||||
| { | ||||
|     <button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel"> | ||||
|         <span class="oi oi-cog"></span> | ||||
| @ -46,16 +46,17 @@ | ||||
| 		</div> | ||||
| 		<div class="@BodyClass"> | ||||
| 			<div class="container-fluid"> | ||||
| 				@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) | ||||
| 				@if (_canViewAdminDashboard) | ||||
| 				{ | ||||
| 					<div class="row d-flex"> | ||||
| 						<div class="col"> | ||||
| 							<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button> | ||||
| 						</div> | ||||
| 					</div> | ||||
|  | ||||
| 					<hr class="app-rule" /> | ||||
|  | ||||
| 				} | ||||
| 				@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) | ||||
| 				{ | ||||
| 					<div class="row"> | ||||
| 						<div class="col text-center"> | ||||
| 							<label class="control-label">@Localizer["Page.Manage"] </label> | ||||
| @ -80,144 +81,149 @@ | ||||
| 							} | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				} | ||||
| 					<hr class="app-rule" /> | ||||
|  | ||||
| 				@if (_deleteConfirmation) | ||||
| 				{ | ||||
| 					<div class="app-admin-modal"> | ||||
| 						<div class="modal" tabindex="-1" role="dialog"> | ||||
| 							<div class="modal-dialog"> | ||||
| 								<div class="modal-content"> | ||||
| 									<div class="modal-header"> | ||||
| 										<h5 class="modal-title">@Localizer["Page.Delete"]</h5> | ||||
| 										<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button> | ||||
| 									</div> | ||||
| 									<div class="modal-body"> | ||||
| 										<p>Are You Sure You Want To Delete This Page?</p> | ||||
| 									</div> | ||||
| 									<div class="modal-footer"> | ||||
| 										<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button> | ||||
| 										<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button> | ||||
| 					@if (_deleteConfirmation) | ||||
| 					{ | ||||
| 						<div class="app-admin-modal"> | ||||
| 							<div class="modal" tabindex="-1" role="dialog"> | ||||
| 								<div class="modal-dialog"> | ||||
| 									<div class="modal-content"> | ||||
| 										<div class="modal-header"> | ||||
| 											<h5 class="modal-title">@Localizer["Page.Delete"]</h5> | ||||
| 											<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button> | ||||
| 										</div> | ||||
| 										<div class="modal-body"> | ||||
| 											<p>Are You Sure You Want To Delete This Page?</p> | ||||
| 										</div> | ||||
| 										<div class="modal-footer"> | ||||
| 											<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button> | ||||
| 											<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button> | ||||
| 										</div> | ||||
| 									</div> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					} | ||||
| 				} | ||||
| 				<hr class="app-rule" /> | ||||
| 				<div class="row"> | ||||
| 					<div class="col text-center"> | ||||
| 						<label for="Module" class="control-label">@Localizer["Module.Manage"] </label> | ||||
| 						<select class="form-select" @bind="@ModuleType"> | ||||
| 							<option value="new">@Localizer["Module.AddNew"]</option> | ||||
| 							<option value="existing">@Localizer["Module.AddExisting"]</option> | ||||
| 						</select> | ||||
| 						@if (ModuleType == "new") | ||||
| 						{ | ||||
| 							@if (_moduleDefinitions != null) | ||||
| 							{ | ||||
| 								<select class="form-select" @onchange="(e => CategoryChanged(e))"> | ||||
| 									@foreach (var category in _categories) | ||||
| 									{ | ||||
| 										if (category == Category) | ||||
| 										{ | ||||
| 											<option value="@category" selected>@category @Localizer["Modules"]</option> | ||||
| 										} | ||||
| 										else | ||||
| 										{ | ||||
| 											<option value="@category">@category @Localizer["Modules"]</option> | ||||
| 										} | ||||
| 									} | ||||
| 								</select> | ||||
| 								<select class="form-select" @onchange="(e => ModuleChanged(e))"> | ||||
| 									@if (ModuleDefinitionName == "-") | ||||
| 									{ | ||||
| 										<option value="-" selected><@Localizer["Module.Select"]></option> | ||||
| 									} | ||||
| 									else | ||||
| 									{ | ||||
| 										<option value="-"><@Localizer["Module.Select"]></option> | ||||
| 									} | ||||
| 									@foreach (var moduledefinition in _moduleDefinitions) | ||||
| 									{ | ||||
| 										if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions)) | ||||
| 										{ | ||||
| 											if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString())) | ||||
| 											{ | ||||
| 												<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option> | ||||
| 											} | ||||
| 										} | ||||
| 									} | ||||
| 								</select> | ||||
| 							} | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							<select class="form-select" @onchange="(e => PageChanged(e))"> | ||||
| 								<option value="-"><@Localizer["Page.Select"]></option> | ||||
| 								@foreach (Page p in _pages) | ||||
| 								{ | ||||
| 									<option value="@p.PageId">@p.Name</option> | ||||
| 								} | ||||
| 							</select> | ||||
| 							<select class="form-select" @bind="@ModuleId"> | ||||
| 								<option value="-"><@Localizer["Module.Select"]></option> | ||||
| 								@foreach (Module module in _modules) | ||||
| 								{ | ||||
| 									<option value="@module.ModuleId">@module.Title</option> | ||||
| 								} | ||||
| 							</select> | ||||
| 						} | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="row"> | ||||
| 					<div class="col text-center"> | ||||
| 						<label for="Title" class="control-label">@Localizer["Title"] </label> | ||||
| 						<input type="text" name="Title" class="form-control" @bind="@Title" /> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				@if (_pane.Length > 1) | ||||
|  | ||||
| 				@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) | ||||
| 				{ | ||||
| 					<div class="row"> | ||||
| 						<div class="col text-center"> | ||||
| 							<label for="Pane" class="control-label">@Localizer["Pane"] </label> | ||||
| 							<select class="form-select" @bind="@Pane"> | ||||
| 								@foreach (string pane in PageState.Page.Panes) | ||||
| 							<label for="Module" class="control-label">@Localizer["Module.Manage"] </label> | ||||
| 							<select class="form-select" @bind="@ModuleType"> | ||||
| 								<option value="new">@Localizer["Module.AddNew"]</option> | ||||
| 								<option value="existing">@Localizer["Module.AddExisting"]</option> | ||||
| 							</select> | ||||
| 							@if (ModuleType == "new") | ||||
| 							{ | ||||
| 								@if (_moduleDefinitions != null) | ||||
| 								{ | ||||
| 									<option value="@pane">@pane Pane</option> | ||||
| 									<select class="form-select" @onchange="(e => CategoryChanged(e))"> | ||||
| 										@foreach (var category in _categories) | ||||
| 										{ | ||||
| 											if (category == Category) | ||||
| 											{ | ||||
| 												<option value="@category" selected>@category @Localizer["Modules"]</option> | ||||
| 											} | ||||
| 											else | ||||
| 											{ | ||||
| 												<option value="@category">@category @Localizer["Modules"]</option> | ||||
| 											} | ||||
| 										} | ||||
| 									</select> | ||||
| 									<select class="form-select" @onchange="(e => ModuleChanged(e))"> | ||||
| 										@if (ModuleDefinitionName == "-") | ||||
| 										{ | ||||
| 											<option value="-" selected><@Localizer["Module.Select"]></option> | ||||
| 										} | ||||
| 										else | ||||
| 										{ | ||||
| 											<option value="-"><@Localizer["Module.Select"]></option> | ||||
| 										} | ||||
| 										@foreach (var moduledefinition in _moduleDefinitions) | ||||
| 										{ | ||||
| 											if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions)) | ||||
| 											{ | ||||
| 												if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString())) | ||||
| 												{ | ||||
| 													<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option> | ||||
| 												} | ||||
| 											} | ||||
| 										} | ||||
| 									</select> | ||||
| 								} | ||||
| 							} | ||||
| 							else | ||||
| 							{ | ||||
| 								<select class="form-select" @onchange="(e => PageChanged(e))"> | ||||
| 									<option value="-"><@Localizer["Page.Select"]></option> | ||||
| 									@foreach (Page p in _pages) | ||||
| 									{ | ||||
| 										<option value="@p.PageId">@p.Name</option> | ||||
| 									} | ||||
| 								</select> | ||||
| 								<select class="form-select" @bind="@ModuleId"> | ||||
| 									<option value="-"><@Localizer["Module.Select"]></option> | ||||
| 									@foreach (Module module in _modules) | ||||
| 									{ | ||||
| 										<option value="@module.ModuleId">@module.Title</option> | ||||
| 									} | ||||
| 								</select> | ||||
| 							} | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div class="row"> | ||||
| 						<div class="col text-center"> | ||||
| 							<label for="Title" class="control-label">@Localizer["Title"] </label> | ||||
| 							<input type="text" name="Title" class="form-control" @bind="@Title" /> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					@if (_pane.Length > 1) | ||||
| 					{ | ||||
| 						<div class="row"> | ||||
| 							<div class="col text-center"> | ||||
| 								<label for="Pane" class="control-label">@Localizer["Pane"] </label> | ||||
| 								<select class="form-select" @bind="@Pane"> | ||||
| 									@foreach (string pane in PageState.Page.Panes) | ||||
| 									{ | ||||
| 										<option value="@pane">@pane Pane</option> | ||||
| 									} | ||||
| 								</select> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					} | ||||
| 					<div class="row"> | ||||
| 						<div class="col text-center"> | ||||
| 							<label for="Container" class="control-label">@Localizer["Container"] </label> | ||||
| 							<select class="form-select" @bind="@ContainerType"> | ||||
| 								@foreach (var container in _containers) | ||||
| 								{ | ||||
| 									<option value="@container.TypeName">@container.Name</option> | ||||
| 								} | ||||
| 							</select> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div class="row"> | ||||
| 						<div class="col text-center"> | ||||
| 							<label for="visibility" class="control-label">@Localizer["Visibility"]</label> | ||||
| 							<select class="form-select" @bind="@Visibility"> | ||||
| 								<option value="view">@Localizer["VisibilityView"]</option> | ||||
| 								<option value="edit">@Localizer["VisibilityEdit"]</option> | ||||
| 							</select> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button> | ||||
| 					@((MarkupString)Message) | ||||
| 				} | ||||
| 				<div class="row"> | ||||
| 					<div class="col text-center"> | ||||
| 						<label for="Container" class="control-label">@Localizer["Container"] </label> | ||||
| 						<select class="form-select" @bind="@ContainerType"> | ||||
| 							@foreach (var container in _containers) | ||||
| 							{ | ||||
| 								<option value="@container.TypeName">@container.Name</option> | ||||
| 							} | ||||
| 						</select> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div class="row"> | ||||
| 					<div class="col text-center"> | ||||
| 						<label for="visibility" class="control-label">@Localizer["Visibility"]</label> | ||||
| 						<select class="form-select" @bind="@Visibility"> | ||||
| 							<option value="view">@Localizer["VisibilityView"]</option> | ||||
| 							<option value="edit">@Localizer["VisibilityEdit"]</option> | ||||
| 						</select> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button> | ||||
| 				@((MarkupString) Message) | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| } | ||||
|  | ||||
| @code{ | ||||
| 	private bool _canViewAdminDashboard = false; | ||||
| 	private bool _showEditMode = false; | ||||
| 	private bool _deleteConfirmation = false; | ||||
| 	private List<string> _categories = new List<string>(); | ||||
| @ -286,6 +292,7 @@ | ||||
|  | ||||
| 	protected override async Task OnParametersSetAsync() | ||||
| 	{ | ||||
| 		_canViewAdminDashboard = CanViewAdminDashboard(); | ||||
| 		_showEditMode = false; | ||||
| 		if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) | ||||
| 		{ | ||||
| @ -321,6 +328,22 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private bool CanViewAdminDashboard() | ||||
| 	{ | ||||
| 		var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); | ||||
| 		if (admin != null) | ||||
| 		{ | ||||
| 			foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId)) | ||||
| 			{ | ||||
| 				if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.Permissions)) | ||||
| 				{ | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	private void CategoryChanged(ChangeEventArgs e) | ||||
| 	{ | ||||
| 		Category = (string)e.Value; | ||||
| @ -465,12 +488,10 @@ | ||||
| 			case "Admin": | ||||
| 			// get admin dashboard moduleid | ||||
| 				module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); | ||||
|  | ||||
| 				if (module != null) | ||||
| 				{ | ||||
| 					NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); | ||||
| 				} | ||||
|  | ||||
| 				break; | ||||
| 			case "Add": | ||||
| 			case "Edit": | ||||
| @ -551,19 +572,19 @@ | ||||
| 			{ | ||||
| 				page.IsDeleted = true; | ||||
| 				await PageService.UpdatePageAsync(page); | ||||
| 				await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); | ||||
| 				await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); | ||||
| 				NavigationManager.NavigateTo(NavigateUrl("")); | ||||
| 			} | ||||
| 			else // personalized page | ||||
| 			{ | ||||
| 				await PageService.DeletePageAsync(page.PageId); | ||||
| 				await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); | ||||
| 				await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); | ||||
| 				NavigationManager.NavigateTo(NavigateUrl()); | ||||
| 			} | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message); | ||||
| 			await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -596,8 +617,8 @@ | ||||
|     private async Task UpdateSettingsAsync() | ||||
|     { | ||||
|         Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); | ||||
|         SettingService.SetSetting(settings, settingCategory, _category); | ||||
|         SettingService.SetSetting(settings, settingPane, _pane); | ||||
| 		settings = SettingService.SetSetting(settings, settingCategory, _category); | ||||
|         settings = SettingService.SetSetting(settings, settingPane, _pane); | ||||
|         await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -32,7 +32,7 @@ namespace Oqtane.Themes.Controls | ||||
|  | ||||
|         protected async Task LogoutUser() | ||||
|         { | ||||
|             await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username); | ||||
|             await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username); | ||||
|  | ||||
|             // check if anonymous user can access page | ||||
|             var url = PageState.Alias.Path + "/" + PageState.Page.Path; | ||||
|  | ||||
| @ -87,10 +87,9 @@ else | ||||
| 		// retrieve friendly localized error | ||||
| 		_error =  Localizer["Error.Module.Exception"]; | ||||
| 		// log error | ||||
| 		int? userId = (PageState.User != null) ? PageState.User.UserId : null; | ||||
| 		string category = GetType().AssemblyQualifiedName; | ||||
| 		string feature = Utilities.GetTypeNameLastSegment(category, 1); | ||||
| 		await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, userId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message); | ||||
| 		await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, PageState.User?.UserId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message); | ||||
| 		await base.OnErrorAsync(exception); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| @ -1,172 +0,0 @@ | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using System.Collections.Generic; | ||||
| using Oqtane.Shared; | ||||
| using Oqtane.Models; | ||||
| using Oqtane.Infrastructure; | ||||
| using Oqtane.Enums; | ||||
| using System.Net; | ||||
| using Oqtane.Repository; | ||||
| using Oqtane.Extensions; | ||||
| using System.Reflection; | ||||
| using System; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace Oqtane.Controllers | ||||
| { | ||||
|     [Route(ControllerRoutes.ApiRoute)] | ||||
|     public class ApiController : Controller | ||||
|     { | ||||
|         private readonly IPermissionRepository _permissions; | ||||
|         private readonly IRoleRepository _roles; | ||||
|         private readonly ILogManager _logger; | ||||
|         private readonly Alias _alias; | ||||
|  | ||||
|         public ApiController(IPermissionRepository permissions, IRoleRepository roles, ILogManager logger, ITenantManager tenantManager) | ||||
|         { | ||||
|             _permissions = permissions; | ||||
|             _roles = roles; | ||||
|             _logger = logger; | ||||
|             _alias = tenantManager.GetAlias(); | ||||
|         } | ||||
|  | ||||
|         // GET: api/<controller>?siteid=x | ||||
|         [HttpGet] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         public List<Api> Get(string siteid) | ||||
|         { | ||||
|             int SiteId; | ||||
|             if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) | ||||
|             { | ||||
|                 var apis = new List<Api>(); | ||||
|  | ||||
|                 var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); | ||||
|                 foreach (var assembly in assemblies) | ||||
|                 { | ||||
|                     // iterate controllers | ||||
|                     foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type))) | ||||
|                     { | ||||
|                         // iterate controller methods with authorize attribute | ||||
|                         var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) | ||||
|                             .Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any()); | ||||
|                         foreach(var action in actions) | ||||
|                         { | ||||
|                             // get policy | ||||
|                             var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy; | ||||
|                             if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId)) | ||||
|                             { | ||||
|                                 // parse policy | ||||
|                                 var segments = policy.Split(':'); | ||||
|                                 if (!apis.Any(item => item.EntityName == segments[0])) | ||||
|                                 { | ||||
|                                     apis.Add(new Api { SiteId = SiteId, EntityName = segments[0], Permissions = segments[1] }); | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     // concatenate permissions | ||||
|                                     var permissions = apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions; | ||||
|                                     if (!permissions.Split(',').Contains(segments[1])) | ||||
|                                     { | ||||
|                                         apis.SingleOrDefault(item => item.EntityName == segments[0]).Permissions += "," + segments[1]; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return apis; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId}", siteid); | ||||
|                 HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // GET: api/<controller>/1/user | ||||
|         [HttpGet("{siteid}/{entityname}")] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         public Api Get(int siteid, string entityname) | ||||
|         { | ||||
|             if (siteid == _alias.SiteId) | ||||
|             { | ||||
|                 var permissions = _permissions.GetPermissions(siteid, entityname); | ||||
|                 if (permissions == null || permissions.ToList().Count == 0) | ||||
|                 { | ||||
|                     permissions = GetPermissions(siteid, entityname); | ||||
|                 } | ||||
|                 return new Api { SiteId = siteid, EntityName = entityname, Permissions = permissions.EncodePermissions() }; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Get Attempt {SiteId} {EntityName}", siteid, entityname); | ||||
|                 HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // POST: api/<controller> | ||||
|         [HttpPost] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         public void Post([FromBody] Api api) | ||||
|         { | ||||
|             if (ModelState.IsValid && api.SiteId == _alias.SiteId) | ||||
|             { | ||||
|                 _permissions.UpdatePermissions(api.SiteId, api.EntityName, -1, api.Permissions); | ||||
|                 _logger.Log(LogLevel.Information, this, LogFunction.Update, "Api Updated {Api}", api); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Api Post Attempt {Api}", api); | ||||
|                 HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private List<Permission> GetPermissions(int siteid, string entityname) | ||||
|         { | ||||
|             var permissions = new List<Permission>(); | ||||
|  | ||||
|             var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); | ||||
|             foreach (var assembly in assemblies) | ||||
|             { | ||||
|                 // iterate controllers | ||||
|                 foreach (var type in assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type))) | ||||
|                 { | ||||
|                     // iterate controller methods with authorize attribute | ||||
|                     var actions = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) | ||||
|                         .Where(m => m.GetCustomAttributes<AuthorizeAttribute>().Any()); | ||||
|                     foreach (var action in actions) | ||||
|                     { | ||||
|                         // get policy | ||||
|                         var policy = action.GetCustomAttribute<AuthorizeAttribute>().Policy; | ||||
|                         if (!string.IsNullOrEmpty(policy) && policy.Contains(":") && !policy.Contains(Constants.RequireEntityId)) | ||||
|                         { | ||||
|                             // parse policy | ||||
|                             var segments = policy.Split(':'); | ||||
|                             // entity match | ||||
|                             if (segments[0] == entityname && segments.Length > 2) | ||||
|                             { | ||||
|                                 var roles = _roles.GetRoles(siteid); | ||||
|                                 foreach (var rolename in (segments[2]).Split(',')) | ||||
|                                 { | ||||
|                                     var role = roles.FirstOrDefault(item => item.Name == rolename); | ||||
|                                     if (role != null) | ||||
|                                     { | ||||
|                                         if (!permissions.Any(item => item.EntityName == entityname && item.PermissionName == segments[1] && item.RoleId == role.RoleId)) | ||||
|                                         { | ||||
|                                             permissions.Add(new Permission { SiteId = siteid, EntityName = entityname, EntityId = -1, PermissionName = segments[1], RoleId = role.RoleId, Role = role, UserId = null, IsAuthorized = true }); | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return permissions; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -47,7 +47,6 @@ namespace Oqtane.Controllers | ||||
|             int SiteId; | ||||
|             if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) | ||||
|             { | ||||
|                 List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(SiteId).ToList(); | ||||
|                 List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList(); | ||||
|  | ||||
|                 foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId)) | ||||
| @ -75,7 +74,6 @@ namespace Oqtane.Controllers | ||||
|                         module.Order = pagemodule.Order; | ||||
|                         module.ContainerType = pagemodule.ContainerType; | ||||
|  | ||||
|                         module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName); | ||||
|                         module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) | ||||
|                             .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions)) | ||||
|                             .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); | ||||
|  | ||||
| @ -281,7 +281,7 @@ namespace Oqtane.Controllers | ||||
|                 // synchronize module permissions | ||||
|                 if (added.Count > 0 || removed.Count > 0) | ||||
|                 { | ||||
|                     foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList()) | ||||
|                     foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList()) | ||||
|                     { | ||||
|                         var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); | ||||
|                         // permissions added | ||||
|  | ||||
| @ -120,7 +120,8 @@ namespace Oqtane.Controllers | ||||
|             if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit)) | ||||
|             { | ||||
|                 int order = 1; | ||||
|                 List<PageModule> pagemodules = _pageModules.GetPageModules(pageid, pane).OrderBy(item => item.Order).ToList(); | ||||
|                 List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId) | ||||
|                     .Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList(); | ||||
|                 foreach (PageModule pagemodule in pagemodules) | ||||
|                 { | ||||
|                     if (pagemodule.Order != order) | ||||
|  | ||||
| @ -28,7 +28,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // GET: api/<controller>?siteid=x | ||||
|         [HttpGet] | ||||
|         [Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")] | ||||
|         [Authorize(Roles = RoleNames.Registered)] | ||||
|         public IEnumerable<Profile> Get(string siteid) | ||||
|         { | ||||
|             int SiteId; | ||||
| @ -46,7 +46,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // GET api/<controller>/5 | ||||
|         [HttpGet("{id}")] | ||||
|         [Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")] | ||||
|         [Authorize(Roles = RoleNames.Registered)] | ||||
|         public Profile Get(int id) | ||||
|         { | ||||
|             var profile = _profiles.GetProfile(id); | ||||
|  | ||||
| @ -28,7 +28,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // GET: api/<controller>?siteid=x&global=true/false | ||||
|         [HttpGet] | ||||
|         [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] | ||||
|         [Authorize(Roles = RoleNames.Registered)] | ||||
|         public IEnumerable<Role> Get(string siteid, string global) | ||||
|         { | ||||
|             int SiteId; | ||||
| @ -50,7 +50,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // GET api/<controller>/5 | ||||
|         [HttpGet("{id}")] | ||||
|         [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Read}:{RoleNames.Registered}")] | ||||
|         [Authorize(Roles = RoleNames.Registered)] | ||||
|         public Role Get(int id) | ||||
|         { | ||||
|             var role = _roles.GetRole(id); | ||||
|  | ||||
| @ -212,7 +212,7 @@ namespace Oqtane.Controllers | ||||
|                     authorized = true; | ||||
|                     if (permissionName == PermissionNames.Edit) | ||||
|                     { | ||||
|                         authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); | ||||
|                         authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); | ||||
|                     } | ||||
|                     break; | ||||
|                 case EntityNames.Visitor: | ||||
| @ -226,14 +226,11 @@ namespace Oqtane.Controllers | ||||
|                     } | ||||
|                     break; | ||||
|                 default: // custom entity | ||||
|                     authorized = true; | ||||
|                     if (permissionName == PermissionNames.Edit) | ||||
|                     { | ||||
|                         authorized = User.IsInRole(RoleNames.Admin) || _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         authorized = true; | ||||
|                     } | ||||
|                     break; | ||||
|             } | ||||
|             return authorized; | ||||
|  | ||||
| @ -29,23 +29,25 @@ namespace Oqtane.Controllers | ||||
|         private readonly ITenantManager _tenantManager; | ||||
|         private readonly INotificationRepository _notifications; | ||||
|         private readonly IFolderRepository _folders; | ||||
|         private readonly ISyncManager _syncManager; | ||||
|         private readonly ISiteRepository _sites; | ||||
|         private readonly IUserPermissions _userPermissions; | ||||
|         private readonly IJwtManager _jwtManager; | ||||
|         private readonly ISyncManager _syncManager; | ||||
|         private readonly ILogManager _logger; | ||||
|  | ||||
|         public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger) | ||||
|         public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger) | ||||
|         { | ||||
|             _users = users; | ||||
|             _userRoles = userRoles; | ||||
|             _identityUserManager = identityUserManager; | ||||
|             _identitySignInManager = identitySignInManager; | ||||
|             _tenantManager = tenantManager; | ||||
|             _folders = folders; | ||||
|             _notifications = notifications; | ||||
|             _syncManager = syncManager; | ||||
|             _folders = folders; | ||||
|             _sites = sites; | ||||
|             _userPermissions = userPermissions; | ||||
|             _jwtManager = jwtManager; | ||||
|             _syncManager = syncManager; | ||||
|             _logger = logger; | ||||
|         } | ||||
|  | ||||
| @ -105,7 +107,7 @@ namespace Oqtane.Controllers | ||||
|                 user.TwoFactorCode = ""; | ||||
|                 user.TwoFactorExpiry = null; | ||||
|  | ||||
|                 if (!User.IsInRole(RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower()) | ||||
|                 if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower()) | ||||
|                 { | ||||
|                     user.Email = ""; | ||||
|                     user.PhotoFileId = null; | ||||
| @ -148,8 +150,8 @@ namespace Oqtane.Controllers | ||||
|             User newUser = null; | ||||
|  | ||||
|             bool verified; | ||||
|             bool allowregistration; | ||||
|             if (User.IsInRole(RoleNames.Admin)) | ||||
|             bool allowregistration;             | ||||
|             if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin)) | ||||
|             { | ||||
|                 verified = true; | ||||
|                 allowregistration = true; | ||||
| @ -241,7 +243,8 @@ namespace Oqtane.Controllers | ||||
|         [Authorize] | ||||
|         public async Task<User> Put(int id, [FromBody] User user) | ||||
|         { | ||||
|             if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username)) | ||||
|             if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null | ||||
|                 && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) | ||||
|             { | ||||
|                 IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); | ||||
|                 if (identityuser != null) | ||||
| @ -287,7 +290,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // DELETE api/<controller>/5?siteid=x | ||||
|         [HttpDelete("{id}")] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         [Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")] | ||||
|         public async Task Delete(int id, string siteid) | ||||
|         { | ||||
|             int SiteId; | ||||
|  | ||||
| @ -10,6 +10,7 @@ using System.Linq; | ||||
| using System.Net; | ||||
| using Oqtane.Security; | ||||
| using System; | ||||
| using Oqtane.Modules.Admin.Roles; | ||||
|  | ||||
| namespace Oqtane.Controllers | ||||
| { | ||||
| @ -93,7 +94,7 @@ namespace Oqtane.Controllers | ||||
|                 userrole.User.TwoFactorCode = ""; | ||||
|                 userrole.User.TwoFactorExpiry = null; | ||||
|  | ||||
|                 if (!User.IsInRole(RoleNames.Admin) && userid != userrole.User.UserId) | ||||
|                 if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId) | ||||
|                 { | ||||
|                     userrole.User.Email = ""; | ||||
|                     userrole.User.PhotoFileId = null; | ||||
| @ -115,7 +116,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // POST api/<controller> | ||||
|         [HttpPost] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")] | ||||
|         public UserRole Post([FromBody] UserRole userRole) | ||||
|         { | ||||
|             var role = _roles.GetRole(userRole.RoleId); | ||||
| @ -138,7 +139,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // PUT api/<controller>/5 | ||||
|         [HttpPut("{id}")] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")] | ||||
|         public UserRole Put(int id, [FromBody] UserRole userRole) | ||||
|         { | ||||
|             var role = _roles.GetRole(userRole.RoleId); | ||||
| @ -160,7 +161,7 @@ namespace Oqtane.Controllers | ||||
|  | ||||
|         // DELETE api/<controller>/5 | ||||
|         [HttpDelete("{id}")] | ||||
|         [Authorize(Roles = RoleNames.Admin)] | ||||
|         [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")] | ||||
|         public void Delete(int id) | ||||
|         { | ||||
|             UserRole userrole = _userRoles.GetUserRole(id); | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| @ -11,20 +11,22 @@ namespace Oqtane.Extensions | ||||
|         public static string EncodePermissions(this IEnumerable<Permission> permissionList) | ||||
|         { | ||||
|             List<PermissionString> permissionstrings = new List<PermissionString>(); | ||||
|             string entityname = ""; | ||||
|             string permissionname = ""; | ||||
|             string permissions = ""; | ||||
|             StringBuilder permissionsbuilder = new StringBuilder(); | ||||
|             string securityid = ""; | ||||
|             foreach (Permission permission in permissionList.OrderBy(item => item.PermissionName)) | ||||
|             foreach (Permission permission in permissionList.OrderBy(item => item.EntityName).ThenBy(item => item.PermissionName)) | ||||
|             { | ||||
|                 // permission collections are grouped by permissionname | ||||
|                 if (permissionname != permission.PermissionName) | ||||
|                 // permission collections are grouped by entityname and permissionname | ||||
|                 if (entityname != permission.EntityName || permissionname != permission.PermissionName) | ||||
|                 { | ||||
|                     permissions = permissionsbuilder.ToString(); | ||||
|                     if (permissions != "") | ||||
|                     { | ||||
|                         permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); | ||||
|                         permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); | ||||
|                     } | ||||
|                     entityname = permission.EntityName; | ||||
|                     permissionname = permission.PermissionName; | ||||
|                     permissionsbuilder = new StringBuilder(); | ||||
|                 } | ||||
| @ -56,7 +58,7 @@ namespace Oqtane.Extensions | ||||
|             permissions = permissionsbuilder.ToString(); | ||||
|             if (permissions != "") | ||||
|             { | ||||
|                 permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); | ||||
|                 permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); | ||||
|             } | ||||
|             return JsonSerializer.Serialize(permissionstrings); | ||||
|         } | ||||
|  | ||||
| @ -308,43 +308,45 @@ namespace Oqtane.Infrastructure | ||||
|  | ||||
|         private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope) | ||||
|         { | ||||
|             var pageTemplates = new List<PageTemplate>(); | ||||
|  | ||||
|             pageTemplates.Add(new PageTemplate | ||||
|             try | ||||
|             { | ||||
|                 Name = "API Management", | ||||
|                 Parent = "Admin", | ||||
|                 Order = 35, | ||||
|                 Path = "admin/apis", | ||||
|                 Icon = Icons.CloudDownload,  | ||||
|                 IsNavigation = true, | ||||
|                 IsPersonalizable = false, | ||||
|                 PagePermissions = new List<Permission> | ||||
|                 var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>(); | ||||
|                 var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>(); | ||||
|                 var modules = scope.ServiceProvider.GetRequiredService<IModuleRepository>(); | ||||
|                 var permissions = scope.ServiceProvider.GetRequiredService<IPermissionRepository>(); | ||||
|                 var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>(); | ||||
|                 foreach (Site site in siteRepository.GetSites().ToList()) | ||||
|                 { | ||||
|                     new Permission(PermissionNames.View, RoleNames.Admin, true), | ||||
|                     new Permission(PermissionNames.Edit, RoleNames.Admin, true) | ||||
|                 }.EncodePermissions(), | ||||
|                 PageTemplateModules = new List<PageTemplateModule> | ||||
|                 { | ||||
|                     new PageTemplateModule | ||||
|                     int roleid = roles.GetRoles(site.SiteId).FirstOrDefault(item => item.Name == RoleNames.Registered).RoleId; | ||||
|  | ||||
|                     int pageid = pages.GetPages(site.SiteId).FirstOrDefault(item => item.Path == "admin").PageId; | ||||
|                     var permission = new Permission | ||||
|                     { | ||||
|                         ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default, | ||||
|                         ModulePermissions = new List<Permission> | ||||
|                         { | ||||
|                             new Permission(PermissionNames.View, RoleNames.Admin, true), | ||||
|                             new Permission(PermissionNames.Edit, RoleNames.Admin, true) | ||||
|                         }.EncodePermissions(), | ||||
|                         Content = "" | ||||
|                     } | ||||
|                         SiteId = site.SiteId, | ||||
|                         EntityName = EntityNames.Page, | ||||
|                         EntityId = pageid, | ||||
|                         PermissionName = PermissionNames.View, | ||||
|                         RoleId = roleid, | ||||
|                         IsAuthorized = true | ||||
|                     }; | ||||
|                     permissions.AddPermission(permission); | ||||
|  | ||||
|                     int moduleid = modules.GetModules(site.SiteId).FirstOrDefault(item => item.ModuleDefinitionName == "Oqtane.Modules.Admin.Dashboard, Oqtane.Client").ModuleId; | ||||
|                     permission = new Permission | ||||
|                     { | ||||
|                         SiteId = site.SiteId, | ||||
|                         EntityName = EntityNames.Module, | ||||
|                         EntityId = moduleid, | ||||
|                         PermissionName = PermissionNames.View, | ||||
|                         RoleId = roleid, | ||||
|                         IsAuthorized = true | ||||
|                     }; | ||||
|                     permissions.AddPermission(permission); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>(); | ||||
|  | ||||
|             var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>(); | ||||
|             foreach (Site site in sites.GetSites().ToList()) | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 sites.CreatePages(site, pageTemplates); | ||||
|                 Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -6,7 +6,6 @@ namespace Oqtane.Repository | ||||
|     public interface IPageModuleRepository | ||||
|     { | ||||
|         IEnumerable<PageModule> GetPageModules(int siteId); | ||||
|         IEnumerable<PageModule> GetPageModules(int pageId, string pane); | ||||
|         PageModule AddPageModule(PageModule pageModule); | ||||
|         PageModule UpdatePageModule(PageModule pageModule); | ||||
|         PageModule GetPageModule(int pageModuleId); | ||||
|  | ||||
| @ -10,46 +10,28 @@ namespace Oqtane.Repository | ||||
|     public class PageModuleRepository : IPageModuleRepository | ||||
|     { | ||||
|         private TenantDBContext _db; | ||||
|         private readonly IModuleDefinitionRepository _moduleDefinitions; | ||||
|         private readonly IPermissionRepository _permissions; | ||||
|  | ||||
|         public PageModuleRepository(TenantDBContext context, IPermissionRepository permissions) | ||||
|         public PageModuleRepository(TenantDBContext context, IModuleDefinitionRepository moduleDefinitions, IPermissionRepository permissions) | ||||
|         { | ||||
|             _db = context; | ||||
|             _moduleDefinitions = moduleDefinitions; | ||||
|             _permissions = permissions; | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<PageModule> GetPageModules(int siteId) | ||||
|         { | ||||
|             IEnumerable<PageModule> pagemodules = _db.PageModule | ||||
|             var pagemodules = _db.PageModule | ||||
|                 .Include(item => item.Module) // eager load modules | ||||
|                 .Where(item => item.Module.SiteId == siteId); | ||||
|                 .Where(item => item.Module.SiteId == siteId).ToList(); | ||||
|             if (pagemodules.Any()) | ||||
|             { | ||||
|                 IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList(); | ||||
|                 foreach (PageModule pagemodule in pagemodules) | ||||
|                 var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList(); | ||||
|                 var permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList(); | ||||
|                 for (int index = 0; index < pagemodules.Count; index++) | ||||
|                 { | ||||
|                     pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions(); | ||||
|                 } | ||||
|             } | ||||
|             return pagemodules; | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<PageModule> GetPageModules(int pageId, string pane) | ||||
|         { | ||||
|             IEnumerable<PageModule> pagemodules = _db.PageModule | ||||
|                 .Include(item => item.Module) // eager load modules | ||||
|                 .Where(item => item.PageId == pageId); | ||||
|             if (pane != "" && pagemodules.Any()) | ||||
|             { | ||||
|                 pagemodules = pagemodules.Where(item => item.Pane == pane); | ||||
|             } | ||||
|             if (pagemodules.Any()) | ||||
|             { | ||||
|                 var siteId = pagemodules.FirstOrDefault().Module.SiteId; | ||||
|                 IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList(); | ||||
|                 foreach (PageModule pagemodule in pagemodules) | ||||
|                 { | ||||
|                     pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions(); | ||||
|                     pagemodules[index] = GetPageModule(pagemodules[index], moduledefinitions, permissions); | ||||
|                 } | ||||
|             } | ||||
|             return pagemodules; | ||||
| @ -89,7 +71,9 @@ namespace Oqtane.Repository | ||||
|             } | ||||
|             if (pagemodule != null) | ||||
|             { | ||||
|                 pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions(); | ||||
|                 var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList(); | ||||
|                 var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList(); | ||||
|                 pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions); | ||||
|             } | ||||
|             return pagemodule; | ||||
|         } | ||||
| @ -100,7 +84,9 @@ namespace Oqtane.Repository | ||||
|                 .SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId); | ||||
|             if (pagemodule != null) | ||||
|             { | ||||
|                 pagemodule.Module.Permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module, pagemodule.ModuleId)?.EncodePermissions(); | ||||
|                 var moduledefinitions = _moduleDefinitions.GetModuleDefinitions(pagemodule.Module.SiteId).ToList(); | ||||
|                 var permissions = _permissions.GetPermissions(pagemodule.Module.SiteId, EntityNames.Module).ToList(); | ||||
|                 pagemodule = GetPageModule(pagemodule, moduledefinitions, permissions); | ||||
|             } | ||||
|             return pagemodule; | ||||
|         } | ||||
| @ -111,5 +97,30 @@ namespace Oqtane.Repository | ||||
|             _db.PageModule.Remove(pageModule); | ||||
|             _db.SaveChanges(); | ||||
|         } | ||||
|  | ||||
|         private PageModule GetPageModule(PageModule pageModule, List<ModuleDefinition> moduleDefinitions, List<Permission> modulePermissions) | ||||
|         { | ||||
|             var permissions = modulePermissions.Where(item => item.EntityId == pageModule.ModuleId).ToList(); | ||||
|  | ||||
|             // moduledefinition permissionnames can specify permissions for other entities (ie. API permissions) | ||||
|             pageModule.Module.ModuleDefinition = moduleDefinitions.Find(item => item.ModuleDefinitionName == pageModule.Module.ModuleDefinitionName); | ||||
|             if (!string.IsNullOrEmpty(pageModule.Module.ModuleDefinition.PermissionNames) && pageModule.Module.ModuleDefinition.PermissionNames.Contains(":")) | ||||
|             { | ||||
|                 foreach (var permissionname in pageModule.Module.ModuleDefinition.PermissionNames.Split(",", System.StringSplitOptions.RemoveEmptyEntries)) | ||||
|                 { | ||||
|                     if (permissionname.Contains(":")) | ||||
|                     { | ||||
|                         // moduledefinition permissionnames can be in the form of "EntityName:PermissionName:Roles" | ||||
|                         var segments = permissionname.Split(':'); | ||||
|                         if (segments.Length == 3 && segments[0] != EntityNames.Module) | ||||
|                         { | ||||
|                             permissions.AddRange(_permissions.GetPermissions(pageModule.Module.SiteId, segments[0], segments[1]).Where(item => item.EntityId == -1)); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             pageModule.Module.Permissions = permissions?.EncodePermissions(); | ||||
|             return pageModule; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -79,23 +79,52 @@ namespace Oqtane.Repository | ||||
|  | ||||
|         public void UpdatePermissions(int siteId, string entityName, int entityId, string permissionStrings) | ||||
|         { | ||||
|             // get current permissions and delete | ||||
|             IEnumerable<Permission> permissions = _db.Permission | ||||
|                 .Where(item => item.EntityName == entityName) | ||||
|                 .Where(item => item.EntityId == entityId) | ||||
|                 .Where(item => item.SiteId == siteId); | ||||
|             foreach (Permission permission in permissions) | ||||
|             bool modified = false; | ||||
|             var existing = new List<Permission>(); | ||||
|             var permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId); | ||||
|             foreach (var permission in permissions) | ||||
|             { | ||||
|                 _db.Permission.Remove(permission); | ||||
|                 if (!existing.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName)) | ||||
|                 { | ||||
|                     existing.AddRange(GetPermissions(siteId, permission.EntityName, permission.PermissionName) | ||||
|                         .Where(item => item.EntityId == entityId || item.EntityId == -1)); | ||||
|                 } | ||||
|  | ||||
|                 var current = existing.FirstOrDefault(item => item.EntityName == permission.EntityName && item.EntityId == permission.EntityId | ||||
|                     && item.PermissionName == permission.PermissionName && item.RoleId == permission.RoleId && item.UserId == permission.UserId); | ||||
|                 if (current != null) | ||||
|                 { | ||||
|                     if (current.IsAuthorized != permission.IsAuthorized) | ||||
|                     { | ||||
|                         current.IsAuthorized = permission.IsAuthorized; | ||||
|                         _db.Entry(current).State = EntityState.Modified; | ||||
|                         modified = true; | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _db.Permission.Add(permission); | ||||
|                     modified = true; | ||||
|                 } | ||||
|             } | ||||
|             // add permissions | ||||
|             permissions = DecodePermissions(permissionStrings, siteId, entityName, entityId); | ||||
|             foreach (Permission permission in permissions) | ||||
|             foreach (var permission in existing) | ||||
|             { | ||||
|                 _db.Permission.Add(permission); | ||||
|                 if (!permissions.Any(item => item.EntityName == permission.EntityName && item.PermissionName == permission.PermissionName | ||||
|                     && item.EntityId == permission.EntityId && item.RoleId == permission.RoleId && item.UserId == permission.UserId)) | ||||
|                 { | ||||
|                     permission.Role = null; // remove linked reference to Role which can cause errors in EF Core change tracking | ||||
|                     _db.Permission.Remove(permission); | ||||
|                     modified = true; | ||||
|                 } | ||||
|             } | ||||
|             if (modified) | ||||
|             { | ||||
|                 _db.SaveChanges(); | ||||
|                 foreach (var entityname in permissions.Select(item => item.EntityName).Distinct()) | ||||
|                 { | ||||
|                     ClearCache(siteId, entityname); | ||||
|                 } | ||||
|             } | ||||
|             _db.SaveChanges(); | ||||
|             ClearCache(siteId, entityName); | ||||
|         } | ||||
|  | ||||
|         public Permission GetPermission(int permissionId) | ||||
| @ -200,8 +229,22 @@ namespace Oqtane.Repository | ||||
|                     securityid = id; | ||||
|                     Permission permission = new Permission(); | ||||
|                     permission.SiteId = siteId; | ||||
|                     permission.EntityName = entityName; | ||||
|                     permission.EntityId = entityId; | ||||
|                     if (!string.IsNullOrEmpty(permissionstring.EntityName)) | ||||
|                     { | ||||
|                         permission.EntityName = permissionstring.EntityName; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         permission.EntityName = entityName; | ||||
|                     } | ||||
|                     if (permission.EntityName == entityName) | ||||
|                     { | ||||
|                         permission.EntityId = entityId; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         permission.EntityId = -1; | ||||
|                     } | ||||
|                     permission.PermissionName = permissionstring.PermissionName; | ||||
|                     permission.RoleId = null; | ||||
|                     permission.UserId = null; | ||||
|  | ||||
| @ -431,6 +431,7 @@ namespace Oqtane.Repository | ||||
|                 PagePermissions = new List<Permission> | ||||
|                 { | ||||
|                     new Permission(PermissionNames.View, RoleNames.Admin, true), | ||||
|                     new Permission(PermissionNames.View, RoleNames.Registered, true), | ||||
|                     new Permission(PermissionNames.Edit, RoleNames.Admin, true) | ||||
|                 }.EncodePermissions(), | ||||
|                 PageTemplateModules = new List<PageTemplateModule> | ||||
| @ -441,6 +442,7 @@ namespace Oqtane.Repository | ||||
|                         ModulePermissions = new List<Permission> | ||||
|                         { | ||||
|                             new Permission(PermissionNames.View, RoleNames.Admin, true), | ||||
|                             new Permission(PermissionNames.View, RoleNames.Registered, true), | ||||
|                             new Permission(PermissionNames.Edit, RoleNames.Admin, true) | ||||
|                         }.EncodePermissions(), | ||||
|                         Content = "" | ||||
|  | ||||
| @ -22,22 +22,15 @@ namespace Oqtane.Security | ||||
|  | ||||
|             if (policy == null) | ||||
|             { | ||||
|                 // policy names must be in the form of "EntityName:PermissionName:Roles" ie. "Module:Edit:Administrators" (roles are comma delimited) | ||||
|                 // policy names must be in the form of "EntityName:PermissionName:Roles" | ||||
|                 if (policyName.Contains(':')) | ||||
|                 { | ||||
|                     var policySegments = policyName.Split(':'); | ||||
|                     if (policySegments.Length >= 3) | ||||
|                     var segments = policyName.Split(':'); | ||||
|                     if (segments.Length == 3) | ||||
|                     { | ||||
|                         // check for optional RequireEntityId segment | ||||
|                         var requireEntityId = false; | ||||
|                         if (policySegments.Length == 4 && policySegments[3] == Constants.RequireEntityId) | ||||
|                         { | ||||
|                             requireEntityId = true; | ||||
|                         } | ||||
|  | ||||
|                         // create policy | ||||
|                         var builder = new AuthorizationPolicyBuilder(); | ||||
|                         builder.AddRequirements(new PermissionRequirement(policySegments[0], policySegments[1], policySegments[2], requireEntityId)); | ||||
|                         builder.AddRequirements(new PermissionRequirement(segments[0], segments[1], segments[2])); | ||||
|                         policy = builder.Build(); | ||||
|  | ||||
|                         // add policy to the AuthorizationOptions | ||||
| @ -59,8 +52,8 @@ namespace Oqtane.Security | ||||
|         private string GetPolicyName(string policyName) | ||||
|         { | ||||
|             // backward compatibility for legacy static policy names | ||||
|             if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}:{Constants.RequireEntityId}"; | ||||
|             if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}:{Constants.RequireEntityId}"; | ||||
|             if (policyName == PolicyNames.ViewModule) policyName = $"{EntityNames.Module}:{PermissionNames.View}:{RoleNames.Admin}"; | ||||
|             if (policyName == PolicyNames.EditModule) policyName = $"{EntityNames.Module}:{PermissionNames.Edit}:{RoleNames.Admin}"; | ||||
|             return policyName; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -34,28 +34,26 @@ namespace Oqtane.Security | ||||
|                 } | ||||
|  | ||||
|                 int entityId = -1; | ||||
|                 if (requirement.RequireEntityId) | ||||
|  | ||||
|                 // get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid )  | ||||
|                 if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) | ||||
|                 { | ||||
|                     // get entityid from querystring based on a parameter format of auth{entityname}id (ie. authmoduleid )  | ||||
|                     if (ctx.Request.Query.ContainsKey("auth" + requirement.EntityName.ToLower() + "id")) | ||||
|                     if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId)) | ||||
|                     { | ||||
|                         if (!int.TryParse(ctx.Request.Query["auth" + requirement.EntityName.ToLower() + "id"], out entityId)) | ||||
|                         entityId = -1; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // legacy support for deprecated CreateAuthorizationPolicyUrl(string url, int entityId) | ||||
|                 if (entityId == -1) | ||||
|                 { | ||||
|                     if (ctx.Request.Query.ContainsKey("entityid")) | ||||
|                     { | ||||
|                         if (!int.TryParse(ctx.Request.Query["entityid"], out entityId)) | ||||
|                         { | ||||
|                             entityId = -1; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // legacy support for deprecated CreateAuthorizationPolicyUrl(string url, int entityId) | ||||
|                     if (entityId == -1) | ||||
|                     { | ||||
|                         if (ctx.Request.Query.ContainsKey("entityid")) | ||||
|                         { | ||||
|                             if (!int.TryParse(ctx.Request.Query["entityid"], out entityId)) | ||||
|                             { | ||||
|                                 entityId = -1; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // validate permissions | ||||
|  | ||||
| @ -8,16 +8,13 @@ namespace Oqtane.Security | ||||
|  | ||||
|         public string PermissionName { get; } | ||||
|  | ||||
|         public string Roles { get; } | ||||
|         public string Roles { get; } // semi-colon delimited | ||||
|  | ||||
|         public bool RequireEntityId { get; } | ||||
|  | ||||
|         public PermissionRequirement(string entityName, string permissionName, string roles, bool requireEntityId) | ||||
|         public PermissionRequirement(string entityName, string permissionName, string roles) | ||||
|         { | ||||
|             EntityName = entityName; | ||||
|             PermissionName = permissionName; | ||||
|             Roles = roles; | ||||
|             RequireEntityId = requireEntityId; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -40,7 +40,7 @@ namespace Oqtane.Security | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return UserSecurity.IsAuthorized(GetUser(principal), roles.Replace(",",";")); | ||||
|                 return UserSecurity.IsAuthorized(GetUser(principal), roles); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| namespace Oqtane.Models | ||||
| { | ||||
|     /// <summary> | ||||
|     /// API management  | ||||
|     /// </summary> | ||||
|     public class Api | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Reference to a <see cref="Site"/>  | ||||
|         /// </summary> | ||||
|         public int SiteId { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The Entity Name | ||||
|         /// </summary> | ||||
|         public string EntityName { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The permissions for the entity | ||||
|         /// </summary> | ||||
|         public string Permissions { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -5,13 +5,18 @@ namespace Oqtane.Models | ||||
|     /// </summary> | ||||
|     public class PermissionString | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// A term describing the entity | ||||
|         /// </summary> | ||||
|         public string EntityName { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A term describing a set of permissions | ||||
|         /// </summary> | ||||
|         public string PermissionName { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The permissions addressed with this name | ||||
|         /// The permissions | ||||
|         /// </summary> | ||||
|         public string Permissions { get; set; } | ||||
|     } | ||||
|  | ||||
| @ -4,8 +4,8 @@ namespace Oqtane.Shared | ||||
| { | ||||
|     public class Constants | ||||
|     { | ||||
|         public static readonly string Version = "3.2.1"; | ||||
|         public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1"; | ||||
|         public static readonly string Version = "3.3.0"; | ||||
|         public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.3.0"; | ||||
|         public const string PackageId = "Oqtane.Framework"; | ||||
|         public const string ClientId = "Oqtane.Client"; | ||||
|         public const string UpdaterPackageId = "Oqtane.Updater"; | ||||
| @ -74,8 +74,6 @@ namespace Oqtane.Shared | ||||
|  | ||||
|         public static readonly string MauiUserAgent = "MAUI"; | ||||
|  | ||||
|         public static readonly string RequireEntityId = "RequireEntityId"; | ||||
|  | ||||
|         // Obsolete constants | ||||
|  | ||||
|         const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shaun Walker
					Shaun Walker