added API Management for managing site level entity permissions
This commit is contained in:
parent
44fc6de82b
commit
cb10dde97d
|
@ -49,6 +49,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||
services.AddScoped<IVisitorService, VisitorService>();
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<IApiService, ApiService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
75
Oqtane.Client/Modules/Admin/Api/Edit.razor
Normal file
75
Oqtane.Client/Modules/Admin/Api/Edit.razor
Normal file
|
@ -0,0 +1,75 @@
|
|||
@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);
|
||||
}
|
||||
}
|
||||
}
|
36
Oqtane.Client/Modules/Admin/Api/Index.razor
Normal file
36
Oqtane.Client/Modules/Admin/Api/Index.razor
Normal file
|
@ -0,0 +1,36 @@
|
|||
@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);
|
||||
}
|
||||
}
|
126
Oqtane.Client/Resources/Modules/Admin/Api/Edit.resx
Normal file
126
Oqtane.Client/Resources/Modules/Admin/Api/Edit.resx
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="EntityName.HelpText" xml:space="preserve">
|
||||
<value>The Name Of The Entity</value>
|
||||
</data>
|
||||
<data name="EntityName.Text" xml:space="preserve">
|
||||
<value>Entity:</value>
|
||||
</data>
|
||||
</root>
|
126
Oqtane.Client/Resources/Modules/Admin/Api/Index.resx
Normal file
126
Oqtane.Client/Resources/Modules/Admin/Api/Index.resx
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Entity" xml:space="preserve">
|
||||
<value>Entity</value>
|
||||
</data>
|
||||
<data name="Permissions" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
</root>
|
32
Oqtane.Client/Services/ApiService.cs
Normal file
32
Oqtane.Client/Services/ApiService.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
31
Oqtane.Client/Services/Interfaces/IApiService.cs
Normal file
31
Oqtane.Client/Services/Interfaces/IApiService.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
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);
|
||||
}
|
||||
}
|
120
Oqtane.Server/Controllers/ApiController.cs
Normal file
120
Oqtane.Server/Controllers/ApiController.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
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 ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public ApiController(IPermissionRepository permissions, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_permissions = permissions;
|
||||
_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)
|
||||
{
|
||||
return new Api { SiteId = siteid, EntityName = entityname, Permissions = _permissions.GetPermissions(siteid, entityname).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,10 +24,11 @@ namespace Oqtane.Controllers
|
|||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/<controller>?siteid=x
|
||||
[HttpGet]
|
||||
// GET: api/<controller>?siteid=x
|
||||
[HttpGet]
|
||||
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
|
||||
public IEnumerable<Profile> Get(string siteid)
|
||||
{
|
||||
int SiteId;
|
||||
|
@ -45,6 +46,7 @@ namespace Oqtane.Controllers
|
|||
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
|
||||
public Profile Get(int id)
|
||||
{
|
||||
var profile = _profiles.GetProfile(id);
|
||||
|
@ -62,7 +64,7 @@ namespace Oqtane.Controllers
|
|||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||
public Profile Post([FromBody] Profile profile)
|
||||
{
|
||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
|
||||
|
@ -82,7 +84,7 @@ namespace Oqtane.Controllers
|
|||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||
public Profile Put(int id, [FromBody] Profile profile)
|
||||
{
|
||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
|
||||
|
@ -102,7 +104,7 @@ namespace Oqtane.Controllers
|
|||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||
public void Delete(int id)
|
||||
{
|
||||
var profile = _profiles.GetProfile(id);
|
||||
|
|
|
@ -60,6 +60,9 @@ namespace Oqtane.Infrastructure
|
|||
case "3.2.1":
|
||||
Upgrade_3_2_1(tenant, scope);
|
||||
break;
|
||||
case "3.3.0":
|
||||
Upgrade_3_3_0(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -303,5 +306,46 @@ namespace Oqtane.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
var pageTemplates = new List<PageTemplate>();
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "API Management",
|
||||
Parent = "Admin",
|
||||
Order = 35,
|
||||
Path = "admin/apis",
|
||||
Icon = Icons.CloudDownload,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
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 = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
|
||||
|
||||
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
foreach (Site site in sites.GetSites().ToList())
|
||||
{
|
||||
sites.CreatePages(site, pageTemplates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
Oqtane.Shared/Models/Api.cs
Normal file
23
Oqtane.Shared/Models/Api.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
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; }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user