mirror of https://github.com/abpframework/abp.git
99 changed files with 8238 additions and 244 deletions
@ -0,0 +1,30 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Primitives; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Security |
|||
{ |
|||
public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency |
|||
{ |
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
/*X-Content-Type-Options header tells the browser to not try and “guess” what a mimetype of a resource might be, and to just take what mimetype the server has returned as fact.*/ |
|||
AddHeaderIfNotExists(context, "X-Content-Type-Options", "nosniff"); |
|||
|
|||
/*X-XSS-Protection is a feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks*/ |
|||
AddHeaderIfNotExists(context, "X-XSS-Protection", "1; mode=block"); |
|||
|
|||
/*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/ |
|||
AddHeaderIfNotExists(context, "X-Frame-Options", "SAMEORIGIN"); |
|||
|
|||
await next.Invoke(context); |
|||
} |
|||
|
|||
protected virtual void AddHeaderIfNotExists(HttpContext context, string key, string value) |
|||
{ |
|||
context.Response.Headers.AddIfNotContains(new KeyValuePair<string, StringValues>(key, value)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.BlobStoring |
|||
{ |
|||
public class BlobNormalizeNaming |
|||
{ |
|||
public string ContainerName { get; } |
|||
|
|||
public string BlobName { get; } |
|||
|
|||
public BlobNormalizeNaming(string containerName, string blobName) |
|||
{ |
|||
ContainerName = containerName; |
|||
BlobName = blobName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.BlobStoring |
|||
{ |
|||
public class BlobNormalizeNamingService : IBlobNormalizeNamingService, ITransientDependency |
|||
{ |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
public BlobNormalizeNamingService(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public BlobNormalizeNaming NormalizeNaming( |
|||
BlobContainerConfiguration configuration, |
|||
string containerName, |
|||
string blobName) |
|||
{ |
|||
|
|||
if (!configuration.NamingNormalizers.Any()) |
|||
{ |
|||
return new BlobNormalizeNaming(containerName, blobName); |
|||
} |
|||
|
|||
using (var scope = ServiceProvider.CreateScope()) |
|||
{ |
|||
foreach (var normalizerType in configuration.NamingNormalizers) |
|||
{ |
|||
var normalizer = scope.ServiceProvider |
|||
.GetRequiredService(normalizerType) |
|||
.As<IBlobNamingNormalizer>(); |
|||
|
|||
containerName = containerName.IsNullOrWhiteSpace()? containerName: normalizer.NormalizeContainerName(containerName); |
|||
blobName = blobName.IsNullOrWhiteSpace()? blobName: normalizer.NormalizeBlobName(blobName); |
|||
} |
|||
|
|||
return new BlobNormalizeNaming(containerName, blobName); |
|||
} |
|||
} |
|||
|
|||
public string NormalizeContainerName(BlobContainerConfiguration configuration, string containerName) |
|||
{ |
|||
if (!configuration.NamingNormalizers.Any()) |
|||
{ |
|||
return containerName; |
|||
} |
|||
|
|||
return NormalizeNaming(configuration, containerName, null).ContainerName; |
|||
} |
|||
|
|||
public string NormalizeBlobName(BlobContainerConfiguration configuration, string blobName) |
|||
{ |
|||
if (!configuration.NamingNormalizers.Any()) |
|||
{ |
|||
return blobName; |
|||
} |
|||
|
|||
return NormalizeNaming(configuration, null, blobName).BlobName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.BlobStoring |
|||
{ |
|||
public interface IBlobNormalizeNamingService |
|||
{ |
|||
BlobNormalizeNaming NormalizeNaming(BlobContainerConfiguration configuration, string containerName, string blobName); |
|||
|
|||
string NormalizeContainerName(BlobContainerConfiguration configuration, string containerName); |
|||
|
|||
string NormalizeBlobName(BlobContainerConfiguration configuration, string blobName); |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Shouldly; |
|||
using System; |
|||
using System.Reflection; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Conventions |
|||
{ |
|||
public class AbpServiceConvention_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
private readonly IConventionalRouteBuilder _conventionalRouteBuilder; |
|||
private readonly IOptions<AbpAspNetCoreMvcOptions> _options; |
|||
|
|||
public AbpServiceConvention_Tests() |
|||
{ |
|||
_conventionalRouteBuilder = GetRequiredService<IConventionalRouteBuilder>(); |
|||
_options = GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Remove_Derived_Controller_If_Not_Expose_Service() |
|||
{ |
|||
// Arrange
|
|||
var applicationModel = new ApplicationModel(); |
|||
var baseControllerModel = new ControllerModel(typeof(BaseController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(baseControllerModel); |
|||
|
|||
var derivedControllerModel = new ControllerModel(typeof(DerivedController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(derivedControllerModel); |
|||
|
|||
var abpServiceConvention = new AbpServiceConvention(_options, _conventionalRouteBuilder); |
|||
|
|||
// Act
|
|||
abpServiceConvention.Apply(applicationModel); |
|||
|
|||
// Assert
|
|||
applicationModel.Controllers.ShouldContain(baseControllerModel); |
|||
applicationModel.Controllers.ShouldContain(derivedControllerModel); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Remove_Exposed_Controller_If_Expose_Self() |
|||
{ |
|||
// Arrange
|
|||
var applicationModel = new ApplicationModel(); |
|||
var baseControllerModel = new ControllerModel(typeof(BaseController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(baseControllerModel); |
|||
|
|||
var derivedControllerModel = new ControllerModel(typeof(ExposeServiceIncludeSelfDerivedController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(derivedControllerModel); |
|||
|
|||
var abpServiceConvention = new AbpServiceConvention(_options, _conventionalRouteBuilder); |
|||
|
|||
// Act
|
|||
abpServiceConvention.Apply(applicationModel); |
|||
|
|||
// Assert
|
|||
applicationModel.Controllers.ShouldNotContain(baseControllerModel); |
|||
applicationModel.Controllers.ShouldContain(derivedControllerModel); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Remove_Derived_Controller_If_No_Base_Controller_Model() |
|||
{ |
|||
// Arrange
|
|||
var applicationModel = new ApplicationModel(); |
|||
var derivedControllerModel = new ControllerModel(typeof(ExposeServiceDerivedController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(derivedControllerModel); |
|||
|
|||
var abpServiceConvention = new AbpServiceConvention(_options, _conventionalRouteBuilder); |
|||
|
|||
// Act
|
|||
abpServiceConvention.Apply(applicationModel); |
|||
|
|||
// Assert
|
|||
applicationModel.Controllers.ShouldContain(derivedControllerModel); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Remove_Derived_Controller_If_Expose_Service() |
|||
{ |
|||
// Arrange
|
|||
var applicationModel = new ApplicationModel(); |
|||
var baseControllerModel = new ControllerModel(typeof(BaseController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(baseControllerModel); |
|||
|
|||
var derivedControllerModel = new ControllerModel(typeof(ExposeServiceDerivedController).GetTypeInfo(), Array.Empty<object>()) |
|||
{ |
|||
Application = applicationModel |
|||
}; |
|||
applicationModel.Controllers.Add(derivedControllerModel); |
|||
|
|||
var abpServiceConvention = new AbpServiceConvention(_options, _conventionalRouteBuilder); |
|||
|
|||
// Act
|
|||
abpServiceConvention.Apply(applicationModel); |
|||
|
|||
// Assert
|
|||
applicationModel.Controllers.ShouldContain(baseControllerModel); |
|||
applicationModel.Controllers.ShouldNotContain(derivedControllerModel); |
|||
} |
|||
} |
|||
|
|||
public class BaseController : Controller |
|||
{ |
|||
} |
|||
|
|||
public class DerivedController : BaseController |
|||
{ |
|||
} |
|||
|
|||
[ExposeServices(typeof(BaseController))] |
|||
public class ExposeServiceDerivedController : BaseController |
|||
{ |
|||
} |
|||
|
|||
[ExposeServices(typeof(BaseController), IncludeSelf = true)] |
|||
public class ExposeServiceIncludeSelfDerivedController : BaseController |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Security.Headers |
|||
{ |
|||
public class SecurityHeadersTestController : AbpController |
|||
{ |
|||
public ActionResult Get() |
|||
{ |
|||
return Content("OK"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Security.Headers |
|||
{ |
|||
public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task SecurityHeaders_Should_Be_Added() |
|||
{ |
|||
var responseMessage = await GetResponseAsync("/SecurityHeadersTest/Get"); |
|||
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff"); |
|||
responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block"); |
|||
responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,24 @@ |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace Volo.CmsKit.Migrations |
|||
{ |
|||
public partial class Page_Remove_Description : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropColumn( |
|||
name: "Description", |
|||
table: "CmsPages"); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AddColumn<string>( |
|||
name: "Description", |
|||
table: "CmsPages", |
|||
type: "nvarchar(512)", |
|||
maxLength: 512, |
|||
nullable: true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using IdentityServer4.Validation; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
|
|||
namespace Volo.Abp.IdentityServer |
|||
{ |
|||
public static class AbpIdentityServerServiceCollectionExtensions |
|||
{ |
|||
public static void AddAbpStrictRedirectUriValidator(this IServiceCollection services) |
|||
{ |
|||
services.Replace(ServiceDescriptor.Transient<IRedirectUriValidator, AbpStrictRedirectUriValidator>()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Validation; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Text.Formatting; |
|||
|
|||
namespace Volo.Abp.IdentityServer |
|||
{ |
|||
public class AbpStrictRedirectUriValidator : StrictRedirectUriValidator |
|||
{ |
|||
public override async Task<bool> IsRedirectUriValidAsync(string requestedUri, Client client) |
|||
{ |
|||
var isAllowed = await base.IsRedirectUriValidAsync(requestedUri, client); |
|||
return isAllowed || await IsRedirectUriValidWithDomainFormatsAsync(client.RedirectUris, requestedUri); |
|||
} |
|||
|
|||
public override async Task<bool> IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client) |
|||
{ |
|||
var isAllowed = await base.IsPostLogoutRedirectUriValidAsync(requestedUri, client); |
|||
return isAllowed || await IsRedirectUriValidWithDomainFormatsAsync(client.PostLogoutRedirectUris, requestedUri); |
|||
} |
|||
|
|||
protected virtual Task<bool> IsRedirectUriValidWithDomainFormatsAsync(IEnumerable<string> uris, string requestedUri) |
|||
{ |
|||
if (uris == null) |
|||
{ |
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
foreach (var url in uris) |
|||
{ |
|||
var extractResult = FormattedStringValueExtracter.Extract(requestedUri, url, ignoreCase: true); |
|||
if (extractResult.IsMatch) |
|||
{ |
|||
return Task.FromResult(true); |
|||
} |
|||
} |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities.Events; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.IdentityServer.Clients; |
|||
using Volo.Abp.IdentityServer.ApiResources; |
|||
using Volo.Abp.IdentityServer.ApiScopes; |
|||
using Volo.Abp.IdentityServer.IdentityResources; |
|||
|
|||
namespace Volo.Abp.IdentityServer |
|||
{ |
|||
public class IdentityServerCacheItemInvalidator : |
|||
ILocalEventHandler<EntityChangedEventData<Client>>, |
|||
ILocalEventHandler<EntityChangedEventData<ClientCorsOrigin>>, |
|||
ILocalEventHandler<EntityChangedEventData<IdentityResource>>, |
|||
ILocalEventHandler<EntityChangedEventData<ApiResource>>, |
|||
ILocalEventHandler<EntityChangedEventData<ApiScope>>, |
|||
ITransientDependency |
|||
{ |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
public IdentityServerCacheItemInvalidator(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<Client> eventData) |
|||
{ |
|||
var clientCache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.Client>>(); |
|||
await clientCache.RemoveAsync(eventData.Entity.ClientId); |
|||
|
|||
var corsCache = ServiceProvider.GetRequiredService<IDistributedCache<AllowedCorsOriginsCacheItem>>(); |
|||
await corsCache.RemoveAsync(AllowedCorsOriginsCacheItem.AllOrigins); |
|||
} |
|||
|
|||
public async Task HandleEventAsync(EntityChangedEventData<ClientCorsOrigin> eventData) |
|||
{ |
|||
var corsCache = ServiceProvider.GetRequiredService<IDistributedCache<AllowedCorsOriginsCacheItem>>(); |
|||
await corsCache.RemoveAsync(AllowedCorsOriginsCacheItem.AllOrigins); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<IdentityResource> eventData) |
|||
{ |
|||
var cache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.IdentityResource>>(); |
|||
await cache.RemoveAsync(eventData.Entity.Name); |
|||
|
|||
var resourcesCache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.Resources>>(); |
|||
await resourcesCache.RemoveAsync(ResourceStore.AllResourcesKey); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<ApiResource> eventData) |
|||
{ |
|||
var cache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.ApiResource>>(); |
|||
await cache.RemoveAsync(ResourceStore.ApiResourceNameCacheKeyPrefix + eventData.Entity.Name); |
|||
await cache.RemoveManyAsync(eventData.Entity.Scopes.Select(x => ResourceStore.ApiResourceScopeNameCacheKeyPrefix + x.Scope)); |
|||
|
|||
var resourcesCache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.Resources>>(); |
|||
await resourcesCache.RemoveAsync(ResourceStore.AllResourcesKey); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(EntityChangedEventData<ApiScope> eventData) |
|||
{ |
|||
var cache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.ApiScope>>(); |
|||
await cache.RemoveAsync(eventData.Entity.Name); |
|||
|
|||
var resourcesCache = ServiceProvider.GetRequiredService<IDistributedCache<IdentityServer4.Models.Resources>>(); |
|||
await resourcesCache.RemoveAsync(ResourceStore.AllResourcesKey); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Validation; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.IdentityServer |
|||
{ |
|||
public class AbpStrictRedirectUriValidator_Tests : AbpIdentityServerTestBase |
|||
{ |
|||
private readonly IRedirectUriValidator _abpStrictRedirectUriValidator; |
|||
|
|||
private readonly Client _testClient = new Client |
|||
{ |
|||
RedirectUris = new List<string> |
|||
{ |
|||
"https://{0}.api.abp.io:8080/signin-oidc", |
|||
"http://{0}.ng.abp.io/index.html" |
|||
}, |
|||
PostLogoutRedirectUris = new List<string> |
|||
{ |
|||
"https://{0}.api.abp.io:8080/signin-oidc", |
|||
"http://{0}.ng.abp.io/index.html" |
|||
} |
|||
}; |
|||
|
|||
public AbpStrictRedirectUriValidator_Tests() |
|||
{ |
|||
_abpStrictRedirectUriValidator = GetRequiredService<IRedirectUriValidator>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
services.AddAbpStrictRedirectUriValidator(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Register_AbpStrictRedirectUriValidator() |
|||
{ |
|||
_abpStrictRedirectUriValidator.GetType().ShouldBe(typeof(AbpStrictRedirectUriValidator)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task IsRedirectUriValidAsync() |
|||
{ |
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://t1.api.abp.io:8080/signin-oidc", _testClient)).ShouldBeTrue(); |
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://t2.ng.abp.io/index.html", _testClient)).ShouldBeTrue(); |
|||
|
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://api.abp:8080/", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://ng.abp.io", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://api.t1.abp:8080/", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://ng.t1.abp.io", _testClient)).ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task IsPostLogoutRedirectUriValidAsync() |
|||
{ |
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://t1.api.abp.io:8080/signin-oidc", _testClient)).ShouldBeTrue(); |
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://t2.ng.abp.io/index.html", _testClient)).ShouldBeTrue(); |
|||
|
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://api.abp:8080/", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://ng.abp.io", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://api.t1.abp:8080/", _testClient)).ShouldBeFalse(); |
|||
(await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://ng.t1.abp.io", _testClient)).ShouldBeFalse(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Services; |
|||
using IdentityServer4.Stores; |
|||
using Shouldly; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.IdentityServer.ApiResources; |
|||
using Volo.Abp.IdentityServer.ApiScopes; |
|||
using Volo.Abp.IdentityServer.Clients; |
|||
using Volo.Abp.IdentityServer.IdentityResources; |
|||
using Xunit; |
|||
using ApiResource = IdentityServer4.Models.ApiResource; |
|||
using ApiScope = IdentityServer4.Models.ApiScope; |
|||
using Client = IdentityServer4.Models.Client; |
|||
using IdentityResource = IdentityServer4.Models.IdentityResource; |
|||
|
|||
namespace Volo.Abp.IdentityServer.Cache |
|||
{ |
|||
public class IdentityServerCacheItemInvalidator_Tests : AbpIdentityServerTestBase |
|||
{ |
|||
private readonly IClientStore _clientStore; |
|||
private readonly IResourceStore _resourceStore; |
|||
|
|||
private readonly IClientRepository _clientRepository; |
|||
private readonly IIdentityResourceRepository _identityResourceRepository; |
|||
private readonly IApiResourceRepository _apiResourceRepository; |
|||
private readonly IApiScopeRepository _apiScopeRepository; |
|||
|
|||
private readonly IDistributedCache<Client> _clientCache; |
|||
private readonly IDistributedCache<IdentityResource> _identityResourceCache; |
|||
private readonly IDistributedCache<ApiResource>_apiResourceCache; |
|||
private readonly IDistributedCache<ApiScope> _apiScopeCache; |
|||
private readonly IDistributedCache<Resources> _resourceCache; |
|||
|
|||
private readonly AbpIdentityServerTestData _testData; |
|||
|
|||
public IdentityServerCacheItemInvalidator_Tests() |
|||
{ |
|||
_clientStore = GetRequiredService<IClientStore>(); |
|||
_resourceStore = GetRequiredService<IResourceStore>(); |
|||
|
|||
_clientRepository = GetRequiredService<IClientRepository>(); |
|||
_identityResourceRepository = GetRequiredService<IIdentityResourceRepository>(); |
|||
_apiResourceRepository = GetRequiredService<IApiResourceRepository>(); |
|||
_apiScopeRepository = GetRequiredService<IApiScopeRepository>(); |
|||
|
|||
_clientCache = GetRequiredService<IDistributedCache<Client>>(); |
|||
_identityResourceCache = GetRequiredService<IDistributedCache<IdentityResource>>(); |
|||
_apiResourceCache = GetRequiredService<IDistributedCache<ApiResource>>(); |
|||
_apiScopeCache = GetRequiredService<IDistributedCache<ApiScope>>(); |
|||
_resourceCache = GetRequiredService<IDistributedCache<Resources>>(); |
|||
|
|||
_testData = GetRequiredService<AbpIdentityServerTestData>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Models_Should_Cached_And_Invalidator_When_Its_Changed() |
|||
{ |
|||
//client
|
|||
var clientId = "ClientId1"; |
|||
(await _clientCache.GetAsync(clientId)).ShouldBeNull(); |
|||
|
|||
var client = await _clientStore.FindClientByIdAsync(clientId); |
|||
client.ShouldNotBeNull(); |
|||
|
|||
var clientCacheItem = await _clientCache.GetAsync(clientId); |
|||
clientCacheItem.ShouldNotBeNull(); |
|||
|
|||
await _clientRepository.DeleteAsync(_testData.Client1Id); |
|||
(await _clientCache.GetAsync(clientId)).ShouldBeNull(); |
|||
|
|||
|
|||
//Api Resource
|
|||
var newApiResource1 = "NewApiResource1"; |
|||
var newApiResource2 = "NewApiResource2"; |
|||
var testApiResourceName1 = "Test-ApiResource-Name-1"; |
|||
var testApiResourceApiScopeName1 = "Test-ApiResource-ApiScope-Name-1"; |
|||
var newApiResources = new[] {newApiResource1, newApiResource2}; |
|||
|
|||
//FindApiResourcesByNameAsync
|
|||
(await _apiResourceCache.GetAsync(newApiResource1)).ShouldBeNull(); |
|||
(await _apiResourceCache.GetAsync(newApiResource2)).ShouldBeNull(); |
|||
await _resourceStore.FindApiResourcesByNameAsync(newApiResources); |
|||
(await _apiResourceCache.GetAsync(ResourceStore.ApiResourceNameCacheKeyPrefix + newApiResource1)).ShouldNotBeNull(); |
|||
(await _apiResourceCache.GetAsync(ResourceStore.ApiResourceNameCacheKeyPrefix + newApiResource2)).ShouldNotBeNull(); |
|||
|
|||
var apiResource1 = await _apiResourceRepository.FindByNameAsync(newApiResource1); |
|||
await _apiResourceRepository.DeleteAsync(apiResource1); |
|||
(await _apiResourceCache.GetAsync(newApiResource1)).ShouldBeNull(); |
|||
|
|||
var apiResource2 = await _apiResourceRepository.FindByNameAsync(newApiResource2); |
|||
await _apiResourceRepository.DeleteAsync(apiResource2); |
|||
(await _apiResourceCache.GetAsync(newApiResource2)).ShouldBeNull(); |
|||
|
|||
//FindApiResourcesByScopeNameAsync
|
|||
(await _apiResourceCache.GetAsync(ResourceStore.ApiResourceScopeNameCacheKeyPrefix + testApiResourceApiScopeName1)).ShouldBeNull(); |
|||
await _resourceStore.FindApiResourcesByScopeNameAsync(new []{ testApiResourceApiScopeName1 }); |
|||
(await _apiResourceCache.GetAsync(ResourceStore.ApiResourceScopeNameCacheKeyPrefix + testApiResourceApiScopeName1)).ShouldNotBeNull(); |
|||
|
|||
var testApiResource1 = await _apiResourceRepository.FindByNameAsync(testApiResourceName1); |
|||
await _apiResourceRepository.DeleteAsync(testApiResource1); |
|||
(await _apiResourceCache.GetAsync(ResourceStore.ApiResourceScopeNameCacheKeyPrefix + testApiResourceApiScopeName1)).ShouldBeNull(); |
|||
|
|||
|
|||
//Identity Resource
|
|||
var testIdentityResourceName = "Test-Identity-Resource-Name-1"; |
|||
var testIdentityResourceNames = new[] {testIdentityResourceName}; |
|||
(await _identityResourceCache.GetAsync(testIdentityResourceName)).ShouldBeNull(); |
|||
await _resourceStore.FindIdentityResourcesByScopeNameAsync(testIdentityResourceNames); |
|||
(await _identityResourceCache.GetAsync(testIdentityResourceName)).ShouldNotBeNull(); |
|||
|
|||
var testIdentityResource = await _identityResourceRepository.FindByNameAsync(testIdentityResourceName); |
|||
await _identityResourceRepository.DeleteAsync(testIdentityResource); |
|||
(await _identityResourceCache.GetAsync(testIdentityResourceName)).ShouldBeNull(); |
|||
|
|||
|
|||
//Api Scope
|
|||
var testApiScopeName = "Test-ApiScope-Name-1"; |
|||
var testApiScopeNames = new[] {testApiScopeName}; |
|||
(await _apiScopeCache.GetAsync(testApiScopeName)).ShouldBeNull(); |
|||
await _resourceStore.FindApiScopesByNameAsync(testApiScopeNames); |
|||
(await _apiScopeCache.GetAsync(testApiScopeName)).ShouldNotBeNull(); |
|||
|
|||
var testApiScope = await _apiScopeRepository.GetByNameAsync(testApiScopeName); |
|||
await _apiScopeRepository.DeleteAsync(testApiScope); |
|||
(await _apiScopeCache.GetAsync(testApiScopeName)).ShouldBeNull(); |
|||
|
|||
|
|||
//Resources
|
|||
(await _resourceCache.GetAsync(ResourceStore.AllResourcesKey)).ShouldBeNull(); |
|||
await _resourceStore.GetAllResourcesAsync(); |
|||
(await _resourceCache.GetAsync(ResourceStore.AllResourcesKey)).ShouldNotBeNull(); |
|||
|
|||
await _identityResourceRepository.DeleteAsync(_testData.IdentityResource1Id); |
|||
(await _resourceCache.GetAsync(ResourceStore.AllResourcesKey)).ShouldBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Stores; |
|||
using Shouldly; |
|||
using Volo.Abp.IdentityServer.ApiResources; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.IdentityServer |
|||
{ |
|||
public class ResourceStore_Cache_Tests : AbpIdentityServerDomainTestBase |
|||
{ |
|||
private readonly IResourceStore _resourceStore; |
|||
|
|||
public ResourceStore_Cache_Tests() |
|||
{ |
|||
_resourceStore = GetRequiredService<IResourceStore>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindIdentityResourcesByScopeNameAsync() |
|||
{ |
|||
var identityResources = (await _resourceStore.FindIdentityResourcesByScopeNameAsync(new[] {"Test-Identity-Resource-Name-1"})).ToList(); |
|||
identityResources.ShouldNotBeEmpty(); |
|||
identityResources.Count.ShouldBe(1); |
|||
identityResources.First().Name.ShouldBe("Test-Identity-Resource-Name-1"); |
|||
|
|||
var identityResources2 = (await _resourceStore.FindIdentityResourcesByScopeNameAsync(new[] {"Test-Identity-Resource-Name-1", "NewIdentityResource1"})).ToList(); |
|||
identityResources2.ShouldNotBeEmpty(); |
|||
identityResources2.Count.ShouldBe(2); |
|||
identityResources2.ShouldContain(x => x.Name == "NewIdentityResource1"); |
|||
identityResources2.ShouldContain(x => x.Name == identityResources.First().Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindApiScopesByNameAsync() |
|||
{ |
|||
var apiScopes1 = (await _resourceStore.FindApiScopesByNameAsync(new[] {"Test-ApiScope-Name-1"})).ToList(); |
|||
apiScopes1.ShouldNotBeEmpty(); |
|||
apiScopes1.Count.ShouldBe(1); |
|||
apiScopes1.First().Name.ShouldBe("Test-ApiScope-Name-1"); |
|||
|
|||
var apiScopes2 = (await _resourceStore.FindApiScopesByNameAsync(new[] {"Test-ApiScope-Name-1", "Test-ApiScope-Name-2"})).ToList(); |
|||
apiScopes2.ShouldNotBeEmpty(); |
|||
apiScopes2.Count.ShouldBe(2); |
|||
apiScopes2.ShouldContain(x => x.Name == "Test-ApiScope-Name-1"); |
|||
apiScopes2.ShouldContain(x => x.Name == apiScopes1.First().Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindApiResourcesByScopeNameAsync() |
|||
{ |
|||
var apiResources1 = (await _resourceStore.FindApiResourcesByScopeNameAsync(new[] {"Test-ApiResource-ApiScope-Name-1"})).ToList(); |
|||
apiResources1.ShouldNotBeEmpty(); |
|||
apiResources1.Count.ShouldBe(1); |
|||
apiResources1.First().Name.ShouldBe("Test-ApiResource-Name-1"); |
|||
|
|||
var apiResources2 = (await _resourceStore.FindApiResourcesByScopeNameAsync(new[] {"Test-ApiResource-ApiScope-Name-1", nameof(ApiResourceScope.Scope)})).ToList(); |
|||
apiResources2.ShouldNotBeEmpty(); |
|||
apiResources2.Count.ShouldBe(2); |
|||
apiResources2.ShouldContain(x => x.Name == "Test-ApiResource-Name-1"); |
|||
apiResources2.ShouldContain(x => x.Name == apiResources1.First().Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FindApiResourcesByNameAsync() |
|||
{ |
|||
var apiResources1 = (await _resourceStore.FindApiResourcesByNameAsync(new[] {"Test-ApiResource-Name-1"})).ToList(); |
|||
apiResources1.ShouldNotBeEmpty(); |
|||
apiResources1.Count.ShouldBe(1); |
|||
apiResources1.First().Name.ShouldBe("Test-ApiResource-Name-1"); |
|||
|
|||
var apiResources2 = (await _resourceStore.FindApiResourcesByNameAsync(new[] {"Test-ApiResource-Name-1", "NewApiResource1"})).ToList(); |
|||
apiResources2.ShouldNotBeEmpty(); |
|||
apiResources2.Count.ShouldBe(2); |
|||
apiResources2.ShouldContain(x => x.Name == "NewApiResource1"); |
|||
apiResources2.ShouldContain(x => x.Name == apiResources1.First().Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
<h2>{{ 'AbpSettingManagement::Menu:Emailing' | abpLocalization }}</h2> |
|||
|
|||
<hr class="my-3" /> |
|||
|
|||
<form *ngIf="form" [formGroup]="form" (ngSubmit)="submit()" validateOnSubmit> |
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::DefaultFromDisplayName' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="defaultFromDisplayName" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::DefaultFromAddress' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="defaultFromAddress" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::SmtpHost' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="smtpHost" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::SmtpPort' | abpLocalization }}</label> |
|||
<input type="number" class="form-control" formControlName="smtpPort" /> |
|||
</div> |
|||
|
|||
<div class="custom-checkbox custom-control mb-2"> |
|||
<input |
|||
type="checkbox" |
|||
id="smtp-enable-ssl" |
|||
class="custom-control-input" |
|||
formControlName="smtpEnableSsl" |
|||
/> |
|||
<label class="custom-control-label" for="smtp-enable-ssl">{{ |
|||
'AbpSettingManagement::SmtpEnableSsl' | abpLocalization |
|||
}}</label> |
|||
</div> |
|||
<div class="custom-checkbox custom-control mb-2"> |
|||
<input |
|||
type="checkbox" |
|||
id="smtp-use-default-credentials" |
|||
class="custom-control-input" |
|||
formControlName="smtpUseDefaultCredentials" |
|||
/> |
|||
<label class="custom-control-label" for="smtp-use-default-credentials">{{ |
|||
'AbpSettingManagement::SmtpUseDefaultCredentials' | abpLocalization |
|||
}}</label> |
|||
</div> |
|||
|
|||
<div [@collapse]="form.get('smtpUseDefaultCredentials').value ? 'collapsed' : 'expanded'"> |
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::SmtpDomain' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="smtpDomain" /> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::SmtpUserName' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="smtpUserName" /> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label>{{ 'AbpSettingManagement::SmtpPassword' | abpLocalization }}</label> |
|||
<input type="text" class="form-control" formControlName="smtpPassword" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<hr /> |
|||
|
|||
<button type="submit" class="btn btn-primary"> |
|||
{{ 'AbpSettingManagement::Save' | abpLocalization }} |
|||
</button> |
|||
</form> |
|||
@ -0,0 +1,60 @@ |
|||
import { collapse, ToasterService } from '@abp/ng.theme.shared'; |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
import { finalize } from 'rxjs/operators'; |
|||
import { EmailSettingsService } from '../../proxy/email-settings.service'; |
|||
import { EmailSettingsDto } from '../../proxy/models'; |
|||
|
|||
@Component({ |
|||
selector: 'abp-email-setting-group', |
|||
templateUrl: 'email-setting-group.component.html', |
|||
animations: [collapse], |
|||
}) |
|||
export class EmailSettingGroupComponent implements OnInit { |
|||
form: FormGroup; |
|||
|
|||
saving = false; |
|||
|
|||
constructor( |
|||
private emailSettingsService: EmailSettingsService, |
|||
private fb: FormBuilder, |
|||
private toasterService: ToasterService, |
|||
) {} |
|||
|
|||
ngOnInit() { |
|||
this.getData(); |
|||
} |
|||
|
|||
private getData() { |
|||
this.emailSettingsService.get().subscribe(res => { |
|||
this.buildForm(res); |
|||
}); |
|||
} |
|||
|
|||
private buildForm(emailSettings: EmailSettingsDto) { |
|||
this.form = this.fb.group({ |
|||
defaultFromDisplayName: [emailSettings.defaultFromDisplayName, [Validators.required]], |
|||
defaultFromAddress: [emailSettings.defaultFromAddress, [Validators.required]], |
|||
smtpHost: [emailSettings.smtpHost], |
|||
smtpPort: [emailSettings.smtpPort, [Validators.required]], |
|||
smtpEnableSsl: [emailSettings.smtpEnableSsl], |
|||
smtpUseDefaultCredentials: [emailSettings.smtpUseDefaultCredentials], |
|||
smtpDomain: [emailSettings.smtpDomain], |
|||
smtpUserName: [emailSettings.smtpUserName], |
|||
smtpPassword: [emailSettings.smtpPassword], |
|||
}); |
|||
} |
|||
|
|||
submit() { |
|||
if (this.saving || this.form.invalid) return; |
|||
|
|||
this.saving = true; |
|||
this.emailSettingsService |
|||
.update(this.form.value) |
|||
.pipe(finalize(() => (this.saving = false))) |
|||
.subscribe(() => { |
|||
this.toasterService.success('AbpSettingManagement::SuccessfullySaved'); |
|||
this.getData(); |
|||
}); |
|||
} |
|||
} |
|||
@ -1 +1,2 @@ |
|||
export * from './route-names'; |
|||
export * from './setting-tab-names'; |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
export const enum eSettingManamagementSettingTabNames { |
|||
EmailSettingGroup = 'AbpSettingManagement::Menu:Emailing', |
|||
} |
|||
@ -1 +1,2 @@ |
|||
export * from './route.provider'; |
|||
export * from './setting-tab.provider'; |
|||
|
|||
@ -0,0 +1,26 @@ |
|||
import { SettingTabsService } from '@abp/ng.core'; |
|||
import { APP_INITIALIZER } from '@angular/core'; |
|||
import { EmailSettingGroupComponent } from '../components/email-setting-group/email-setting-group.component'; |
|||
import { eSettingManamagementSettingTabNames } from '../enums/setting-tab-names'; |
|||
|
|||
export const SETTING_MANAGEMENT_SETTING_TAB_PROVIDERS = [ |
|||
{ |
|||
provide: APP_INITIALIZER, |
|||
useFactory: configureSettingTabs, |
|||
deps: [SettingTabsService], |
|||
multi: true, |
|||
}, |
|||
]; |
|||
|
|||
export function configureSettingTabs(settingTabs: SettingTabsService) { |
|||
return () => { |
|||
settingTabs.add([ |
|||
{ |
|||
name: eSettingManamagementSettingTabNames.EmailSettingGroup, |
|||
order: 1, |
|||
requiredPolicy: 'SettingManagement.Emailing', |
|||
component: EmailSettingGroupComponent, |
|||
}, |
|||
]); |
|||
}; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
# Proxy Generation Output |
|||
|
|||
This directory includes the output of the latest proxy generation. |
|||
The files and folders in it will be overwritten when proxy generation is run again. |
|||
Therefore, please do not place your own content in this folder. |
|||
|
|||
In addition, `generate-proxy.json` works like a lock file. |
|||
It includes information used by the proxy generator, so please do not delete or modify it. |
|||
|
|||
Finally, the name of the files and folders should not be changed for two reasons: |
|||
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content. |
|||
- ABP Suite generates files which include imports from this folder. |
|||
|
|||
> **Important Notice:** If you are building a module and are planning to publish to npm, |
|||
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case, |
|||
> please make sure you export files directly and not from barrel exports. In other words, |
|||
> do not include index.ts exports in your public-api.ts exports. |
|||
@ -0,0 +1,27 @@ |
|||
import type { EmailSettingsDto, UpdateEmailSettingsDto } from './models'; |
|||
import { RestService } from '@abp/ng.core'; |
|||
import { Injectable } from '@angular/core'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root', |
|||
}) |
|||
export class EmailSettingsService { |
|||
apiName = 'SettingManagement'; |
|||
|
|||
get = () => |
|||
this.restService.request<any, EmailSettingsDto>({ |
|||
method: 'GET', |
|||
url: '/api/setting-management/emailing', |
|||
}, |
|||
{ apiName: this.apiName }); |
|||
|
|||
update = (input: UpdateEmailSettingsDto) => |
|||
this.restService.request<any, void>({ |
|||
method: 'POST', |
|||
url: '/api/setting-management/emailing', |
|||
body: input, |
|||
}, |
|||
{ apiName: this.apiName }); |
|||
|
|||
constructor(private restService: RestService) {} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,2 @@ |
|||
export * from './email-settings.service'; |
|||
export * from './models'; |
|||
@ -0,0 +1,24 @@ |
|||
|
|||
export interface EmailSettingsDto { |
|||
smtpHost?: string; |
|||
smtpPort: number; |
|||
smtpUserName?: string; |
|||
smtpPassword?: string; |
|||
smtpDomain?: string; |
|||
smtpEnableSsl: boolean; |
|||
smtpUseDefaultCredentials: boolean; |
|||
defaultFromAddress?: string; |
|||
defaultFromDisplayName?: string; |
|||
} |
|||
|
|||
export interface UpdateEmailSettingsDto { |
|||
smtpHost?: string; |
|||
smtpPort: number; |
|||
smtpUserName?: string; |
|||
smtpPassword?: string; |
|||
smtpDomain?: string; |
|||
smtpEnableSsl: boolean; |
|||
smtpUseDefaultCredentials: boolean; |
|||
defaultFromAddress: string; |
|||
defaultFromDisplayName: string; |
|||
} |
|||
@ -1,3 +1,5 @@ |
|||
export * from './enums'; |
|||
export * from './providers'; |
|||
export * from './setting-management-config.module'; |
|||
export * from './proxy'; |
|||
export * from './components/email-setting-group/email-setting-group.component'; |
|||
|
|||
@ -1,12 +1,20 @@ |
|||
import { ModuleWithProviders, NgModule } from '@angular/core'; |
|||
import { CoreModule } from '@abp/ng.core'; |
|||
import { EmailSettingGroupComponent } from './components/email-setting-group/email-setting-group.component'; |
|||
import { SETTING_MANAGEMENT_ROUTE_PROVIDERS } from './providers/route.provider'; |
|||
import { SETTING_MANAGEMENT_SETTING_TAB_PROVIDERS } from './providers/setting-tab.provider'; |
|||
import { NgxValidateCoreModule } from '@ngx-validate/core'; |
|||
|
|||
@NgModule() |
|||
@NgModule({ |
|||
imports: [CoreModule, NgxValidateCoreModule], |
|||
declarations: [EmailSettingGroupComponent], |
|||
exports: [EmailSettingGroupComponent], |
|||
}) |
|||
export class SettingManagementConfigModule { |
|||
static forRoot(): ModuleWithProviders<SettingManagementConfigModule> { |
|||
return { |
|||
ngModule: SettingManagementConfigModule, |
|||
providers: [SETTING_MANAGEMENT_ROUTE_PROVIDERS], |
|||
providers: [SETTING_MANAGEMENT_ROUTE_PROVIDERS, SETTING_MANAGEMENT_SETTING_TAB_PROVIDERS], |
|||
}; |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue