Merge pull request #2517 from sbwalker/dev
added API Management for managing site level entity permissions
This commit is contained in:
commit
2c179867e8
|
@ -49,6 +49,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||||
services.AddScoped<IVisitorService, VisitorService>();
|
services.AddScoped<IVisitorService, VisitorService>();
|
||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
|
services.AddScoped<IApiService, ApiService>();
|
||||||
|
|
||||||
return services;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// GET: api/<controller>?siteid=x
|
// GET: api/<controller>?siteid=x
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
|
||||||
public IEnumerable<Profile> Get(string siteid)
|
public IEnumerable<Profile> Get(string siteid)
|
||||||
{
|
{
|
||||||
int SiteId;
|
int SiteId;
|
||||||
|
@ -45,6 +46,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// GET api/<controller>/5
|
// GET api/<controller>/5
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
|
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Read}:{RoleNames.Registered}")]
|
||||||
public Profile Get(int id)
|
public Profile Get(int id)
|
||||||
{
|
{
|
||||||
var profile = _profiles.GetProfile(id);
|
var profile = _profiles.GetProfile(id);
|
||||||
|
@ -62,7 +64,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// POST api/<controller>
|
// POST api/<controller>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Roles = RoleNames.Admin)]
|
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||||
public Profile Post([FromBody] Profile profile)
|
public Profile Post([FromBody] Profile profile)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
|
if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
|
||||||
|
@ -82,7 +84,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// PUT api/<controller>/5
|
// PUT api/<controller>/5
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
[Authorize(Roles = RoleNames.Admin)]
|
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||||
public Profile Put(int id, [FromBody] Profile profile)
|
public Profile Put(int id, [FromBody] Profile profile)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
|
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
|
||||||
|
@ -102,7 +104,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// DELETE api/<controller>/5
|
// DELETE api/<controller>/5
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[Authorize(Roles = RoleNames.Admin)]
|
[Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
||||||
public void Delete(int id)
|
public void Delete(int id)
|
||||||
{
|
{
|
||||||
var profile = _profiles.GetProfile(id);
|
var profile = _profiles.GetProfile(id);
|
||||||
|
|
|
@ -60,6 +60,9 @@ namespace Oqtane.Infrastructure
|
||||||
case "3.2.1":
|
case "3.2.1":
|
||||||
Upgrade_3_2_1(tenant, scope);
|
Upgrade_3_2_1(tenant, scope);
|
||||||
break;
|
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