diff --git a/Abp.Zero.sln b/Abp.Zero.sln index dc60f43d..ada053e2 100644 --- a/Abp.Zero.sln +++ b/Abp.Zero.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26228.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{364398C2-9B6A-40B3-9845-23ABFB8D12AB}" EndProject @@ -41,6 +41,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abp.Zero.Owin", "src\Abp.Ze EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abp.Zero.AspNetCore", "src\Abp.Zero.AspNetCore\Abp.Zero.AspNetCore.csproj", "{8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.ZeroCore", "src\Abp.ZeroCore\Abp.ZeroCore.csproj", "{C5F4218F-2637-4629-B43B-7728D28CB19E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.Zero.Common", "src\Abp.Zero.Common\Abp.Zero.Common.csproj", "{EABE7BBB-A837-4607-9B0D-BE41E4A0F81F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abp.ZeroCore.EntityFrameworkCore", "src\Abp.ZeroCore.EntityFrameworkCore\Abp.ZeroCore.EntityFrameworkCore.csproj", "{39A3807E-4309-43D7-98C1-246547108006}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ZeroCore", "ZeroCore", "{A6CB86B2-8C58-42D7-917A-F1DEB29C121A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Zero", "Zero", "{22551F30-C7C5-40D5-AE24-3D675ABC9B46}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Zero", "Zero", "{4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{12068527-E860-4AC1-88C3-62B435E4BBE5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -103,24 +117,43 @@ Global {8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44}.Debug|Any CPU.Build.0 = Debug|Any CPU {8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44}.Release|Any CPU.ActiveCfg = Release|Any CPU {8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44}.Release|Any CPU.Build.0 = Release|Any CPU + {C5F4218F-2637-4629-B43B-7728D28CB19E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5F4218F-2637-4629-B43B-7728D28CB19E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5F4218F-2637-4629-B43B-7728D28CB19E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5F4218F-2637-4629-B43B-7728D28CB19E}.Release|Any CPU.Build.0 = Release|Any CPU + {EABE7BBB-A837-4607-9B0D-BE41E4A0F81F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EABE7BBB-A837-4607-9B0D-BE41E4A0F81F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EABE7BBB-A837-4607-9B0D-BE41E4A0F81F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EABE7BBB-A837-4607-9B0D-BE41E4A0F81F}.Release|Any CPU.Build.0 = Release|Any CPU + {39A3807E-4309-43D7-98C1-246547108006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39A3807E-4309-43D7-98C1-246547108006}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39A3807E-4309-43D7-98C1-246547108006}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39A3807E-4309-43D7-98C1-246547108006}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {E06353A0-8403-44A0-8D57-200D55E2F2C5} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {C28F608B-F34E-411C-8D88-C5D566141735} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {A57787C3-AD9A-40A8-B955-EDA596397429} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {C64C24CD-74CE-47A2-A03C-06F4F78413F1} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {667801A0-99A5-414A-8685-A035408BF413} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {97670397-EDF9-4968-9A25-0EB71C68D5F8} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {FA3E87C5-7273-461C-AE9F-67B416E07949} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {41F88D4F-FAB9-4D0A-85E5-C71F8B63B973} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {83108E5E-FD31-4762-8854-D795A8FB482F} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {76E2CCE7-A61F-4700-9054-9923645E41CC} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {9223F076-ECE7-4381-9BBF-F3E33073B91A} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {25F73233-3922-4BC0-AC1C-2042613DEC5E} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} - {81D2351F-6CA1-4976-86EB-63946EBD0A36} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} - {8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} + {E06353A0-8403-44A0-8D57-200D55E2F2C5} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {C28F608B-F34E-411C-8D88-C5D566141735} = {12068527-E860-4AC1-88C3-62B435E4BBE5} + {A57787C3-AD9A-40A8-B955-EDA596397429} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {C64C24CD-74CE-47A2-A03C-06F4F78413F1} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {667801A0-99A5-414A-8685-A035408BF413} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {97670397-EDF9-4968-9A25-0EB71C68D5F8} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {FA3E87C5-7273-461C-AE9F-67B416E07949} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {41F88D4F-FAB9-4D0A-85E5-C71F8B63B973} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {83108E5E-FD31-4762-8854-D795A8FB482F} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {76E2CCE7-A61F-4700-9054-9923645E41CC} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {9223F076-ECE7-4381-9BBF-F3E33073B91A} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {25F73233-3922-4BC0-AC1C-2042613DEC5E} = {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} + {81D2351F-6CA1-4976-86EB-63946EBD0A36} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {8EE2ACFF-CB44-4A7E-8FE7-5709AD245F44} = {22551F30-C7C5-40D5-AE24-3D675ABC9B46} + {C5F4218F-2637-4629-B43B-7728D28CB19E} = {A6CB86B2-8C58-42D7-917A-F1DEB29C121A} + {EABE7BBB-A837-4607-9B0D-BE41E4A0F81F} = {12068527-E860-4AC1-88C3-62B435E4BBE5} + {39A3807E-4309-43D7-98C1-246547108006} = {A6CB86B2-8C58-42D7-917A-F1DEB29C121A} + {A6CB86B2-8C58-42D7-917A-F1DEB29C121A} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} + {22551F30-C7C5-40D5-AE24-3D675ABC9B46} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} + {4A2AFB7B-F4F7-4E47-A8A3-F1F848C9F1B9} = {E1AD01DD-1A0D-40DF-AFBE-F2AA3B4160AB} + {12068527-E860-4AC1-88C3-62B435E4BBE5} = {364398C2-9B6A-40B3-9845-23ABFB8D12AB} EndGlobalSection EndGlobal diff --git a/appveyor.yml b/appveyor.yml index 50adeba1..af2aae6f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ configuration: Release -version: 1.5.1-beta{build} -image: Visual Studio 2017 RC +version: 2.0.0-beta{build} +image: Visual Studio 2017 nuget: disable_publish_on_pr: true @@ -28,7 +28,7 @@ pull_requests: test: assemblies: - test\Abp.Zero.SampleApp.Tests\bin\Release\net452\Abp.Zero.SampleApp.Tests.dll - - test\Abp.Zero.SampleApp.EntityFrameworkCore.Tests\bin\Release\net461\Abp.Zero.SampleApp.EntityFrameworkCore.Tests.dll + # - test\Abp.Zero.SampleApp.EntityFrameworkCore.Tests\bin\Release\net461\Abp.Zero.SampleApp.EntityFrameworkCore.Tests.dll # - test\Abp.Zero.SampleApp.NHibernateTests\bin\Release\net452\Abp.Zero.SampleApp.NHibernateTests.dll artifacts: diff --git a/common.props b/common.props index 8b053288..2f072b41 100644 --- a/common.props +++ b/common.props @@ -1,6 +1,6 @@ - 1.5.1 + 2.0.0 $(NoWarn);CS1591 http://www.aspnetboilerplate.com/images/abp_nupkg.png http://www.aspnetboilerplate.com/ @@ -8,4 +8,8 @@ git https://github.com/aspnetboilerplate/aspnetboilerplate + + + NET452 + \ No newline at end of file diff --git a/nupkg/pack.bat b/nupkg/pack.bat deleted file mode 100644 index dbd246c5..00000000 --- a/nupkg/pack.bat +++ /dev/null @@ -1,14 +0,0 @@ -REM "..\tools\gitlink\GitLink.exe" ..\ -u https://github.com/aspnetboilerplate/module-zero -c release - -@ECHO OFF -SET /P VERSION_SUFFIX=Please enter version-suffix (can be left empty): - -dotnet "pack" "..\src\Abp.Zero" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.Owin" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.AspNetCore" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.Ldap" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.EntityFramework" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.EntityFrameworkCore" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" -dotnet "pack" "..\src\Abp.Zero.NHibernate" -c "Release" -o "." --version-suffix "%VERSION_SUFFIX%" - -pause diff --git a/nupkg/pack.ps1 b/nupkg/pack.ps1 index 4fc41034..3cf10e0b 100644 --- a/nupkg/pack.ps1 +++ b/nupkg/pack.ps1 @@ -5,17 +5,21 @@ $srcPath = Join-Path $slnPath "src" # List of projects $projects = ( + "Abp.Zero.Common", + "Abp.Zero.Ldap", "Abp.Zero", "Abp.Zero.Owin", "Abp.Zero.AspNetCore", - "Abp.Zero.Ldap", "Abp.Zero.EntityFramework", "Abp.Zero.EntityFrameworkCore", - "Abp.Zero.NHibernate" + "Abp.Zero.NHibernate", + "Abp.ZeroCore", + "Abp.ZeroCore.EntityFrameworkCore" ) # Rebuild solution Set-Location $slnPath +& dotnet restore & dotnet msbuild /t:Rebuild /p:Configuration=Release # Copy all nuget packages to the pack folder diff --git a/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.1.5.0.nupkg b/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.1.5.0.nupkg deleted file mode 100644 index d10d27d5..00000000 Binary files a/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.1.5.0.nupkg and /dev/null differ diff --git a/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.csproj b/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.csproj index ba9b2589..f2ad096a 100644 --- a/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.csproj +++ b/src/Abp.Zero.AspNetCore/Abp.Zero.AspNetCore.csproj @@ -3,7 +3,7 @@ - net461 + net46 true Abp.Zero.AspNetCore Abp.Zero.AspNetCore @@ -14,21 +14,18 @@ - - lib/net461/ + + lib/net46/ true - - - - + diff --git a/src/Abp.Zero.Common/Abp.Zero.Common.csproj b/src/Abp.Zero.Common/Abp.Zero.Common.csproj new file mode 100644 index 00000000..9b7e14bb --- /dev/null +++ b/src/Abp.Zero.Common/Abp.Zero.Common.csproj @@ -0,0 +1,50 @@ + + + + + + net46;netstandard1.6 + true + Abp.Zero.Common + Abp.Zero.Common + asp.net;asp.net mvc;boilerplate;application framework;web framework;framework;domain driven design;multitenancy;user management;role management;identity;aspnet core + false + false + false + Abp + + + + + + + + + + + + + + + + + + + + + + + lib/net46/ + true + + + lib/netstandard1.6/ + true + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero/Application/Editions/AbpEditionManager.cs b/src/Abp.Zero.Common/Application/Editions/AbpEditionManager.cs similarity index 96% rename from src/Abp.Zero/Application/Editions/AbpEditionManager.cs rename to src/Abp.Zero.Common/Application/Editions/AbpEditionManager.cs index 462dd7b0..5d22c608 100644 --- a/src/Abp.Zero/Application/Editions/AbpEditionManager.cs +++ b/src/Abp.Zero.Common/Application/Editions/AbpEditionManager.cs @@ -1,92 +1,92 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Abp.Application.Features; -using Abp.Collections.Extensions; -using Abp.Domain.Repositories; -using Abp.Domain.Services; -using Abp.Runtime.Caching; - -namespace Abp.Application.Editions -{ - public abstract class AbpEditionManager : IDomainService - { - private readonly IAbpZeroFeatureValueStore _featureValueStore; - - public IQueryable Editions => EditionRepository.GetAll(); - - public ICacheManager CacheManager { get; set; } - - public IFeatureManager FeatureManager { get; set; } - - protected IRepository EditionRepository { get; set; } - - protected AbpEditionManager( - IRepository editionRepository, - IAbpZeroFeatureValueStore featureValueStore) - { - _featureValueStore = featureValueStore; - EditionRepository = editionRepository; - } - - public virtual Task GetFeatureValueOrNullAsync(int editionId, string featureName) - { - return _featureValueStore.GetEditionValueOrNullAsync(editionId, featureName); - } - - public virtual Task SetFeatureValueAsync(int editionId, string featureName, string value) - { - return _featureValueStore.SetEditionFeatureValueAsync(editionId, featureName, value); - } - - public virtual async Task> GetFeatureValuesAsync(int editionId) - { - var values = new List(); - - foreach (var feature in FeatureManager.GetAll()) - { - values.Add(new NameValue(feature.Name, await GetFeatureValueOrNullAsync(editionId, feature.Name) ?? feature.DefaultValue)); - } - - return values; - } - - public virtual async Task SetFeatureValuesAsync(int editionId, params NameValue[] values) - { - if (values.IsNullOrEmpty()) - { - return; - } - - foreach (var value in values) - { - await SetFeatureValueAsync(editionId, value.Name, value.Value); - } - } - - public virtual Task CreateAsync(Edition edition) - { - return EditionRepository.InsertAsync(edition); - } - - public virtual Task FindByNameAsync(string name) - { - return EditionRepository.FirstOrDefaultAsync(edition => edition.Name == name); - } - - public virtual Task FindByIdAsync(int id) - { - return EditionRepository.FirstOrDefaultAsync(id); - } - - public virtual Task GetByIdAsync(int id) - { - return EditionRepository.GetAsync(id); - } - - public virtual Task DeleteAsync(Edition edition) - { - return EditionRepository.DeleteAsync(edition); - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Abp.Application.Features; +using Abp.Collections.Extensions; +using Abp.Domain.Repositories; +using Abp.Domain.Services; +using Abp.Runtime.Caching; + +namespace Abp.Application.Editions +{ + public abstract class AbpEditionManager : IDomainService + { + private readonly IAbpZeroFeatureValueStore _featureValueStore; + + public IQueryable Editions => EditionRepository.GetAll(); + + public ICacheManager CacheManager { get; set; } + + public IFeatureManager FeatureManager { get; set; } + + protected IRepository EditionRepository { get; set; } + + protected AbpEditionManager( + IRepository editionRepository, + IAbpZeroFeatureValueStore featureValueStore) + { + _featureValueStore = featureValueStore; + EditionRepository = editionRepository; + } + + public virtual Task GetFeatureValueOrNullAsync(int editionId, string featureName) + { + return _featureValueStore.GetEditionValueOrNullAsync(editionId, featureName); + } + + public virtual Task SetFeatureValueAsync(int editionId, string featureName, string value) + { + return _featureValueStore.SetEditionFeatureValueAsync(editionId, featureName, value); + } + + public virtual async Task> GetFeatureValuesAsync(int editionId) + { + var values = new List(); + + foreach (var feature in FeatureManager.GetAll()) + { + values.Add(new NameValue(feature.Name, await GetFeatureValueOrNullAsync(editionId, feature.Name) ?? feature.DefaultValue)); + } + + return values; + } + + public virtual async Task SetFeatureValuesAsync(int editionId, params NameValue[] values) + { + if (values.IsNullOrEmpty()) + { + return; + } + + foreach (var value in values) + { + await SetFeatureValueAsync(editionId, value.Name, value.Value); + } + } + + public virtual Task CreateAsync(Edition edition) + { + return EditionRepository.InsertAsync(edition); + } + + public virtual Task FindByNameAsync(string name) + { + return EditionRepository.FirstOrDefaultAsync(edition => edition.Name == name); + } + + public virtual Task FindByIdAsync(int id) + { + return EditionRepository.FirstOrDefaultAsync(id); + } + + public virtual Task GetByIdAsync(int id) + { + return EditionRepository.GetAsync(id); + } + + public virtual Task DeleteAsync(Edition edition) + { + return EditionRepository.DeleteAsync(edition); + } + } +} diff --git a/src/Abp.Zero/Application/Editions/Edition.cs b/src/Abp.Zero.Common/Application/Editions/Edition.cs similarity index 96% rename from src/Abp.Zero/Application/Editions/Edition.cs rename to src/Abp.Zero.Common/Application/Editions/Edition.cs index 9ac9dc43..56cc1f92 100644 --- a/src/Abp.Zero/Application/Editions/Edition.cs +++ b/src/Abp.Zero.Common/Application/Editions/Edition.cs @@ -1,51 +1,51 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities.Auditing; -using Abp.MultiTenancy; - -namespace Abp.Application.Editions -{ - /// - /// Represents an edition of the application. - /// - [Table("AbpEditions")] - [MultiTenancySide(MultiTenancySides.Host)] - public class Edition : FullAuditedEntity - { - /// - /// Maximum length of the property. - /// - public const int MaxNameLength = 32; - - /// - /// Maximum length of the property. - /// - public const int MaxDisplayNameLength = 64; - - /// - /// Unique name of this edition. - /// - [Required] - [StringLength(MaxNameLength)] - public virtual string Name { get; set; } - - /// - /// Display name of this edition. - /// - [Required] - [StringLength(MaxDisplayNameLength)] - public virtual string DisplayName { get; set; } - - public Edition() - { - Name = Guid.NewGuid().ToString("N"); - } - - public Edition(string displayName) - : this() - { - DisplayName = displayName; - } - } +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities.Auditing; +using Abp.MultiTenancy; + +namespace Abp.Application.Editions +{ + /// + /// Represents an edition of the application. + /// + [Table("AbpEditions")] + [MultiTenancySide(MultiTenancySides.Host)] + public class Edition : FullAuditedEntity + { + /// + /// Maximum length of the property. + /// + public const int MaxNameLength = 32; + + /// + /// Maximum length of the property. + /// + public const int MaxDisplayNameLength = 64; + + /// + /// Unique name of this edition. + /// + [Required] + [StringLength(MaxNameLength)] + public virtual string Name { get; set; } + + /// + /// Display name of this edition. + /// + [Required] + [StringLength(MaxDisplayNameLength)] + public virtual string DisplayName { get; set; } + + public Edition() + { + Name = Guid.NewGuid().ToString("N"); + } + + public Edition(string displayName) + : this() + { + DisplayName = displayName; + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Application/Editions/EditionfeatureCacheItem.cs b/src/Abp.Zero.Common/Application/Editions/EditionfeatureCacheItem.cs similarity index 96% rename from src/Abp.Zero/Application/Editions/EditionfeatureCacheItem.cs rename to src/Abp.Zero.Common/Application/Editions/EditionfeatureCacheItem.cs index ec9bb4f2..395d8dba 100644 --- a/src/Abp.Zero/Application/Editions/EditionfeatureCacheItem.cs +++ b/src/Abp.Zero.Common/Application/Editions/EditionfeatureCacheItem.cs @@ -1,18 +1,18 @@ -using System; -using System.Collections.Generic; - -namespace Abp.Application.Editions -{ - [Serializable] - public class EditionfeatureCacheItem - { - public const string CacheStoreName = "AbpZeroEditionFeatures"; - - public IDictionary FeatureValues { get; set; } - - public EditionfeatureCacheItem() - { - FeatureValues = new Dictionary(); - } - } +using System; +using System.Collections.Generic; + +namespace Abp.Application.Editions +{ + [Serializable] + public class EditionfeatureCacheItem + { + public const string CacheStoreName = "AbpZeroEditionFeatures"; + + public IDictionary FeatureValues { get; set; } + + public EditionfeatureCacheItem() + { + FeatureValues = new Dictionary(); + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Application/Features/AbpFeatureValueStore.cs b/src/Abp.Zero.Common/Application/Features/AbpFeatureValueStore.cs similarity index 96% rename from src/Abp.Zero/Application/Features/AbpFeatureValueStore.cs rename to src/Abp.Zero.Common/Application/Features/AbpFeatureValueStore.cs index 160bb841..cc4ff400 100644 --- a/src/Abp.Zero/Application/Features/AbpFeatureValueStore.cs +++ b/src/Abp.Zero.Common/Application/Features/AbpFeatureValueStore.cs @@ -1,190 +1,190 @@ -using System.Threading.Tasks; -using Abp.Application.Editions; -using Abp.Authorization.Users; -using Abp.Collections.Extensions; -using Abp.Dependency; -using Abp.Domain.Repositories; -using Abp.Domain.Uow; -using Abp.Events.Bus.Entities; -using Abp.Events.Bus.Handlers; -using Abp.MultiTenancy; -using Abp.Runtime.Caching; - -namespace Abp.Application.Features -{ - /// - /// Implements . - /// - public abstract class AbpFeatureValueStore : - IAbpZeroFeatureValueStore, - ITransientDependency, - IEventHandler>, - IEventHandler> - - where TTenant : AbpTenant - where TUser : AbpUser - { - private readonly ICacheManager _cacheManager; - private readonly IRepository _tenantFeatureRepository; - private readonly IRepository _tenantRepository; - private readonly IRepository _editionFeatureRepository; - private readonly IFeatureManager _featureManager; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - /// - /// Initializes a new instance of the class. - /// - protected AbpFeatureValueStore( - ICacheManager cacheManager, - IRepository tenantFeatureRepository, - IRepository tenantRepository, - IRepository editionFeatureRepository, - IFeatureManager featureManager, - IUnitOfWorkManager unitOfWorkManager) - { - _cacheManager = cacheManager; - _tenantFeatureRepository = tenantFeatureRepository; - _tenantRepository = tenantRepository; - _editionFeatureRepository = editionFeatureRepository; - _featureManager = featureManager; - _unitOfWorkManager = unitOfWorkManager; - } - - /// - public virtual Task GetValueOrNullAsync(int tenantId, Feature feature) - { - return GetValueOrNullAsync(tenantId, feature.Name); - } - - public virtual async Task GetEditionValueOrNullAsync(int editionId, string featureName) - { - var cacheItem = await GetEditionFeatureCacheItemAsync(editionId); - return cacheItem.FeatureValues.GetOrDefault(featureName); - } - - public async Task GetValueOrNullAsync(int tenantId, string featureName) - { - var cacheItem = await GetTenantFeatureCacheItemAsync(tenantId); - var value = cacheItem.FeatureValues.GetOrDefault(featureName); - if (value != null) - { - return value; - } - - if (cacheItem.EditionId.HasValue) - { - value = await GetEditionValueOrNullAsync(cacheItem.EditionId.Value, featureName); - if (value != null) - { - return value; - } - } - - return null; - } - - [UnitOfWork] - public virtual async Task SetEditionFeatureValueAsync(int editionId, string featureName, string value) - { - if (await GetEditionValueOrNullAsync(editionId, featureName) == value) - { - return; - } - - var currentFeature = await _editionFeatureRepository.FirstOrDefaultAsync(f => f.EditionId == editionId && f.Name == featureName); - - var feature = _featureManager.GetOrNull(featureName); - if (feature == null || feature.DefaultValue == value) - { - if (currentFeature != null) - { - await _editionFeatureRepository.DeleteAsync(currentFeature); - } - - return; - } - - if (currentFeature == null) - { - await _editionFeatureRepository.InsertAsync(new EditionFeatureSetting(editionId, featureName, value)); - } - else - { - currentFeature.Value = value; - } - } - - protected async Task GetTenantFeatureCacheItemAsync(int tenantId) - { - return await _cacheManager.GetTenantFeatureCache().GetAsync(tenantId, async () => - { - TTenant tenant; - using (var uow = _unitOfWorkManager.Begin()) - { - using (_unitOfWorkManager.Current.SetTenantId(null)) - { - tenant = await _tenantRepository.GetAsync(tenantId); - - await uow.CompleteAsync(); - } - } - - var newCacheItem = new TenantFeatureCacheItem { EditionId = tenant.EditionId }; - - using (var uow = _unitOfWorkManager.Begin()) - { - using (_unitOfWorkManager.Current.SetTenantId(tenantId)) - { - var featureSettings = await _tenantFeatureRepository.GetAllListAsync(); - foreach (var featureSetting in featureSettings) - { - newCacheItem.FeatureValues[featureSetting.Name] = featureSetting.Value; - } - - await uow.CompleteAsync(); - } - } - - return newCacheItem; - }); - } - - protected virtual async Task GetEditionFeatureCacheItemAsync(int editionId) - { - return await _cacheManager - .GetEditionFeatureCache() - .GetAsync( - editionId, - async () => await CreateEditionFeatureCacheItem(editionId) - ); - } - - protected virtual async Task CreateEditionFeatureCacheItem(int editionId) - { - var newCacheItem = new EditionfeatureCacheItem(); - - var featureSettings = await _editionFeatureRepository.GetAllListAsync(f => f.EditionId == editionId); - foreach (var featureSetting in featureSettings) - { - newCacheItem.FeatureValues[featureSetting.Name] = featureSetting.Value; - } - - return newCacheItem; - } - - public virtual void HandleEvent(EntityChangedEventData eventData) - { - _cacheManager.GetEditionFeatureCache().Remove(eventData.Entity.EditionId); - } - - public virtual void HandleEvent(EntityChangedEventData eventData) - { - if (eventData.Entity.IsTransient()) - { - return; - } - - _cacheManager.GetEditionFeatureCache().Remove(eventData.Entity.Id); - } - } +using System.Threading.Tasks; +using Abp.Application.Editions; +using Abp.Authorization.Users; +using Abp.Collections.Extensions; +using Abp.Dependency; +using Abp.Domain.Repositories; +using Abp.Domain.Uow; +using Abp.Events.Bus.Entities; +using Abp.Events.Bus.Handlers; +using Abp.MultiTenancy; +using Abp.Runtime.Caching; + +namespace Abp.Application.Features +{ + /// + /// Implements . + /// + public abstract class AbpFeatureValueStore : + IAbpZeroFeatureValueStore, + ITransientDependency, + IEventHandler>, + IEventHandler> + + where TTenant : AbpTenant + where TUser : AbpUserBase + { + private readonly ICacheManager _cacheManager; + private readonly IRepository _tenantFeatureRepository; + private readonly IRepository _tenantRepository; + private readonly IRepository _editionFeatureRepository; + private readonly IFeatureManager _featureManager; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + /// + /// Initializes a new instance of the class. + /// + protected AbpFeatureValueStore( + ICacheManager cacheManager, + IRepository tenantFeatureRepository, + IRepository tenantRepository, + IRepository editionFeatureRepository, + IFeatureManager featureManager, + IUnitOfWorkManager unitOfWorkManager) + { + _cacheManager = cacheManager; + _tenantFeatureRepository = tenantFeatureRepository; + _tenantRepository = tenantRepository; + _editionFeatureRepository = editionFeatureRepository; + _featureManager = featureManager; + _unitOfWorkManager = unitOfWorkManager; + } + + /// + public virtual Task GetValueOrNullAsync(int tenantId, Feature feature) + { + return GetValueOrNullAsync(tenantId, feature.Name); + } + + public virtual async Task GetEditionValueOrNullAsync(int editionId, string featureName) + { + var cacheItem = await GetEditionFeatureCacheItemAsync(editionId); + return cacheItem.FeatureValues.GetOrDefault(featureName); + } + + public async Task GetValueOrNullAsync(int tenantId, string featureName) + { + var cacheItem = await GetTenantFeatureCacheItemAsync(tenantId); + var value = cacheItem.FeatureValues.GetOrDefault(featureName); + if (value != null) + { + return value; + } + + if (cacheItem.EditionId.HasValue) + { + value = await GetEditionValueOrNullAsync(cacheItem.EditionId.Value, featureName); + if (value != null) + { + return value; + } + } + + return null; + } + + [UnitOfWork] + public virtual async Task SetEditionFeatureValueAsync(int editionId, string featureName, string value) + { + if (await GetEditionValueOrNullAsync(editionId, featureName) == value) + { + return; + } + + var currentFeature = await _editionFeatureRepository.FirstOrDefaultAsync(f => f.EditionId == editionId && f.Name == featureName); + + var feature = _featureManager.GetOrNull(featureName); + if (feature == null || feature.DefaultValue == value) + { + if (currentFeature != null) + { + await _editionFeatureRepository.DeleteAsync(currentFeature); + } + + return; + } + + if (currentFeature == null) + { + await _editionFeatureRepository.InsertAsync(new EditionFeatureSetting(editionId, featureName, value)); + } + else + { + currentFeature.Value = value; + } + } + + protected async Task GetTenantFeatureCacheItemAsync(int tenantId) + { + return await _cacheManager.GetTenantFeatureCache().GetAsync(tenantId, async () => + { + TTenant tenant; + using (var uow = _unitOfWorkManager.Begin()) + { + using (_unitOfWorkManager.Current.SetTenantId(null)) + { + tenant = await _tenantRepository.GetAsync(tenantId); + + await uow.CompleteAsync(); + } + } + + var newCacheItem = new TenantFeatureCacheItem { EditionId = tenant.EditionId }; + + using (var uow = _unitOfWorkManager.Begin()) + { + using (_unitOfWorkManager.Current.SetTenantId(tenantId)) + { + var featureSettings = await _tenantFeatureRepository.GetAllListAsync(); + foreach (var featureSetting in featureSettings) + { + newCacheItem.FeatureValues[featureSetting.Name] = featureSetting.Value; + } + + await uow.CompleteAsync(); + } + } + + return newCacheItem; + }); + } + + protected virtual async Task GetEditionFeatureCacheItemAsync(int editionId) + { + return await _cacheManager + .GetEditionFeatureCache() + .GetAsync( + editionId, + async () => await CreateEditionFeatureCacheItem(editionId) + ); + } + + protected virtual async Task CreateEditionFeatureCacheItem(int editionId) + { + var newCacheItem = new EditionfeatureCacheItem(); + + var featureSettings = await _editionFeatureRepository.GetAllListAsync(f => f.EditionId == editionId); + foreach (var featureSetting in featureSettings) + { + newCacheItem.FeatureValues[featureSetting.Name] = featureSetting.Value; + } + + return newCacheItem; + } + + public virtual void HandleEvent(EntityChangedEventData eventData) + { + _cacheManager.GetEditionFeatureCache().Remove(eventData.Entity.EditionId); + } + + public virtual void HandleEvent(EntityChangedEventData eventData) + { + if (eventData.Entity.IsTransient()) + { + return; + } + + _cacheManager.GetEditionFeatureCache().Remove(eventData.Entity.Id); + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Application/Features/EditionFeatureSetting.cs b/src/Abp.Zero.Common/Application/Features/EditionFeatureSetting.cs similarity index 96% rename from src/Abp.Zero/Application/Features/EditionFeatureSetting.cs rename to src/Abp.Zero.Common/Application/Features/EditionFeatureSetting.cs index 7023c844..3d02c894 100644 --- a/src/Abp.Zero/Application/Features/EditionFeatureSetting.cs +++ b/src/Abp.Zero.Common/Application/Features/EditionFeatureSetting.cs @@ -1,48 +1,48 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Application.Editions; - -namespace Abp.Application.Features -{ - /// - /// Feature setting for an . - /// - public class EditionFeatureSetting : FeatureSetting - { - /// - /// Gets or sets the edition. - /// - /// - /// The edition. - /// - [ForeignKey("EditionId")] - public virtual Edition Edition { get; set; } - - /// - /// Gets or sets the edition Id. - /// - /// - /// The edition Id. - /// - public virtual int EditionId { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public EditionFeatureSetting() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// The edition Id. - /// Feature name. - /// Feature value. - public EditionFeatureSetting(int editionId, string name, string value) - :base(name, value) - { - EditionId = editionId; - } - } +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Application.Editions; + +namespace Abp.Application.Features +{ + /// + /// Feature setting for an . + /// + public class EditionFeatureSetting : FeatureSetting + { + /// + /// Gets or sets the edition. + /// + /// + /// The edition. + /// + [ForeignKey("EditionId")] + public virtual Edition Edition { get; set; } + + /// + /// Gets or sets the edition Id. + /// + /// + /// The edition Id. + /// + public virtual int EditionId { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public EditionFeatureSetting() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The edition Id. + /// Feature name. + /// Feature value. + public EditionFeatureSetting(int editionId, string name, string value) + :base(name, value) + { + EditionId = editionId; + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Application/Features/FeatureSetting.cs b/src/Abp.Zero.Common/Application/Features/FeatureSetting.cs similarity index 96% rename from src/Abp.Zero/Application/Features/FeatureSetting.cs rename to src/Abp.Zero.Common/Application/Features/FeatureSetting.cs index 19c4c89b..42eeee7b 100644 --- a/src/Abp.Zero/Application/Features/FeatureSetting.cs +++ b/src/Abp.Zero.Common/Application/Features/FeatureSetting.cs @@ -1,58 +1,58 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities.Auditing; -using Abp.MultiTenancy; - -namespace Abp.Application.Features -{ - /// - /// Base class for feature settings - /// - [Table("AbpFeatures")] - [MultiTenancySide(MultiTenancySides.Host)] - public abstract class FeatureSetting : CreationAuditedEntity - { - /// - /// Maximum length of the field. - /// - public const int MaxNameLength = 128; - - /// - /// Maximum length of the property. - /// - public const int MaxValueLength = 2000; - - /// - /// Feature name. - /// - [Required] - [MaxLength(MaxNameLength)] - public virtual string Name { get; set; } - - /// - /// Value. - /// - [Required(AllowEmptyStrings = true)] - [MaxLength(MaxValueLength)] - public virtual string Value { get; set; } - - /// - /// Initializes a new instance of the class. - /// - protected FeatureSetting() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// Feature name. - /// Feature value. - protected FeatureSetting(string name, string value) - { - Name = name; - Value = value; - } - } +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities.Auditing; +using Abp.MultiTenancy; + +namespace Abp.Application.Features +{ + /// + /// Base class for feature settings + /// + [Table("AbpFeatures")] + [MultiTenancySide(MultiTenancySides.Host)] + public abstract class FeatureSetting : CreationAuditedEntity + { + /// + /// Maximum length of the field. + /// + public const int MaxNameLength = 128; + + /// + /// Maximum length of the property. + /// + public const int MaxValueLength = 2000; + + /// + /// Feature name. + /// + [Required] + [MaxLength(MaxNameLength)] + public virtual string Name { get; set; } + + /// + /// Value. + /// + [Required(AllowEmptyStrings = true)] + [MaxLength(MaxValueLength)] + public virtual string Value { get; set; } + + /// + /// Initializes a new instance of the class. + /// + protected FeatureSetting() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// Feature name. + /// Feature value. + protected FeatureSetting(string name, string value) + { + Name = name; + Value = value; + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Application/Features/IAbpZeroFeatureValueStore.cs b/src/Abp.Zero.Common/Application/Features/IAbpZeroFeatureValueStore.cs similarity index 100% rename from src/Abp.Zero/Application/Features/IAbpZeroFeatureValueStore.cs rename to src/Abp.Zero.Common/Application/Features/IAbpZeroFeatureValueStore.cs diff --git a/src/Abp.Zero/Auditing/AuditLog.cs b/src/Abp.Zero.Common/Auditing/AuditLog.cs similarity index 97% rename from src/Abp.Zero/Auditing/AuditLog.cs rename to src/Abp.Zero.Common/Auditing/AuditLog.cs index a9c93323..9ff02052 100644 --- a/src/Abp.Zero/Auditing/AuditLog.cs +++ b/src/Abp.Zero.Common/Auditing/AuditLog.cs @@ -1,168 +1,168 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities; -using Abp.Extensions; - -namespace Abp.Auditing -{ - /// - /// Used to store audit logs. - /// - [Table("AbpAuditLogs")] - public class AuditLog : Entity, IMayHaveTenant - { - /// - /// Maximum length of property. - /// - public const int MaxServiceNameLength = 256; - - /// - /// Maximum length of property. - /// - public const int MaxMethodNameLength = 256; - - /// - /// Maximum length of property. - /// - public const int MaxParametersLength = 1024; - - /// - /// Maximum length of property. - /// - public const int MaxClientIpAddressLength = 64; - - /// - /// Maximum length of property. - /// - public const int MaxClientNameLength = 128; - - /// - /// Maximum length of property. - /// - public const int MaxBrowserInfoLength = 256; - - /// - /// Maximum length of property. - /// - public const int MaxExceptionLength = 2000; - - /// - /// Maximum length of property. - /// - public const int MaxCustomDataLength = 2000; - - /// - /// TenantId. - /// - public virtual int? TenantId { get; set; } - - /// - /// UserId. - /// - public virtual long? UserId { get; set; } - - /// - /// Service (class/interface) name. - /// - [MaxLength(MaxServiceNameLength)] - public virtual string ServiceName { get; set; } - - /// - /// Executed method name. - /// - [MaxLength(MaxMethodNameLength)] - public virtual string MethodName { get; set; } - - /// - /// Calling parameters. - /// - [MaxLength(MaxParametersLength)] - public virtual string Parameters { get; set; } - - /// - /// Start time of the method execution. - /// - public virtual DateTime ExecutionTime { get; set; } - - /// - /// Total duration of the method call as milliseconds. - /// - public virtual int ExecutionDuration { get; set; } - - /// - /// IP address of the client. - /// - [MaxLength(MaxClientIpAddressLength)] - public virtual string ClientIpAddress { get; set; } - - /// - /// Name (generally computer name) of the client. - /// - [MaxLength(MaxClientNameLength)] - public virtual string ClientName { get; set; } - - /// - /// Browser information if this method is called in a web request. - /// - [MaxLength(MaxBrowserInfoLength)] - public virtual string BrowserInfo { get; set; } - - /// - /// Exception object, if an exception occured during execution of the method. - /// - [MaxLength(MaxExceptionLength)] - public virtual string Exception { get; set; } - - /// - /// . - /// - public virtual long? ImpersonatorUserId { get; set; } - - /// - /// . - /// - public virtual int? ImpersonatorTenantId { get; set; } - - /// - /// . - /// - [MaxLength(MaxCustomDataLength)] - public virtual string CustomData { get; set; } - - /// - /// Creates a new CreateFromAuditInfo from given . - /// - /// Source object - /// The object that is created using - public static AuditLog CreateFromAuditInfo(AuditInfo auditInfo) - { - var exceptionMessage = auditInfo.Exception != null ? auditInfo.Exception.ToString() : null; - return new AuditLog - { - TenantId = auditInfo.TenantId, - UserId = auditInfo.UserId, - ServiceName = auditInfo.ServiceName.TruncateWithPostfix(MaxServiceNameLength), - MethodName = auditInfo.MethodName.TruncateWithPostfix(MaxMethodNameLength), - Parameters = auditInfo.Parameters.TruncateWithPostfix(MaxParametersLength), - ExecutionTime = auditInfo.ExecutionTime, - ExecutionDuration = auditInfo.ExecutionDuration, - ClientIpAddress = auditInfo.ClientIpAddress.TruncateWithPostfix(MaxClientIpAddressLength), - ClientName = auditInfo.ClientName.TruncateWithPostfix(MaxClientNameLength), - BrowserInfo = auditInfo.BrowserInfo.TruncateWithPostfix(MaxBrowserInfoLength), - Exception = exceptionMessage.TruncateWithPostfix(MaxExceptionLength), - ImpersonatorUserId = auditInfo.ImpersonatorUserId, - ImpersonatorTenantId = auditInfo.ImpersonatorTenantId, - CustomData = auditInfo.CustomData.TruncateWithPostfix(MaxCustomDataLength) - }; - } - - public override string ToString() - { - return string.Format( - "AUDIT LOG: {0}.{1} is executed by user {2} in {3} ms from {4} IP address.", - ServiceName, MethodName, UserId, ExecutionDuration, ClientIpAddress - ); - } - } -} +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities; +using Abp.Extensions; + +namespace Abp.Auditing +{ + /// + /// Used to store audit logs. + /// + [Table("AbpAuditLogs")] + public class AuditLog : Entity, IMayHaveTenant + { + /// + /// Maximum length of property. + /// + public const int MaxServiceNameLength = 256; + + /// + /// Maximum length of property. + /// + public const int MaxMethodNameLength = 256; + + /// + /// Maximum length of property. + /// + public const int MaxParametersLength = 1024; + + /// + /// Maximum length of property. + /// + public const int MaxClientIpAddressLength = 64; + + /// + /// Maximum length of property. + /// + public const int MaxClientNameLength = 128; + + /// + /// Maximum length of property. + /// + public const int MaxBrowserInfoLength = 256; + + /// + /// Maximum length of property. + /// + public const int MaxExceptionLength = 2000; + + /// + /// Maximum length of property. + /// + public const int MaxCustomDataLength = 2000; + + /// + /// TenantId. + /// + public virtual int? TenantId { get; set; } + + /// + /// UserId. + /// + public virtual long? UserId { get; set; } + + /// + /// Service (class/interface) name. + /// + [MaxLength(MaxServiceNameLength)] + public virtual string ServiceName { get; set; } + + /// + /// Executed method name. + /// + [MaxLength(MaxMethodNameLength)] + public virtual string MethodName { get; set; } + + /// + /// Calling parameters. + /// + [MaxLength(MaxParametersLength)] + public virtual string Parameters { get; set; } + + /// + /// Start time of the method execution. + /// + public virtual DateTime ExecutionTime { get; set; } + + /// + /// Total duration of the method call as milliseconds. + /// + public virtual int ExecutionDuration { get; set; } + + /// + /// IP address of the client. + /// + [MaxLength(MaxClientIpAddressLength)] + public virtual string ClientIpAddress { get; set; } + + /// + /// Name (generally computer name) of the client. + /// + [MaxLength(MaxClientNameLength)] + public virtual string ClientName { get; set; } + + /// + /// Browser information if this method is called in a web request. + /// + [MaxLength(MaxBrowserInfoLength)] + public virtual string BrowserInfo { get; set; } + + /// + /// Exception object, if an exception occured during execution of the method. + /// + [MaxLength(MaxExceptionLength)] + public virtual string Exception { get; set; } + + /// + /// . + /// + public virtual long? ImpersonatorUserId { get; set; } + + /// + /// . + /// + public virtual int? ImpersonatorTenantId { get; set; } + + /// + /// . + /// + [MaxLength(MaxCustomDataLength)] + public virtual string CustomData { get; set; } + + /// + /// Creates a new CreateFromAuditInfo from given . + /// + /// Source object + /// The object that is created using + public static AuditLog CreateFromAuditInfo(AuditInfo auditInfo) + { + var exceptionMessage = auditInfo.Exception != null ? auditInfo.Exception.ToString() : null; + return new AuditLog + { + TenantId = auditInfo.TenantId, + UserId = auditInfo.UserId, + ServiceName = auditInfo.ServiceName.TruncateWithPostfix(MaxServiceNameLength), + MethodName = auditInfo.MethodName.TruncateWithPostfix(MaxMethodNameLength), + Parameters = auditInfo.Parameters.TruncateWithPostfix(MaxParametersLength), + ExecutionTime = auditInfo.ExecutionTime, + ExecutionDuration = auditInfo.ExecutionDuration, + ClientIpAddress = auditInfo.ClientIpAddress.TruncateWithPostfix(MaxClientIpAddressLength), + ClientName = auditInfo.ClientName.TruncateWithPostfix(MaxClientNameLength), + BrowserInfo = auditInfo.BrowserInfo.TruncateWithPostfix(MaxBrowserInfoLength), + Exception = exceptionMessage.TruncateWithPostfix(MaxExceptionLength), + ImpersonatorUserId = auditInfo.ImpersonatorUserId, + ImpersonatorTenantId = auditInfo.ImpersonatorTenantId, + CustomData = auditInfo.CustomData.TruncateWithPostfix(MaxCustomDataLength) + }; + } + + public override string ToString() + { + return string.Format( + "AUDIT LOG: {0}.{1} is executed by user {2} in {3} ms from {4} IP address.", + ServiceName, MethodName, UserId, ExecutionDuration, ClientIpAddress + ); + } + } +} diff --git a/src/Abp.Zero/Auditing/AuditingStore.cs b/src/Abp.Zero.Common/Auditing/AuditingStore.cs similarity index 100% rename from src/Abp.Zero/Auditing/AuditingStore.cs rename to src/Abp.Zero.Common/Auditing/AuditingStore.cs diff --git a/src/Abp.Zero/Authorization/Users/AbpLoginResultType.cs b/src/Abp.Zero.Common/Authorization/AbpLoginResultType.cs similarity index 76% rename from src/Abp.Zero/Authorization/Users/AbpLoginResultType.cs rename to src/Abp.Zero.Common/Authorization/AbpLoginResultType.cs index d0e5dfaf..7becff79 100644 --- a/src/Abp.Zero/Authorization/Users/AbpLoginResultType.cs +++ b/src/Abp.Zero.Common/Authorization/AbpLoginResultType.cs @@ -1,23 +1,25 @@ -namespace Abp.Authorization.Users -{ - public enum AbpLoginResultType : byte - { - Success = 1, - - InvalidUserNameOrEmailAddress, - - InvalidPassword, - - UserIsNotActive, - - InvalidTenancyName, - - TenantIsNotActive, - - UserEmailIsNotConfirmed, - - UnknownExternalLogin, - - LockedOut - } +namespace Abp.Authorization +{ + public enum AbpLoginResultType : byte + { + Success = 1, + + InvalidUserNameOrEmailAddress, + + InvalidPassword, + + UserIsNotActive, + + InvalidTenancyName, + + TenantIsNotActive, + + UserEmailIsNotConfirmed, + + UnknownExternalLogin, + + LockedOut, + + UserPhoneNumberIsNotConfirmed, + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/PermissionGrantInfo.cs b/src/Abp.Zero.Common/Authorization/PermissionGrantInfo.cs similarity index 100% rename from src/Abp.Zero/Authorization/PermissionGrantInfo.cs rename to src/Abp.Zero.Common/Authorization/PermissionGrantInfo.cs diff --git a/src/Abp.Zero/Authorization/PermissionSetting.cs b/src/Abp.Zero.Common/Authorization/PermissionSetting.cs similarity index 100% rename from src/Abp.Zero/Authorization/PermissionSetting.cs rename to src/Abp.Zero.Common/Authorization/PermissionSetting.cs diff --git a/src/Abp.Zero.Common/Authorization/Roles/AbpRoleBase.cs b/src/Abp.Zero.Common/Authorization/Roles/AbpRoleBase.cs new file mode 100644 index 00000000..07ba941a --- /dev/null +++ b/src/Abp.Zero.Common/Authorization/Roles/AbpRoleBase.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Authorization.Users; +using Abp.Domain.Entities; +using Abp.Domain.Entities.Auditing; + +namespace Abp.Authorization.Roles +{ + /// + /// Base class for role. + /// + [Table("AbpRoles")] + public abstract class AbpRoleBase : FullAuditedEntity, IMayHaveTenant + { + /// + /// Maximum length of the property. + /// + public const int MaxDisplayNameLength = 64; + + /// + /// Maximum length of the property. + /// + public const int MaxNameLength = 32; + + /// + /// Tenant's Id, if this role is a tenant-level role. Null, if not. + /// + public virtual int? TenantId { get; set; } + + /// + /// Unique name of this role. + /// + [Required] + [StringLength(MaxNameLength)] + public virtual string Name { get; set; } + + /// + /// Display name of this role. + /// + [Required] + [StringLength(MaxDisplayNameLength)] + public virtual string DisplayName { get; set; } + + /// + /// Is this a static role? + /// Static roles can not be deleted, can not change their name. + /// They can be used programmatically. + /// + public virtual bool IsStatic { get; set; } + + /// + /// Is this role will be assigned to new users as default? + /// + public virtual bool IsDefault { get; set; } + + /// + /// List of permissions of the role. + /// + [ForeignKey("RoleId")] + public virtual ICollection Permissions { get; set; } + + protected AbpRoleBase() + { + Name = Guid.NewGuid().ToString("N"); + } + + protected AbpRoleBase(int? tenantId, string displayName) + : this() + { + TenantId = tenantId; + DisplayName = displayName; + } + + protected AbpRoleBase(int? tenantId, string name, string displayName) + : this(tenantId, displayName) + { + Name = name; + } + + public override string ToString() + { + return $"[Role {Id}, Name={Name}]"; + } + } +} \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Roles/AbpRolePermissionCacheItemInvalidator.cs b/src/Abp.Zero.Common/Authorization/Roles/AbpRolePermissionCacheItemInvalidator.cs similarity index 100% rename from src/Abp.Zero/Authorization/Roles/AbpRolePermissionCacheItemInvalidator.cs rename to src/Abp.Zero.Common/Authorization/Roles/AbpRolePermissionCacheItemInvalidator.cs diff --git a/src/Abp.Zero/Authorization/Roles/IRolePermissionStore.cs b/src/Abp.Zero.Common/Authorization/Roles/IRolePermissionStore.cs similarity index 90% rename from src/Abp.Zero/Authorization/Roles/IRolePermissionStore.cs rename to src/Abp.Zero.Common/Authorization/Roles/IRolePermissionStore.cs index acd3baac..d6b6b2f7 100644 --- a/src/Abp.Zero/Authorization/Roles/IRolePermissionStore.cs +++ b/src/Abp.Zero.Common/Authorization/Roles/IRolePermissionStore.cs @@ -1,56 +1,54 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Abp.Authorization.Users; - -namespace Abp.Authorization.Roles -{ - /// - /// Used to perform permission database operations for a role. - /// - public interface IRolePermissionStore - where TRole : AbpRole - where TUser : AbpUser - { - /// - /// Adds a permission grant setting to a role. - /// - /// Role - /// Permission grant setting info - Task AddPermissionAsync(TRole role, PermissionGrantInfo permissionGrant); - - /// - /// Removes a permission grant setting from a role. - /// - /// Role - /// Permission grant setting info - Task RemovePermissionAsync(TRole role, PermissionGrantInfo permissionGrant); - - /// - /// Gets permission grant setting informations for a role. - /// - /// Role - /// List of permission setting informations - Task> GetPermissionsAsync(TRole role); - - /// - /// Gets permission grant setting informations for a role. - /// - /// Role id - /// List of permission setting informations - Task> GetPermissionsAsync(int roleId); - - /// - /// Checks whether a role has a permission grant setting info. - /// - /// Role id - /// Permission grant setting info - /// - Task HasPermissionAsync(int roleId, PermissionGrantInfo permissionGrant); - - /// - /// Deleted all permission settings for a role. - /// - /// Role - Task RemoveAllPermissionSettingsAsync(TRole role); - } +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Abp.Authorization.Roles +{ + /// + /// Used to perform permission database operations for a role. + /// + public interface IRolePermissionStore + where TRole : AbpRoleBase + { + /// + /// Adds a permission grant setting to a role. + /// + /// Role + /// Permission grant setting info + Task AddPermissionAsync(TRole role, PermissionGrantInfo permissionGrant); + + /// + /// Removes a permission grant setting from a role. + /// + /// Role + /// Permission grant setting info + Task RemovePermissionAsync(TRole role, PermissionGrantInfo permissionGrant); + + /// + /// Gets permission grant setting informations for a role. + /// + /// Role + /// List of permission setting informations + Task> GetPermissionsAsync(TRole role); + + /// + /// Gets permission grant setting informations for a role. + /// + /// Role id + /// List of permission setting informations + Task> GetPermissionsAsync(int roleId); + + /// + /// Checks whether a role has a permission grant setting info. + /// + /// Role id + /// Permission grant setting info + /// + Task HasPermissionAsync(int roleId, PermissionGrantInfo permissionGrant); + + /// + /// Deleted all permission settings for a role. + /// + /// Role + Task RemoveAllPermissionSettingsAsync(TRole role); + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Roles/PermissionEqualityComparer.cs b/src/Abp.Zero.Common/Authorization/Roles/PermissionEqualityComparer.cs similarity index 100% rename from src/Abp.Zero/Authorization/Roles/PermissionEqualityComparer.cs rename to src/Abp.Zero.Common/Authorization/Roles/PermissionEqualityComparer.cs diff --git a/src/Abp.Zero.Common/Authorization/Roles/RoleClaim.cs b/src/Abp.Zero.Common/Authorization/Roles/RoleClaim.cs new file mode 100644 index 00000000..41ffded0 --- /dev/null +++ b/src/Abp.Zero.Common/Authorization/Roles/RoleClaim.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.Security.Claims; +using Abp.Domain.Entities; +using Abp.Domain.Entities.Auditing; + +namespace Abp.Authorization.Roles +{ + [Table("AbpRoleClaims")] + public class RoleClaim : CreationAuditedEntity, IMayHaveTenant + { + public virtual int? TenantId { get; set; } + + public virtual int RoleId { get; set; } + + public virtual string ClaimType { get; set; } + + public virtual string ClaimValue { get; set; } + + public RoleClaim() + { + + } + + public RoleClaim(AbpRoleBase role, Claim claim) + { + TenantId = role.TenantId; + RoleId = role.Id; + ClaimType = claim.Type; + ClaimValue = claim.Value; + } + } +} diff --git a/src/Abp.Zero/Authorization/Roles/RolePermissionCacheItem.cs b/src/Abp.Zero.Common/Authorization/Roles/RolePermissionCacheItem.cs similarity index 95% rename from src/Abp.Zero/Authorization/Roles/RolePermissionCacheItem.cs rename to src/Abp.Zero.Common/Authorization/Roles/RolePermissionCacheItem.cs index f7e82701..b5d7b0b4 100644 --- a/src/Abp.Zero/Authorization/Roles/RolePermissionCacheItem.cs +++ b/src/Abp.Zero.Common/Authorization/Roles/RolePermissionCacheItem.cs @@ -1,29 +1,29 @@ -using System; -using System.Collections.Generic; - -namespace Abp.Authorization.Roles -{ - /// - /// Used to cache permissions of a role. - /// - [Serializable] - public class RolePermissionCacheItem - { - public const string CacheStoreName = "AbpZeroRolePermissions"; - - public long RoleId { get; set; } - - public HashSet GrantedPermissions { get; set; } - - public RolePermissionCacheItem() - { - GrantedPermissions = new HashSet(); - } - - public RolePermissionCacheItem(int roleId) - : this() - { - RoleId = roleId; - } - } +using System; +using System.Collections.Generic; + +namespace Abp.Authorization.Roles +{ + /// + /// Used to cache permissions of a role. + /// + [Serializable] + public class RolePermissionCacheItem + { + public const string CacheStoreName = "AbpZeroRolePermissions"; + + public long RoleId { get; set; } + + public HashSet GrantedPermissions { get; set; } + + public RolePermissionCacheItem() + { + GrantedPermissions = new HashSet(); + } + + public RolePermissionCacheItem(int roleId) + : this() + { + RoleId = roleId; + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Roles/RolePermissionSetting.cs b/src/Abp.Zero.Common/Authorization/Roles/RolePermissionSetting.cs similarity index 100% rename from src/Abp.Zero/Authorization/Roles/RolePermissionSetting.cs rename to src/Abp.Zero.Common/Authorization/Roles/RolePermissionSetting.cs diff --git a/src/Abp.Zero.Common/Authorization/Users/AbpUserBase.cs b/src/Abp.Zero.Common/Authorization/Users/AbpUserBase.cs new file mode 100644 index 00000000..4c30cf74 --- /dev/null +++ b/src/Abp.Zero.Common/Authorization/Users/AbpUserBase.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Configuration; +using Abp.Domain.Entities; +using Abp.Domain.Entities.Auditing; +using Abp.Extensions; + +namespace Abp.Authorization.Users +{ + /// + /// Base class for user. + /// + [Table("AbpUsers")] + public abstract class AbpUserBase : FullAuditedEntity, IMayHaveTenant, IPassivable + { + /// + /// Maximum length of the property. + /// + public const int MaxUserNameLength = 32; + + /// + /// Maximum length of the property. + /// + public const int MaxEmailAddressLength = 256; + + /// + /// Maximum length of the property. + /// + public const int MaxNameLength = 32; + + /// + /// Maximum length of the property. + /// + public const int MaxSurnameLength = 32; + + /// + /// Maximum length of the property. + /// + public const int MaxAuthenticationSourceLength = 64; + + /// + /// UserName of the admin. + /// admin can not be deleted and UserName of the admin can not be changed. + /// + public const string AdminUserName = "admin"; + + /// + /// Maximum length of the property. + /// + public const int MaxPasswordLength = 128; + + /// + /// Maximum length of the without hashed. + /// + public const int MaxPlainPasswordLength = 32; + + /// + /// Maximum length of the property. + /// + public const int MaxEmailConfirmationCodeLength = 328; + + /// + /// Maximum length of the property. + /// + public const int MaxPasswordResetCodeLength = 328; + + /// + /// Authorization source name. + /// It's set to external authentication source name if created by an external source. + /// Default: null. + /// + [MaxLength(MaxAuthenticationSourceLength)] + public virtual string AuthenticationSource { get; set; } + + /// + /// User name. + /// User name must be unique for it's tenant. + /// + [Required] + [StringLength(MaxUserNameLength)] + public virtual string UserName { get; set; } + + /// + /// Tenant Id of this user. + /// + public virtual int? TenantId { get; set; } + + /// + /// Email address of the user. + /// Email address must be unique for it's tenant. + /// + [Required] + [StringLength(MaxEmailAddressLength)] + public virtual string EmailAddress { get; set; } + + /// + /// Name of the user. + /// + [Required] + [StringLength(MaxNameLength)] + public virtual string Name { get; set; } + + /// + /// Surname of the user. + /// + [Required] + [StringLength(MaxSurnameLength)] + public virtual string Surname { get; set; } + + /// + /// Return full name (Name Surname ) + /// + [NotMapped] + public virtual string FullName { get { return this.Name + " " + this.Surname; } } + + /// + /// Password of the user. + /// + [Required] + [StringLength(MaxPasswordLength)] + public virtual string Password { get; set; } + + /// + /// Confirmation code for email. + /// + [StringLength(MaxEmailConfirmationCodeLength)] + public virtual string EmailConfirmationCode { get; set; } + + /// + /// Reset code for password. + /// It's not valid if it's null. + /// It's for one usage and must be set to null after reset. + /// + [StringLength(MaxPasswordResetCodeLength)] + public virtual string PasswordResetCode { get; set; } + + /// + /// Lockout end date. + /// + public virtual DateTime? LockoutEndDateUtc { get; set; } + + /// + /// Gets or sets the access failed count. + /// + public virtual int AccessFailedCount { get; set; } + + /// + /// Gets or sets the lockout enabled. + /// + public virtual bool IsLockoutEnabled { get; set; } + + /// + /// Gets or sets the phone number. + /// + public virtual string PhoneNumber { get; set; } + + /// + /// Is the confirmed. + /// + public virtual bool IsPhoneNumberConfirmed { get; set; } + + /// + /// Gets or sets the security stamp. + /// + public virtual string SecurityStamp { get; set; } + + /// + /// Is two factor auth enabled. + /// + public virtual bool IsTwoFactorEnabled { get; set; } + + /// + /// Login definitions for this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Logins { get; set; } + + /// + /// Roles of this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Roles { get; set; } + + /// + /// Claims of this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Claims { get; set; } + + /// + /// Permission definitions for this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Permissions { get; set; } + + /// + /// Settings for this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Settings { get; set; } + + /// + /// Is the confirmed. + /// + public virtual bool IsEmailConfirmed { get; set; } + + /// + /// Is this user active? + /// If as user is not active, he/she can not use the application. + /// + public virtual bool IsActive { get; set; } + + /// + /// The last time this user entered to the system. + /// + public virtual DateTime? LastLoginTime { get; set; } + + protected AbpUserBase() + { + IsActive = true; + IsLockoutEnabled = true; + SecurityStamp = SequentialGuidGenerator.Instance.Create().ToString(); + } + + public virtual void SetNewPasswordResetCode() + { + PasswordResetCode = Guid.NewGuid().ToString("N").Truncate(MaxPasswordResetCodeLength); + } + + public virtual void SetNewEmailConfirmationCode() + { + EmailConfirmationCode = Guid.NewGuid().ToString("N").Truncate(MaxEmailConfirmationCodeLength); + } + + /// + /// Creates from this User. + /// + /// + public virtual UserIdentifier ToUserIdentifier() + { + return new UserIdentifier(TenantId, Id); + } + + public override string ToString() + { + return $"[User {Id}] {UserName}"; + } + } +} \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/AbpUserPermissionCacheItemInvalidator.cs b/src/Abp.Zero.Common/Authorization/Users/AbpUserPermissionCacheItemInvalidator.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/AbpUserPermissionCacheItemInvalidator.cs rename to src/Abp.Zero.Common/Authorization/Users/AbpUserPermissionCacheItemInvalidator.cs diff --git a/src/Abp.Zero/Authorization/Users/DefaultExternalAuthenticationSource.cs b/src/Abp.Zero.Common/Authorization/Users/DefaultExternalAuthenticationSource.cs similarity index 97% rename from src/Abp.Zero/Authorization/Users/DefaultExternalAuthenticationSource.cs rename to src/Abp.Zero.Common/Authorization/Users/DefaultExternalAuthenticationSource.cs index e45b39f4..e465a7f3 100644 --- a/src/Abp.Zero/Authorization/Users/DefaultExternalAuthenticationSource.cs +++ b/src/Abp.Zero.Common/Authorization/Users/DefaultExternalAuthenticationSource.cs @@ -11,7 +11,7 @@ namespace Abp.Authorization.Users /// User type public abstract class DefaultExternalAuthenticationSource : IExternalAuthenticationSource where TTenant : AbpTenant - where TUser : AbpUser, new() + where TUser : AbpUserBase, new() { /// public abstract string Name { get; } diff --git a/src/Abp.Zero/Authorization/Users/IExternalAuthenticationSource.cs b/src/Abp.Zero.Common/Authorization/Users/IExternalAuthenticationSource.cs similarity index 89% rename from src/Abp.Zero/Authorization/Users/IExternalAuthenticationSource.cs rename to src/Abp.Zero.Common/Authorization/Users/IExternalAuthenticationSource.cs index d6adb2c6..cfd6dea0 100644 --- a/src/Abp.Zero/Authorization/Users/IExternalAuthenticationSource.cs +++ b/src/Abp.Zero.Common/Authorization/Users/IExternalAuthenticationSource.cs @@ -4,17 +4,17 @@ namespace Abp.Authorization.Users { /// - /// Defines an authorization source to be used by method. + /// Defines an external authorization source. /// /// Tenant type /// User type public interface IExternalAuthenticationSource where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { /// /// Unique name of the authentication source. - /// This source name is set to + /// This source name is set to /// if the user authenticated by this authentication source /// string Name { get; } diff --git a/src/Abp.Zero/Authorization/Users/IUserPermissionStore.cs b/src/Abp.Zero.Common/Authorization/Users/IUserPermissionStore.cs similarity index 95% rename from src/Abp.Zero/Authorization/Users/IUserPermissionStore.cs rename to src/Abp.Zero.Common/Authorization/Users/IUserPermissionStore.cs index 808418d2..8bf692fe 100644 --- a/src/Abp.Zero/Authorization/Users/IUserPermissionStore.cs +++ b/src/Abp.Zero.Common/Authorization/Users/IUserPermissionStore.cs @@ -1,47 +1,47 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Abp.Authorization.Users -{ - /// - /// Used to perform permission database operations for a user. - /// - public interface IUserPermissionStore - where TUser : AbpUser - { - /// - /// Adds a permission grant setting to a user. - /// - /// User - /// Permission grant setting info - Task AddPermissionAsync(TUser user, PermissionGrantInfo permissionGrant); - - /// - /// Removes a permission grant setting from a user. - /// - /// User - /// Permission grant setting info - Task RemovePermissionAsync(TUser user, PermissionGrantInfo permissionGrant); - - /// - /// Gets permission grant setting informations for a user. - /// - /// User id - /// List of permission setting informations - Task> GetPermissionsAsync(long userId); - - /// - /// Checks whether a role has a permission grant setting info. - /// - /// User id - /// Permission grant setting info - /// - Task HasPermissionAsync(long userId, PermissionGrantInfo permissionGrant); - - /// - /// Deleted all permission settings for a role. - /// - /// User - Task RemoveAllPermissionSettingsAsync(TUser user); - } +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Abp.Authorization.Users +{ + /// + /// Used to perform permission database operations for a user. + /// + public interface IUserPermissionStore + where TUser : AbpUserBase + { + /// + /// Adds a permission grant setting to a user. + /// + /// User + /// Permission grant setting info + Task AddPermissionAsync(TUser user, PermissionGrantInfo permissionGrant); + + /// + /// Removes a permission grant setting from a user. + /// + /// User + /// Permission grant setting info + Task RemovePermissionAsync(TUser user, PermissionGrantInfo permissionGrant); + + /// + /// Gets permission grant setting informations for a user. + /// + /// User id + /// List of permission setting informations + Task> GetPermissionsAsync(long userId); + + /// + /// Checks whether a role has a permission grant setting info. + /// + /// User id + /// Permission grant setting info + /// + Task HasPermissionAsync(long userId, PermissionGrantInfo permissionGrant); + + /// + /// Deleted all permission settings for a role. + /// + /// User + Task RemoveAllPermissionSettingsAsync(TUser user); + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/UserAccount.cs b/src/Abp.Zero.Common/Authorization/Users/UserAccount.cs similarity index 96% rename from src/Abp.Zero/Authorization/Users/UserAccount.cs rename to src/Abp.Zero.Common/Authorization/Users/UserAccount.cs index a7864da9..c027cfe3 100644 --- a/src/Abp.Zero/Authorization/Users/UserAccount.cs +++ b/src/Abp.Zero.Common/Authorization/Users/UserAccount.cs @@ -1,27 +1,27 @@ -using System; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities.Auditing; -using Abp.MultiTenancy; - -namespace Abp.Authorization.Users -{ - /// - /// Represents a summary user - /// - [Table("AbpUserAccounts")] - [MultiTenancySide(MultiTenancySides.Host)] - public class UserAccount : FullAuditedEntity - { - public virtual int? TenantId { get; set; } - - public virtual long UserId { get; set; } - - public virtual long? UserLinkId { get; set; } - - public virtual string UserName { get; set; } - - public virtual string EmailAddress { get; set; } - - public virtual DateTime? LastLoginTime { get; set; } - } +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities.Auditing; +using Abp.MultiTenancy; + +namespace Abp.Authorization.Users +{ + /// + /// Represents a summary user + /// + [Table("AbpUserAccounts")] + [MultiTenancySide(MultiTenancySides.Host)] + public class UserAccount : FullAuditedEntity + { + public virtual int? TenantId { get; set; } + + public virtual long UserId { get; set; } + + public virtual long? UserLinkId { get; set; } + + public virtual string UserName { get; set; } + + public virtual string EmailAddress { get; set; } + + public virtual DateTime? LastLoginTime { get; set; } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/UserAccountSynchronizer.cs b/src/Abp.Zero.Common/Authorization/Users/UserAccountSynchronizer.cs similarity index 97% rename from src/Abp.Zero/Authorization/Users/UserAccountSynchronizer.cs rename to src/Abp.Zero.Common/Authorization/Users/UserAccountSynchronizer.cs index 3cd059cf..42a89140 100644 --- a/src/Abp.Zero/Authorization/Users/UserAccountSynchronizer.cs +++ b/src/Abp.Zero.Common/Authorization/Users/UserAccountSynchronizer.cs @@ -1,90 +1,90 @@ -using Abp.Dependency; -using Abp.Domain.Repositories; -using Abp.Domain.Uow; -using Abp.Events.Bus.Entities; -using Abp.Events.Bus.Handlers; - -namespace Abp.Authorization.Users -{ - /// - /// Synchronizes a user's information to user account. - /// - public class UserAccountSynchronizer : - IEventHandler>, - IEventHandler>, - IEventHandler>, - ITransientDependency - { - private readonly IRepository _userAccountRepository; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - /// - /// Constructor - /// - public UserAccountSynchronizer( - IRepository userAccountRepository, - IUnitOfWorkManager unitOfWorkManager) - { - _userAccountRepository = userAccountRepository; - _unitOfWorkManager = unitOfWorkManager; - } - - /// - /// Handles creation event of user - /// - [UnitOfWork] - public virtual void HandleEvent(EntityCreatedEventData eventData) - { - using (_unitOfWorkManager.Current.SetTenantId(null)) - { - _userAccountRepository.Insert(new UserAccount - { - TenantId = eventData.Entity.TenantId, - UserName = eventData.Entity.UserName, - UserId = eventData.Entity.Id, - EmailAddress = eventData.Entity.EmailAddress, - LastLoginTime = eventData.Entity.LastLoginTime - }); - } - } - - /// - /// Handles deletion event of user - /// - /// - [UnitOfWork] - public virtual void HandleEvent(EntityDeletedEventData eventData) - { - using (_unitOfWorkManager.Current.SetTenantId(null)) - { - var userAccount = - _userAccountRepository.FirstOrDefault( - ua => ua.TenantId == eventData.Entity.TenantId && ua.UserId == eventData.Entity.Id); - if (userAccount != null) - { - _userAccountRepository.Delete(userAccount); - } - } - } - - /// - /// Handles update event of user - /// - /// - [UnitOfWork] - public virtual void HandleEvent(EntityUpdatedEventData eventData) - { - using (_unitOfWorkManager.Current.SetTenantId(null)) - { - var userAccount = _userAccountRepository.FirstOrDefault(ua => ua.TenantId == eventData.Entity.TenantId && ua.UserId == eventData.Entity.Id); - if (userAccount != null) - { - userAccount.UserName = eventData.Entity.UserName; - userAccount.EmailAddress = eventData.Entity.EmailAddress; - userAccount.LastLoginTime = eventData.Entity.LastLoginTime; - _userAccountRepository.Update(userAccount); - } - } - } - } +using Abp.Dependency; +using Abp.Domain.Repositories; +using Abp.Domain.Uow; +using Abp.Events.Bus.Entities; +using Abp.Events.Bus.Handlers; + +namespace Abp.Authorization.Users +{ + /// + /// Synchronizes a user's information to user account. + /// + public class UserAccountSynchronizer : + IEventHandler>, + IEventHandler>, + IEventHandler>, + ITransientDependency + { + private readonly IRepository _userAccountRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + /// + /// Constructor + /// + public UserAccountSynchronizer( + IRepository userAccountRepository, + IUnitOfWorkManager unitOfWorkManager) + { + _userAccountRepository = userAccountRepository; + _unitOfWorkManager = unitOfWorkManager; + } + + /// + /// Handles creation event of user + /// + [UnitOfWork] + public virtual void HandleEvent(EntityCreatedEventData eventData) + { + using (_unitOfWorkManager.Current.SetTenantId(null)) + { + _userAccountRepository.Insert(new UserAccount + { + TenantId = eventData.Entity.TenantId, + UserName = eventData.Entity.UserName, + UserId = eventData.Entity.Id, + EmailAddress = eventData.Entity.EmailAddress, + LastLoginTime = eventData.Entity.LastLoginTime + }); + } + } + + /// + /// Handles deletion event of user + /// + /// + [UnitOfWork] + public virtual void HandleEvent(EntityDeletedEventData eventData) + { + using (_unitOfWorkManager.Current.SetTenantId(null)) + { + var userAccount = + _userAccountRepository.FirstOrDefault( + ua => ua.TenantId == eventData.Entity.TenantId && ua.UserId == eventData.Entity.Id); + if (userAccount != null) + { + _userAccountRepository.Delete(userAccount); + } + } + } + + /// + /// Handles update event of user + /// + /// + [UnitOfWork] + public virtual void HandleEvent(EntityUpdatedEventData eventData) + { + using (_unitOfWorkManager.Current.SetTenantId(null)) + { + var userAccount = _userAccountRepository.FirstOrDefault(ua => ua.TenantId == eventData.Entity.TenantId && ua.UserId == eventData.Entity.Id); + if (userAccount != null) + { + userAccount.UserName = eventData.Entity.UserName; + userAccount.EmailAddress = eventData.Entity.EmailAddress; + userAccount.LastLoginTime = eventData.Entity.LastLoginTime; + _userAccountRepository.Update(userAccount); + } + } + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/UserClaim.cs b/src/Abp.Zero.Common/Authorization/Users/UserClaim.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserClaim.cs rename to src/Abp.Zero.Common/Authorization/Users/UserClaim.cs diff --git a/src/Abp.Zero/Authorization/Users/UserLogin.cs b/src/Abp.Zero.Common/Authorization/Users/UserLogin.cs similarity index 80% rename from src/Abp.Zero/Authorization/Users/UserLogin.cs rename to src/Abp.Zero.Common/Authorization/Users/UserLogin.cs index ac9d6da6..a3ca3ce9 100644 --- a/src/Abp.Zero/Authorization/Users/UserLogin.cs +++ b/src/Abp.Zero.Common/Authorization/Users/UserLogin.cs @@ -40,5 +40,18 @@ public class UserLogin : Entity, IMayHaveTenant [Required] [MaxLength(MaxProviderKeyLength)] public virtual string ProviderKey { get; set; } + + public UserLogin() + { + + } + + public UserLogin(int? tenantId, long userId, string loginProvider, string providerKey) + { + TenantId = tenantId; + UserId = userId; + LoginProvider = loginProvider; + ProviderKey = providerKey; + } } } diff --git a/src/Abp.Zero/Authorization/Users/UserLoginAttempt.cs b/src/Abp.Zero.Common/Authorization/Users/UserLoginAttempt.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserLoginAttempt.cs rename to src/Abp.Zero.Common/Authorization/Users/UserLoginAttempt.cs diff --git a/src/Abp.Zero/Authorization/Users/UserOrganizationUnit.cs b/src/Abp.Zero.Common/Authorization/Users/UserOrganizationUnit.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserOrganizationUnit.cs rename to src/Abp.Zero.Common/Authorization/Users/UserOrganizationUnit.cs diff --git a/src/Abp.Zero/Authorization/Users/UserOrganizationUnitRemover.cs b/src/Abp.Zero.Common/Authorization/Users/UserOrganizationUnitRemover.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserOrganizationUnitRemover.cs rename to src/Abp.Zero.Common/Authorization/Users/UserOrganizationUnitRemover.cs diff --git a/src/Abp.Zero/Authorization/Users/UserPermissionCacheItem.cs b/src/Abp.Zero.Common/Authorization/Users/UserPermissionCacheItem.cs similarity index 96% rename from src/Abp.Zero/Authorization/Users/UserPermissionCacheItem.cs rename to src/Abp.Zero.Common/Authorization/Users/UserPermissionCacheItem.cs index ed198ab8..ebf2f995 100644 --- a/src/Abp.Zero/Authorization/Users/UserPermissionCacheItem.cs +++ b/src/Abp.Zero.Common/Authorization/Users/UserPermissionCacheItem.cs @@ -1,35 +1,35 @@ -using System; -using System.Collections.Generic; - -namespace Abp.Authorization.Users -{ - /// - /// Used to cache roles and permissions of a user. - /// - [Serializable] - public class UserPermissionCacheItem - { - public const string CacheStoreName = "AbpZeroUserPermissions"; - - public long UserId { get; set; } - - public List RoleIds { get; set; } - - public HashSet GrantedPermissions { get; set; } - - public HashSet ProhibitedPermissions { get; set; } - - public UserPermissionCacheItem() - { - RoleIds = new List(); - GrantedPermissions = new HashSet(); - ProhibitedPermissions = new HashSet(); - } - - public UserPermissionCacheItem(long userId) - : this() - { - UserId = userId; - } - } -} +using System; +using System.Collections.Generic; + +namespace Abp.Authorization.Users +{ + /// + /// Used to cache roles and permissions of a user. + /// + [Serializable] + public class UserPermissionCacheItem + { + public const string CacheStoreName = "AbpZeroUserPermissions"; + + public long UserId { get; set; } + + public List RoleIds { get; set; } + + public HashSet GrantedPermissions { get; set; } + + public HashSet ProhibitedPermissions { get; set; } + + public UserPermissionCacheItem() + { + RoleIds = new List(); + GrantedPermissions = new HashSet(); + ProhibitedPermissions = new HashSet(); + } + + public UserPermissionCacheItem(long userId) + : this() + { + UserId = userId; + } + } +} diff --git a/src/Abp.Zero/Authorization/Users/UserPermissionSetting.cs b/src/Abp.Zero.Common/Authorization/Users/UserPermissionSetting.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserPermissionSetting.cs rename to src/Abp.Zero.Common/Authorization/Users/UserPermissionSetting.cs diff --git a/src/Abp.Zero/Authorization/Users/UserRole.cs b/src/Abp.Zero.Common/Authorization/Users/UserRole.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserRole.cs rename to src/Abp.Zero.Common/Authorization/Users/UserRole.cs diff --git a/src/Abp.Zero/Authorization/Users/UserRoleRemover.cs b/src/Abp.Zero.Common/Authorization/Users/UserRoleRemover.cs similarity index 100% rename from src/Abp.Zero/Authorization/Users/UserRoleRemover.cs rename to src/Abp.Zero.Common/Authorization/Users/UserRoleRemover.cs diff --git a/src/Abp.Zero/BackgroundJobs/BackgroundJobStore.cs b/src/Abp.Zero.Common/BackgroundJobs/BackgroundJobStore.cs similarity index 100% rename from src/Abp.Zero/BackgroundJobs/BackgroundJobStore.cs rename to src/Abp.Zero.Common/BackgroundJobs/BackgroundJobStore.cs diff --git a/src/Abp.Zero/Configuration/Setting.cs b/src/Abp.Zero.Common/Configuration/Setting.cs similarity index 100% rename from src/Abp.Zero/Configuration/Setting.cs rename to src/Abp.Zero.Common/Configuration/Setting.cs diff --git a/src/Abp.Zero/Configuration/SettingExtensions.cs b/src/Abp.Zero.Common/Configuration/SettingExtensions.cs similarity index 100% rename from src/Abp.Zero/Configuration/SettingExtensions.cs rename to src/Abp.Zero.Common/Configuration/SettingExtensions.cs diff --git a/src/Abp.Zero/Configuration/SettingStore.cs b/src/Abp.Zero.Common/Configuration/SettingStore.cs similarity index 100% rename from src/Abp.Zero/Configuration/SettingStore.cs rename to src/Abp.Zero.Common/Configuration/SettingStore.cs diff --git a/src/Abp.Zero/Localization/ApplicationLanguage.cs b/src/Abp.Zero.Common/Localization/ApplicationLanguage.cs similarity index 100% rename from src/Abp.Zero/Localization/ApplicationLanguage.cs rename to src/Abp.Zero.Common/Localization/ApplicationLanguage.cs diff --git a/src/Abp.Zero/Localization/ApplicationLanguageManager.cs b/src/Abp.Zero.Common/Localization/ApplicationLanguageManager.cs similarity index 99% rename from src/Abp.Zero/Localization/ApplicationLanguageManager.cs rename to src/Abp.Zero.Common/Localization/ApplicationLanguageManager.cs index beb72a01..995b6da2 100644 --- a/src/Abp.Zero/Localization/ApplicationLanguageManager.cs +++ b/src/Abp.Zero.Common/Localization/ApplicationLanguageManager.cs @@ -152,7 +152,7 @@ public async Task GetDefaultLanguageOrNullAsync(int? tenant /// Name of the language. public async Task SetDefaultLanguageAsync(int? tenantId, string languageName) { - var cultureInfo = CultureInfo.GetCultureInfo(languageName); + var cultureInfo = CultureInfoHelper.Get(languageName); if (tenantId.HasValue) { await _settingManager.ChangeSettingForTenantAsync(tenantId.Value, LocalizationSettingNames.DefaultLanguage, cultureInfo.Name); diff --git a/src/Abp.Zero/Localization/ApplicationLanguageProvider.cs b/src/Abp.Zero.Common/Localization/ApplicationLanguageProvider.cs similarity index 100% rename from src/Abp.Zero/Localization/ApplicationLanguageProvider.cs rename to src/Abp.Zero.Common/Localization/ApplicationLanguageProvider.cs diff --git a/src/Abp.Zero/Localization/ApplicationLanguageText.cs b/src/Abp.Zero.Common/Localization/ApplicationLanguageText.cs similarity index 100% rename from src/Abp.Zero/Localization/ApplicationLanguageText.cs rename to src/Abp.Zero.Common/Localization/ApplicationLanguageText.cs diff --git a/src/Abp.Zero/Localization/ApplicationLanguageTextManager.cs b/src/Abp.Zero.Common/Localization/ApplicationLanguageTextManager.cs similarity index 100% rename from src/Abp.Zero/Localization/ApplicationLanguageTextManager.cs rename to src/Abp.Zero.Common/Localization/ApplicationLanguageTextManager.cs diff --git a/src/Abp.Zero/Localization/EmptyDictionary.cs b/src/Abp.Zero.Common/Localization/EmptyDictionary.cs similarity index 100% rename from src/Abp.Zero/Localization/EmptyDictionary.cs rename to src/Abp.Zero.Common/Localization/EmptyDictionary.cs diff --git a/src/Abp.Zero/Localization/IApplicationLanguageManager.cs b/src/Abp.Zero.Common/Localization/IApplicationLanguageManager.cs similarity index 100% rename from src/Abp.Zero/Localization/IApplicationLanguageManager.cs rename to src/Abp.Zero.Common/Localization/IApplicationLanguageManager.cs diff --git a/src/Abp.Zero/Localization/IApplicationLanguageTextManager.cs b/src/Abp.Zero.Common/Localization/IApplicationLanguageTextManager.cs similarity index 100% rename from src/Abp.Zero/Localization/IApplicationLanguageTextManager.cs rename to src/Abp.Zero.Common/Localization/IApplicationLanguageTextManager.cs diff --git a/src/Abp.Zero/Localization/IMultiTenantLocalizationDictionary.cs b/src/Abp.Zero.Common/Localization/IMultiTenantLocalizationDictionary.cs similarity index 100% rename from src/Abp.Zero/Localization/IMultiTenantLocalizationDictionary.cs rename to src/Abp.Zero.Common/Localization/IMultiTenantLocalizationDictionary.cs diff --git a/src/Abp.Zero/Localization/IMultiTenantLocalizationSource.cs b/src/Abp.Zero.Common/Localization/IMultiTenantLocalizationSource.cs similarity index 100% rename from src/Abp.Zero/Localization/IMultiTenantLocalizationSource.cs rename to src/Abp.Zero.Common/Localization/IMultiTenantLocalizationSource.cs diff --git a/src/Abp.Zero/Localization/MultiTenantLocalizationDictionary.cs b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionary.cs similarity index 100% rename from src/Abp.Zero/Localization/MultiTenantLocalizationDictionary.cs rename to src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionary.cs diff --git a/src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryCacheCleaner.cs b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryCacheCleaner.cs similarity index 100% rename from src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryCacheCleaner.cs rename to src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryCacheCleaner.cs diff --git a/src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryCacheHelper.cs b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryCacheHelper.cs similarity index 100% rename from src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryCacheHelper.cs rename to src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryCacheHelper.cs diff --git a/src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryProvider.cs b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryProvider.cs similarity index 93% rename from src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryProvider.cs rename to src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryProvider.cs index 7bc343be..a7a1274b 100644 --- a/src/Abp.Zero/Localization/MultiTenantLocalizationDictionaryProvider.cs +++ b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationDictionaryProvider.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; @@ -6,7 +5,6 @@ using Abp.Collections.Extensions; using Abp.Dependency; using Abp.Localization.Dictionaries; -using Abp.Collections.Extensions; namespace Abp.Localization { @@ -68,13 +66,13 @@ protected virtual ILocalizationDictionary GetDefaultDictionary() var languages = _languageManager.GetLanguages(); if (!languages.Any()) { - throw new ApplicationException("No language defined!"); + throw new AbpException("No language defined!"); } var defaultLanguage = languages.FirstOrDefault(l => l.IsDefault); if (defaultLanguage == null) { - throw new ApplicationException("Default language is not defined!"); + throw new AbpException("Default language is not defined!"); } return _dictionaries.GetOrAdd(defaultLanguage.Name, s => CreateLocalizationDictionary(defaultLanguage)); @@ -84,7 +82,7 @@ protected virtual IMultiTenantLocalizationDictionary CreateLocalizationDictionar { var internalDictionary = _internalProvider.Dictionaries.GetOrDefault(language.Name) ?? - new EmptyDictionary(CultureInfo.GetCultureInfo(language.Name)); + new EmptyDictionary(CultureInfoHelper.Get(language.Name)); var dictionary = _iocManager.Resolve(new { diff --git a/src/Abp.Zero/Localization/MultiTenantLocalizationSource.cs b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationSource.cs similarity index 99% rename from src/Abp.Zero/Localization/MultiTenantLocalizationSource.cs rename to src/Abp.Zero.Common/Localization/MultiTenantLocalizationSource.cs index d3487005..c91a87a2 100644 --- a/src/Abp.Zero/Localization/MultiTenantLocalizationSource.cs +++ b/src/Abp.Zero.Common/Localization/MultiTenantLocalizationSource.cs @@ -107,7 +107,7 @@ public override void Extend(ILocalizationDictionary dictionary) private static string GetBaseCultureName(string cultureName) { return cultureName.Contains("-") - ? cultureName.Left(cultureName.IndexOf("-", StringComparison.InvariantCulture)) + ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) : cultureName; } } diff --git a/src/Abp.Zero/MultiTenancy/AbpTenant.cs b/src/Abp.Zero.Common/MultiTenancy/AbpTenant.cs similarity index 85% rename from src/Abp.Zero/MultiTenancy/AbpTenant.cs rename to src/Abp.Zero.Common/MultiTenancy/AbpTenant.cs index ae41ca01..86742e4a 100644 --- a/src/Abp.Zero/MultiTenancy/AbpTenant.cs +++ b/src/Abp.Zero.Common/MultiTenancy/AbpTenant.cs @@ -1,84 +1,78 @@ -using System.ComponentModel.DataAnnotations; -using Abp.Application.Editions; -using Abp.Authorization.Users; -using Abp.Domain.Entities; -using Abp.Domain.Entities.Auditing; - -namespace Abp.MultiTenancy -{ - /// - /// Represents a Tenant of the application. - /// - public abstract class AbpTenant : AbpTenantBase, IFullAudited, IPassivable - where TUser : AbpUser - { - /// - /// "Default". - /// - public const string DefaultTenantName = "Default"; - - /// - /// "^[a-zA-Z][a-zA-Z0-9_-]{1,}$". - /// - public const string TenancyNameRegex = "^[a-zA-Z][a-zA-Z0-9_-]{1,}$"; - - /// - /// Max length of the property. - /// - public const int MaxNameLength = 128; - - /// - /// Current of the Tenant. - /// - public virtual Edition Edition { get; set; } - public virtual int? EditionId { get; set; } - - /// - /// Display name of the Tenant. - /// - [Required] - [StringLength(MaxNameLength)] - public virtual string Name { get; set; } - - /// - /// Is this tenant active? - /// If as tenant is not active, no user of this tenant can use the application. - /// - public virtual bool IsActive { get; set; } - - /// - /// Reference to the creator user of this entity. - /// - public virtual TUser CreatorUser { get; set; } - - /// - /// Reference to the last modifier user of this entity. - /// - public virtual TUser LastModifierUser { get; set; } - - /// - /// Reference to the deleter user of this entity. - /// - public virtual TUser DeleterUser { get; set; } - - /// - /// Creates a new tenant. - /// - protected AbpTenant() - { - IsActive = true; - } - - /// - /// Creates a new tenant. - /// - /// UNIQUE name of this Tenant - /// Display name of the Tenant - protected AbpTenant(string tenancyName, string name) - : this() - { - TenancyName = tenancyName; - Name = name; - } - } -} +using System.ComponentModel.DataAnnotations; +using Abp.Application.Editions; +using Abp.Authorization.Users; +using Abp.Domain.Entities; +using Abp.Domain.Entities.Auditing; + +namespace Abp.MultiTenancy +{ + /// + /// Represents a Tenant of the application. + /// + public abstract class AbpTenant : AbpTenantBase, IFullAudited + where TUser : AbpUserBase + { + /// + /// "Default". + /// + public const string DefaultTenantName = "Default"; + + /// + /// "^[a-zA-Z][a-zA-Z0-9_-]{1,}$". + /// + public const string TenancyNameRegex = "^[a-zA-Z][a-zA-Z0-9_-]{1,}$"; + + /// + /// Max length of the property. + /// + public const int MaxNameLength = 128; + + /// + /// Current of the Tenant. + /// + public virtual Edition Edition { get; set; } + public virtual int? EditionId { get; set; } + + /// + /// Display name of the Tenant. + /// + [Required] + [StringLength(MaxNameLength)] + public virtual string Name { get; set; } + + /// + /// Reference to the creator user of this entity. + /// + public virtual TUser CreatorUser { get; set; } + + /// + /// Reference to the last modifier user of this entity. + /// + public virtual TUser LastModifierUser { get; set; } + + /// + /// Reference to the deleter user of this entity. + /// + public virtual TUser DeleterUser { get; set; } + + /// + /// Creates a new tenant. + /// + protected AbpTenant() + { + IsActive = true; + } + + /// + /// Creates a new tenant. + /// + /// UNIQUE name of this Tenant + /// Display name of the Tenant + protected AbpTenant(string tenancyName, string name) + : this() + { + TenancyName = tenancyName; + Name = name; + } + } +} diff --git a/src/Abp.Zero/MultiTenancy/AbpTenantBase.cs b/src/Abp.Zero.Common/MultiTenancy/AbpTenantBase.cs similarity index 84% rename from src/Abp.Zero/MultiTenancy/AbpTenantBase.cs rename to src/Abp.Zero.Common/MultiTenancy/AbpTenantBase.cs index 2e035f0f..e5937ab0 100644 --- a/src/Abp.Zero/MultiTenancy/AbpTenantBase.cs +++ b/src/Abp.Zero.Common/MultiTenancy/AbpTenantBase.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities; using Abp.Domain.Entities.Auditing; using Abp.Runtime.Security; @@ -10,7 +11,7 @@ namespace Abp.MultiTenancy /// [Table("AbpTenants")] [MultiTenancySide(MultiTenancySides.Host)] - public abstract class AbpTenantBase : FullAuditedEntity + public abstract class AbpTenantBase : FullAuditedEntity, IPassivable { /// /// Max length of the property. @@ -37,5 +38,11 @@ public abstract class AbpTenantBase : FullAuditedEntity /// [StringLength(MaxConnectionStringLength)] public virtual string ConnectionString { get; set; } + + /// + /// Is this tenant active? + /// If as tenant is not active, no user of this tenant can use the application. + /// + public virtual bool IsActive { get; set; } } } \ No newline at end of file diff --git a/src/Abp.Zero/MultiTenancy/AbpTenantManager.cs b/src/Abp.Zero.Common/MultiTenancy/AbpTenantManager.cs similarity index 83% rename from src/Abp.Zero/MultiTenancy/AbpTenantManager.cs rename to src/Abp.Zero.Common/MultiTenancy/AbpTenantManager.cs index 31f2c853..d671a74e 100644 --- a/src/Abp.Zero/MultiTenancy/AbpTenantManager.cs +++ b/src/Abp.Zero.Common/MultiTenancy/AbpTenantManager.cs @@ -1,261 +1,247 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Abp.Application.Editions; -using Abp.Application.Features; -using Abp.Authorization.Users; -using Abp.Collections.Extensions; -using Abp.Domain.Repositories; -using Abp.Domain.Services; -using Abp.Domain.Uow; -using Abp.Events.Bus.Entities; -using Abp.Events.Bus.Handlers; -using Abp.IdentityFramework; -using Abp.Localization; -using Abp.Runtime.Caching; -using Abp.Zero; -using Microsoft.AspNet.Identity; - -namespace Abp.MultiTenancy -{ - /// - /// Tenant manager. - /// Implements domain logic for . - /// - /// Type of the application Tenant - /// Type of the application User - public abstract class AbpTenantManager : IDomainService, - IEventHandler>, - IEventHandler> - where TTenant : AbpTenant - where TUser : AbpUser - { - public AbpEditionManager EditionManager { get; set; } - - public ILocalizationManager LocalizationManager { get; set; } - - public ICacheManager CacheManager { get; set; } - - public IFeatureManager FeatureManager { get; set; } - - protected IRepository TenantRepository { get; set; } - - protected IRepository TenantFeatureRepository { get; set; } - - private readonly IAbpZeroFeatureValueStore _featureValueStore; - - protected AbpTenantManager( - IRepository tenantRepository, - IRepository tenantFeatureRepository, - AbpEditionManager editionManager, - IAbpZeroFeatureValueStore featureValueStore) - { - _featureValueStore = featureValueStore; - TenantRepository = tenantRepository; - TenantFeatureRepository = tenantFeatureRepository; - EditionManager = editionManager; - LocalizationManager = NullLocalizationManager.Instance; - } - - public virtual IQueryable Tenants { get { return TenantRepository.GetAll(); } } - - public virtual async Task CreateAsync(TTenant tenant) - { - if (await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenant.TenancyName) != null) - { - return AbpIdentityResult.Failed(string.Format(L("TenancyNameIsAlreadyTaken"), tenant.TenancyName)); - } - - var validationResult = await ValidateTenantAsync(tenant); - if (!validationResult.Succeeded) - { - return validationResult; - } - - await TenantRepository.InsertAsync(tenant); - return IdentityResult.Success; - } - - public async Task UpdateAsync(TTenant tenant) - { - if (await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenant.TenancyName && t.Id != tenant.Id) != null) - { - return AbpIdentityResult.Failed(string.Format(L("TenancyNameIsAlreadyTaken"), tenant.TenancyName)); - } - - await TenantRepository.UpdateAsync(tenant); - return IdentityResult.Success; - } - - public virtual async Task FindByIdAsync(int id) - { - return await TenantRepository.FirstOrDefaultAsync(id); - } - - public virtual async Task GetByIdAsync(int id) - { - var tenant = await FindByIdAsync(id); - if (tenant == null) - { - throw new AbpException("There is no tenant with id: " + id); - } - - return tenant; - } - - public virtual Task FindByTenancyNameAsync(string tenancyName) - { - return TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName); - } - - public virtual async Task DeleteAsync(TTenant tenant) - { - await TenantRepository.DeleteAsync(tenant); - return IdentityResult.Success; - } - - public Task GetFeatureValueOrNullAsync(int tenantId, string featureName) - { - return _featureValueStore.GetValueOrNullAsync(tenantId, featureName); - } - - public virtual async Task> GetFeatureValuesAsync(int tenantId) - { - var values = new List(); - - foreach (var feature in FeatureManager.GetAll()) - { - values.Add(new NameValue(feature.Name, await GetFeatureValueOrNullAsync(tenantId, feature.Name) ?? feature.DefaultValue)); - } - - return values; - } - - public virtual async Task SetFeatureValuesAsync(int tenantId, params NameValue[] values) - { - if (values.IsNullOrEmpty()) - { - return; - } - - foreach (var value in values) - { - await SetFeatureValueAsync(tenantId, value.Name, value.Value); - } - } - - [UnitOfWork] - public virtual async Task SetFeatureValueAsync(int tenantId, string featureName, string value) - { - await SetFeatureValueAsync(await GetByIdAsync(tenantId), featureName, value); - } - - [UnitOfWork] - public virtual async Task SetFeatureValueAsync(TTenant tenant, string featureName, string value) - { - //No need to change if it's already equals to the current value - if (await GetFeatureValueOrNullAsync(tenant.Id, featureName) == value) - { - return; - } - - //Get the current feature setting - var currentSetting = await TenantFeatureRepository.FirstOrDefaultAsync(f => f.TenantId == tenant.Id && f.Name == featureName); - - //Get the feature - var feature = FeatureManager.GetOrNull(featureName); - if (feature == null) - { - if (currentSetting != null) - { - await TenantFeatureRepository.DeleteAsync(currentSetting); - } - - return; - } - - //Determine default value - var defaultValue = tenant.EditionId.HasValue - ? (await EditionManager.GetFeatureValueOrNullAsync(tenant.EditionId.Value, featureName) ?? feature.DefaultValue) - : feature.DefaultValue; - - //No need to store value if it's default - if (value == defaultValue) - { - if (currentSetting != null) - { - await TenantFeatureRepository.DeleteAsync(currentSetting); - } - - return; - } - - //Insert/update the feature value - if (currentSetting == null) - { - await TenantFeatureRepository.InsertAsync(new TenantFeatureSetting(tenant.Id, featureName, value)); - } - else - { - currentSetting.Value = value; - } - } - - /// - /// Resets all custom feature settings for a tenant. - /// Tenant will have features according to it's edition. - /// - /// Tenant Id - public async Task ResetAllFeaturesAsync(int tenantId) - { - await TenantFeatureRepository.DeleteAsync(f => f.TenantId == tenantId); - } - - protected virtual async Task ValidateTenantAsync(TTenant tenant) - { - var nameValidationResult = await ValidateTenancyNameAsync(tenant.TenancyName); - if (!nameValidationResult.Succeeded) - { - return nameValidationResult; - } - - return IdentityResult.Success; - } - - protected virtual async Task ValidateTenancyNameAsync(string tenancyName) - { - if (!Regex.IsMatch(tenancyName, AbpTenant.TenancyNameRegex)) - { - return AbpIdentityResult.Failed(L("InvalidTenancyName")); - } - - return IdentityResult.Success; - } - - private string L(string name) - { - return LocalizationManager.GetString(AbpZeroConsts.LocalizationSourceName, name); - } - - public void HandleEvent(EntityChangedEventData eventData) - { - if (eventData.Entity.IsTransient()) - { - return; - } - - CacheManager.GetTenantFeatureCache().Remove(eventData.Entity.Id); - } - - [UnitOfWork] - public virtual void HandleEvent(EntityDeletedEventData eventData) - { - var relatedTenants = TenantRepository.GetAllList(t => t.EditionId == eventData.Entity.Id); - foreach (var relatedTenant in relatedTenants) - { - relatedTenant.EditionId = null; - } - } - } +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Abp.Application.Editions; +using Abp.Application.Features; +using Abp.Authorization.Users; +using Abp.Collections.Extensions; +using Abp.Domain.Repositories; +using Abp.Domain.Services; +using Abp.Domain.Uow; +using Abp.Events.Bus.Entities; +using Abp.Events.Bus.Handlers; +using Abp.Localization; +using Abp.Runtime.Caching; +using Abp.UI; +using Abp.Zero; + +namespace Abp.MultiTenancy +{ + /// + /// Tenant manager. + /// Implements domain logic for . + /// + /// Type of the application Tenant + /// Type of the application User + public abstract class AbpTenantManager : IDomainService, + IEventHandler>, + IEventHandler> + where TTenant : AbpTenant + where TUser : AbpUserBase + { + public AbpEditionManager EditionManager { get; set; } + + public ILocalizationManager LocalizationManager { get; set; } + + public ICacheManager CacheManager { get; set; } + + public IFeatureManager FeatureManager { get; set; } + + protected IRepository TenantRepository { get; set; } + + protected IRepository TenantFeatureRepository { get; set; } + + private readonly IAbpZeroFeatureValueStore _featureValueStore; + + protected AbpTenantManager( + IRepository tenantRepository, + IRepository tenantFeatureRepository, + AbpEditionManager editionManager, + IAbpZeroFeatureValueStore featureValueStore) + { + _featureValueStore = featureValueStore; + TenantRepository = tenantRepository; + TenantFeatureRepository = tenantFeatureRepository; + EditionManager = editionManager; + LocalizationManager = NullLocalizationManager.Instance; + } + + public virtual IQueryable Tenants { get { return TenantRepository.GetAll(); } } + + public virtual async Task CreateAsync(TTenant tenant) + { + await ValidateTenantAsync(tenant); + + if (await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenant.TenancyName) != null) + { + throw new UserFriendlyException(string.Format(L("TenancyNameIsAlreadyTaken"), tenant.TenancyName)); + } + + await TenantRepository.InsertAsync(tenant); + } + + public async Task UpdateAsync(TTenant tenant) + { + if (await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenant.TenancyName && t.Id != tenant.Id) != null) + { + throw new UserFriendlyException(string.Format(L("TenancyNameIsAlreadyTaken"), tenant.TenancyName)); + } + + await TenantRepository.UpdateAsync(tenant); + } + + public virtual async Task FindByIdAsync(int id) + { + return await TenantRepository.FirstOrDefaultAsync(id); + } + + public virtual async Task GetByIdAsync(int id) + { + var tenant = await FindByIdAsync(id); + if (tenant == null) + { + throw new AbpException("There is no tenant with id: " + id); + } + + return tenant; + } + + public virtual Task FindByTenancyNameAsync(string tenancyName) + { + return TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName); + } + + public virtual async Task DeleteAsync(TTenant tenant) + { + await TenantRepository.DeleteAsync(tenant); + } + + public Task GetFeatureValueOrNullAsync(int tenantId, string featureName) + { + return _featureValueStore.GetValueOrNullAsync(tenantId, featureName); + } + + public virtual async Task> GetFeatureValuesAsync(int tenantId) + { + var values = new List(); + + foreach (var feature in FeatureManager.GetAll()) + { + values.Add(new NameValue(feature.Name, await GetFeatureValueOrNullAsync(tenantId, feature.Name) ?? feature.DefaultValue)); + } + + return values; + } + + public virtual async Task SetFeatureValuesAsync(int tenantId, params NameValue[] values) + { + if (values.IsNullOrEmpty()) + { + return; + } + + foreach (var value in values) + { + await SetFeatureValueAsync(tenantId, value.Name, value.Value); + } + } + + [UnitOfWork] + public virtual async Task SetFeatureValueAsync(int tenantId, string featureName, string value) + { + await SetFeatureValueAsync(await GetByIdAsync(tenantId), featureName, value); + } + + [UnitOfWork] + public virtual async Task SetFeatureValueAsync(TTenant tenant, string featureName, string value) + { + //No need to change if it's already equals to the current value + if (await GetFeatureValueOrNullAsync(tenant.Id, featureName) == value) + { + return; + } + + //Get the current feature setting + var currentSetting = await TenantFeatureRepository.FirstOrDefaultAsync(f => f.TenantId == tenant.Id && f.Name == featureName); + + //Get the feature + var feature = FeatureManager.GetOrNull(featureName); + if (feature == null) + { + if (currentSetting != null) + { + await TenantFeatureRepository.DeleteAsync(currentSetting); + } + + return; + } + + //Determine default value + var defaultValue = tenant.EditionId.HasValue + ? (await EditionManager.GetFeatureValueOrNullAsync(tenant.EditionId.Value, featureName) ?? feature.DefaultValue) + : feature.DefaultValue; + + //No need to store value if it's default + if (value == defaultValue) + { + if (currentSetting != null) + { + await TenantFeatureRepository.DeleteAsync(currentSetting); + } + + return; + } + + //Insert/update the feature value + if (currentSetting == null) + { + await TenantFeatureRepository.InsertAsync(new TenantFeatureSetting(tenant.Id, featureName, value)); + } + else + { + currentSetting.Value = value; + } + } + + /// + /// Resets all custom feature settings for a tenant. + /// Tenant will have features according to it's edition. + /// + /// Tenant Id + public async Task ResetAllFeaturesAsync(int tenantId) + { + await TenantFeatureRepository.DeleteAsync(f => f.TenantId == tenantId); + } + + protected virtual async Task ValidateTenantAsync(TTenant tenant) + { + await ValidateTenancyNameAsync(tenant.TenancyName); + } + + protected virtual Task ValidateTenancyNameAsync(string tenancyName) + { + if (!Regex.IsMatch(tenancyName, AbpTenant.TenancyNameRegex)) + { + throw new UserFriendlyException(L("InvalidTenancyName")); + } + + return Task.FromResult(0); + } + + private string L(string name) + { + return LocalizationManager.GetString(AbpZeroConsts.LocalizationSourceName, name); + } + + public void HandleEvent(EntityChangedEventData eventData) + { + if (eventData.Entity.IsTransient()) + { + return; + } + + CacheManager.GetTenantFeatureCache().Remove(eventData.Entity.Id); + } + + [UnitOfWork] + public virtual void HandleEvent(EntityDeletedEventData eventData) + { + var relatedTenants = TenantRepository.GetAllList(t => t.EditionId == eventData.Entity.Id); + foreach (var relatedTenant in relatedTenants) + { + relatedTenant.EditionId = null; + } + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/MultiTenancy/AbpTenantManagerExtensions.cs b/src/Abp.Zero.Common/MultiTenancy/AbpTenantManagerExtensions.cs similarity index 72% rename from src/Abp.Zero/MultiTenancy/AbpTenantManagerExtensions.cs rename to src/Abp.Zero.Common/MultiTenancy/AbpTenantManagerExtensions.cs index 8f6bb93d..1873e755 100644 --- a/src/Abp.Zero/MultiTenancy/AbpTenantManagerExtensions.cs +++ b/src/Abp.Zero.Common/MultiTenancy/AbpTenantManagerExtensions.cs @@ -1,92 +1,91 @@ using System.Collections.Generic; using Abp.Authorization.Users; using Abp.Threading; -using Microsoft.AspNet.Identity; namespace Abp.MultiTenancy { public static class AbpTenantManagerExtensions { - public static IdentityResult Create(this AbpTenantManager tenantManager, TTenant tenant) + public static void Create(this AbpTenantManager tenantManager, TTenant tenant) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { - return AsyncHelper.RunSync(() => tenantManager.CreateAsync(tenant)); + AsyncHelper.RunSync(() => tenantManager.CreateAsync(tenant)); } - public static IdentityResult Update(this AbpTenantManager tenantManager, TTenant tenant) + public static void Update(this AbpTenantManager tenantManager, TTenant tenant) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { - return AsyncHelper.RunSync(() => tenantManager.UpdateAsync(tenant)); + AsyncHelper.RunSync(() => tenantManager.UpdateAsync(tenant)); } public static TTenant FindById(this AbpTenantManager tenantManager, int id) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { return AsyncHelper.RunSync(() => tenantManager.FindByIdAsync(id)); } public static TTenant GetById(this AbpTenantManager tenantManager, int id) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { return AsyncHelper.RunSync(() => tenantManager.GetByIdAsync(id)); } public static TTenant FindByTenancyName(this AbpTenantManager tenantManager, string tenancyName) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { return AsyncHelper.RunSync(() => tenantManager.FindByTenancyNameAsync(tenancyName)); } - public static IdentityResult Delete(this AbpTenantManager tenantManager, TTenant tenant) + public static void Delete(this AbpTenantManager tenantManager, TTenant tenant) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { - return AsyncHelper.RunSync(() => tenantManager.DeleteAsync(tenant)); + AsyncHelper.RunSync(() => tenantManager.DeleteAsync(tenant)); } public static string GetFeatureValueOrNull(this AbpTenantManager tenantManager, int tenantId, string featureName) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { return AsyncHelper.RunSync(() => tenantManager.GetFeatureValueOrNullAsync(tenantId, featureName)); } public static IReadOnlyList GetFeatureValues(this AbpTenantManager tenantManager, int tenantId) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { return AsyncHelper.RunSync(() => tenantManager.GetFeatureValuesAsync(tenantId)); } public static void SetFeatureValues(this AbpTenantManager tenantManager, int tenantId, params NameValue[] values) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { AsyncHelper.RunSync(() => tenantManager.SetFeatureValuesAsync(tenantId, values)); } public static void SetFeatureValue(this AbpTenantManager tenantManager, int tenantId, string featureName, string value) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { AsyncHelper.RunSync(() => tenantManager.SetFeatureValueAsync(tenantId, featureName, value)); } public static void SetFeatureValue(this AbpTenantManager tenantManager, TTenant tenant, string featureName, string value) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { AsyncHelper.RunSync(() => tenantManager.SetFeatureValueAsync(tenant, featureName, value)); } public static void ResetAllFeatures(this AbpTenantManager tenantManager, int tenantId) where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { AsyncHelper.RunSync(() => tenantManager.ResetAllFeaturesAsync(tenantId)); } diff --git a/src/Abp.Zero/MultiTenancy/DbPerTenantConnectionStringResolveArgs.cs b/src/Abp.Zero.Common/MultiTenancy/DbPerTenantConnectionStringResolveArgs.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/DbPerTenantConnectionStringResolveArgs.cs rename to src/Abp.Zero.Common/MultiTenancy/DbPerTenantConnectionStringResolveArgs.cs diff --git a/src/Abp.Zero/MultiTenancy/IAbpZeroDbMigrator.cs b/src/Abp.Zero.Common/MultiTenancy/IAbpZeroDbMigrator.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/IAbpZeroDbMigrator.cs rename to src/Abp.Zero.Common/MultiTenancy/IAbpZeroDbMigrator.cs diff --git a/src/Abp.Zero/MultiTenancy/IDbPerTenantConnectionStringResolver.cs b/src/Abp.Zero.Common/MultiTenancy/IDbPerTenantConnectionStringResolver.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/IDbPerTenantConnectionStringResolver.cs rename to src/Abp.Zero.Common/MultiTenancy/IDbPerTenantConnectionStringResolver.cs diff --git a/src/Abp.Zero/MultiTenancy/ITenantCache.cs b/src/Abp.Zero.Common/MultiTenancy/ITenantCache.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/ITenantCache.cs rename to src/Abp.Zero.Common/MultiTenancy/ITenantCache.cs diff --git a/src/Abp.Zero/MultiTenancy/TenantCache.cs b/src/Abp.Zero.Common/MultiTenancy/TenantCache.cs similarity index 99% rename from src/Abp.Zero/MultiTenancy/TenantCache.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantCache.cs index 967b43c4..70dabed7 100644 --- a/src/Abp.Zero/MultiTenancy/TenantCache.cs +++ b/src/Abp.Zero.Common/MultiTenancy/TenantCache.cs @@ -10,7 +10,7 @@ namespace Abp.MultiTenancy { public class TenantCache : ITenantCache, IEventHandler> where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { private readonly ICacheManager _cacheManager; private readonly IRepository _tenantRepository; diff --git a/src/Abp.Zero/MultiTenancy/TenantCacheItem.cs b/src/Abp.Zero.Common/MultiTenancy/TenantCacheItem.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/TenantCacheItem.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantCacheItem.cs diff --git a/src/Abp.Zero/MultiTenancy/TenantCacheManagerExtensions.cs b/src/Abp.Zero.Common/MultiTenancy/TenantCacheManagerExtensions.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/TenantCacheManagerExtensions.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantCacheManagerExtensions.cs diff --git a/src/Abp.Zero/MultiTenancy/TenantFeatureCacheItem.cs b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItem.cs similarity index 96% rename from src/Abp.Zero/MultiTenancy/TenantFeatureCacheItem.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItem.cs index 1e5ca843..10f6f50c 100644 --- a/src/Abp.Zero/MultiTenancy/TenantFeatureCacheItem.cs +++ b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItem.cs @@ -1,35 +1,35 @@ -using System; -using System.Collections.Generic; - -namespace Abp.MultiTenancy -{ - /// - /// Used to store features of a Tenant in the cache. - /// - [Serializable] - public class TenantFeatureCacheItem - { - /// - /// The cache store name. - /// - public const string CacheStoreName = "AbpZeroTenantFeatures"; - - /// - /// Edition of the tenant. - /// - public int? EditionId { get; set; } - - /// - /// Feature values. - /// - public IDictionary FeatureValues { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - public TenantFeatureCacheItem() - { - FeatureValues = new Dictionary(); - } - } +using System; +using System.Collections.Generic; + +namespace Abp.MultiTenancy +{ + /// + /// Used to store features of a Tenant in the cache. + /// + [Serializable] + public class TenantFeatureCacheItem + { + /// + /// The cache store name. + /// + public const string CacheStoreName = "AbpZeroTenantFeatures"; + + /// + /// Edition of the tenant. + /// + public int? EditionId { get; set; } + + /// + /// Feature values. + /// + public IDictionary FeatureValues { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public TenantFeatureCacheItem() + { + FeatureValues = new Dictionary(); + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/MultiTenancy/TenantFeatureCacheItemInvalidator.cs b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItemInvalidator.cs similarity index 97% rename from src/Abp.Zero/MultiTenancy/TenantFeatureCacheItemInvalidator.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItemInvalidator.cs index 4f66a1a9..a8ea2499 100644 --- a/src/Abp.Zero/MultiTenancy/TenantFeatureCacheItemInvalidator.cs +++ b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureCacheItemInvalidator.cs @@ -1,31 +1,31 @@ -using Abp.Dependency; -using Abp.Events.Bus.Entities; -using Abp.Events.Bus.Handlers; -using Abp.Runtime.Caching; - -namespace Abp.MultiTenancy -{ - /// - /// This class handles related events and invalidated tenant feature cache items if needed. - /// - public class TenantFeatureCacheItemInvalidator : - IEventHandler>, - ITransientDependency - { - private readonly ICacheManager _cacheManager; - - /// - /// Initializes a new instance of the class. - /// - /// The cache manager. - public TenantFeatureCacheItemInvalidator(ICacheManager cacheManager) - { - _cacheManager = cacheManager; - } - - public void HandleEvent(EntityChangedEventData eventData) - { - _cacheManager.GetTenantFeatureCache().Remove(eventData.Entity.TenantId); - } - } +using Abp.Dependency; +using Abp.Events.Bus.Entities; +using Abp.Events.Bus.Handlers; +using Abp.Runtime.Caching; + +namespace Abp.MultiTenancy +{ + /// + /// This class handles related events and invalidated tenant feature cache items if needed. + /// + public class TenantFeatureCacheItemInvalidator : + IEventHandler>, + ITransientDependency + { + private readonly ICacheManager _cacheManager; + + /// + /// Initializes a new instance of the class. + /// + /// The cache manager. + public TenantFeatureCacheItemInvalidator(ICacheManager cacheManager) + { + _cacheManager = cacheManager; + } + + public void HandleEvent(EntityChangedEventData eventData) + { + _cacheManager.GetTenantFeatureCache().Remove(eventData.Entity.TenantId); + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/MultiTenancy/TenantFeatureSetting.cs b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureSetting.cs similarity index 96% rename from src/Abp.Zero/MultiTenancy/TenantFeatureSetting.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantFeatureSetting.cs index 41e11faf..a832aea6 100644 --- a/src/Abp.Zero/MultiTenancy/TenantFeatureSetting.cs +++ b/src/Abp.Zero.Common/MultiTenancy/TenantFeatureSetting.cs @@ -1,36 +1,36 @@ -using Abp.Application.Features; -using Abp.Domain.Entities; - -namespace Abp.MultiTenancy -{ - /// - /// Feature setting for a Tenant (). - /// - public class TenantFeatureSetting : FeatureSetting, IMustHaveTenant - { - /// - /// Tenant's Id. - /// - public virtual int TenantId { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public TenantFeatureSetting() - { - - } - - /// - /// Initializes a new instance of the class. - /// - /// The tenant identifier. - /// Feature name. - /// Feature value. - public TenantFeatureSetting(int tenantId, string name, string value) - :base(name, value) - { - TenantId = tenantId; - } - } +using Abp.Application.Features; +using Abp.Domain.Entities; + +namespace Abp.MultiTenancy +{ + /// + /// Feature setting for a Tenant (). + /// + public class TenantFeatureSetting : FeatureSetting, IMustHaveTenant + { + /// + /// Tenant's Id. + /// + public virtual int TenantId { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public TenantFeatureSetting() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The tenant identifier. + /// Feature name. + /// Feature value. + public TenantFeatureSetting(int tenantId, string name, string value) + :base(name, value) + { + TenantId = tenantId; + } + } } \ No newline at end of file diff --git a/src/Abp.Zero/MultiTenancy/TenantStore.cs b/src/Abp.Zero.Common/MultiTenancy/TenantStore.cs similarity index 100% rename from src/Abp.Zero/MultiTenancy/TenantStore.cs rename to src/Abp.Zero.Common/MultiTenancy/TenantStore.cs diff --git a/src/Abp.Zero/Notifications/NotificationStore.cs b/src/Abp.Zero.Common/Notifications/NotificationStore.cs similarity index 100% rename from src/Abp.Zero/Notifications/NotificationStore.cs rename to src/Abp.Zero.Common/Notifications/NotificationStore.cs diff --git a/src/Abp.Zero/Organizations/IMayHaveOrganizationUnit.cs b/src/Abp.Zero.Common/Organizations/IMayHaveOrganizationUnit.cs similarity index 100% rename from src/Abp.Zero/Organizations/IMayHaveOrganizationUnit.cs rename to src/Abp.Zero.Common/Organizations/IMayHaveOrganizationUnit.cs diff --git a/src/Abp.Zero/Organizations/IMustHaveOrganizationUnit.cs b/src/Abp.Zero.Common/Organizations/IMustHaveOrganizationUnit.cs similarity index 100% rename from src/Abp.Zero/Organizations/IMustHaveOrganizationUnit.cs rename to src/Abp.Zero.Common/Organizations/IMustHaveOrganizationUnit.cs diff --git a/src/Abp.Zero/Organizations/IOrganizationUnitSettings.cs b/src/Abp.Zero.Common/Organizations/IOrganizationUnitSettings.cs similarity index 100% rename from src/Abp.Zero/Organizations/IOrganizationUnitSettings.cs rename to src/Abp.Zero.Common/Organizations/IOrganizationUnitSettings.cs diff --git a/src/Abp.Zero/Organizations/OrganizationUnit.cs b/src/Abp.Zero.Common/Organizations/OrganizationUnit.cs similarity index 100% rename from src/Abp.Zero/Organizations/OrganizationUnit.cs rename to src/Abp.Zero.Common/Organizations/OrganizationUnit.cs diff --git a/src/Abp.Zero/Organizations/OrganizationUnitManager.cs b/src/Abp.Zero.Common/Organizations/OrganizationUnitManager.cs similarity index 100% rename from src/Abp.Zero/Organizations/OrganizationUnitManager.cs rename to src/Abp.Zero.Common/Organizations/OrganizationUnitManager.cs diff --git a/src/Abp.Zero/Organizations/OrganizationUnitManagerExtensions.cs b/src/Abp.Zero.Common/Organizations/OrganizationUnitManagerExtensions.cs similarity index 100% rename from src/Abp.Zero/Organizations/OrganizationUnitManagerExtensions.cs rename to src/Abp.Zero.Common/Organizations/OrganizationUnitManagerExtensions.cs diff --git a/src/Abp.Zero/Organizations/OrganizationUnitSettings.cs b/src/Abp.Zero.Common/Organizations/OrganizationUnitSettings.cs similarity index 100% rename from src/Abp.Zero/Organizations/OrganizationUnitSettings.cs rename to src/Abp.Zero.Common/Organizations/OrganizationUnitSettings.cs diff --git a/src/Abp.Zero.Common/Properties/AssemblyInfo.cs b/src/Abp.Zero.Common/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..c133eeed --- /dev/null +++ b/src/Abp.Zero.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Abp.Zero")] +[assembly: InternalsVisibleTo("Abp.ZeroCore")] \ No newline at end of file diff --git a/src/Abp.Zero/Runtime/Caching/AbpZeroCacheManagerExtensions.cs b/src/Abp.Zero.Common/Runtime/Caching/AbpZeroCacheManagerExtensions.cs similarity index 97% rename from src/Abp.Zero/Runtime/Caching/AbpZeroCacheManagerExtensions.cs rename to src/Abp.Zero.Common/Runtime/Caching/AbpZeroCacheManagerExtensions.cs index e8f329cc..954105f5 100644 --- a/src/Abp.Zero/Runtime/Caching/AbpZeroCacheManagerExtensions.cs +++ b/src/Abp.Zero.Common/Runtime/Caching/AbpZeroCacheManagerExtensions.cs @@ -1,30 +1,30 @@ -using Abp.Application.Editions; -using Abp.Authorization.Roles; -using Abp.Authorization.Users; -using Abp.MultiTenancy; - -namespace Abp.Runtime.Caching -{ - public static class AbpZeroCacheManagerExtensions - { - public static ITypedCache GetUserPermissionCache(this ICacheManager cacheManager) - { - return cacheManager.GetCache(UserPermissionCacheItem.CacheStoreName); - } - - public static ITypedCache GetRolePermissionCache(this ICacheManager cacheManager) - { - return cacheManager.GetCache(RolePermissionCacheItem.CacheStoreName); - } - - public static ITypedCache GetTenantFeatureCache(this ICacheManager cacheManager) - { - return cacheManager.GetCache(TenantFeatureCacheItem.CacheStoreName); - } - - public static ITypedCache GetEditionFeatureCache(this ICacheManager cacheManager) - { - return cacheManager.GetCache(EditionfeatureCacheItem.CacheStoreName); - } - } -} +using Abp.Application.Editions; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.MultiTenancy; + +namespace Abp.Runtime.Caching +{ + public static class AbpZeroCacheManagerExtensions + { + public static ITypedCache GetUserPermissionCache(this ICacheManager cacheManager) + { + return cacheManager.GetCache(UserPermissionCacheItem.CacheStoreName); + } + + public static ITypedCache GetRolePermissionCache(this ICacheManager cacheManager) + { + return cacheManager.GetCache(RolePermissionCacheItem.CacheStoreName); + } + + public static ITypedCache GetTenantFeatureCache(this ICacheManager cacheManager) + { + return cacheManager.GetCache(TenantFeatureCacheItem.CacheStoreName); + } + + public static ITypedCache GetEditionFeatureCache(this ICacheManager cacheManager) + { + return cacheManager.GetCache(EditionfeatureCacheItem.CacheStoreName); + } + } +} diff --git a/src/Abp.Zero/Runtime/Session/AbpSessionExtensions.cs b/src/Abp.Zero.Common/Runtime/Session/AbpSessionExtensions.cs similarity index 100% rename from src/Abp.Zero/Runtime/Session/AbpSessionExtensions.cs rename to src/Abp.Zero.Common/Runtime/Session/AbpSessionExtensions.cs diff --git a/src/Abp.Zero.Common/Zero/AbpZeroCommonModule.cs b/src/Abp.Zero.Common/Zero/AbpZeroCommonModule.cs new file mode 100644 index 00000000..b945eedb --- /dev/null +++ b/src/Abp.Zero.Common/Zero/AbpZeroCommonModule.cs @@ -0,0 +1,105 @@ +using System.Linq; +using System.Reflection; +using Abp.Application.Features; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Dependency; +using Abp.Localization; +using Abp.Localization.Dictionaries; +using Abp.Localization.Dictionaries.Xml; +using Abp.Modules; +using Abp.MultiTenancy; +using Abp.Reflection; +using Abp.Zero.Configuration; +using Abp.Configuration.Startup; +using Abp.Reflection.Extensions; +using Castle.MicroKernel.Registration; + +namespace Abp.Zero +{ + /// + /// ABP zero core module. + /// + [DependsOn(typeof(AbpKernelModule))] + public class AbpZeroCommonModule : AbpModule + { + public override void PreInitialize() + { + IocManager.Register(); + IocManager.Register(); + IocManager.Register(); + IocManager.Register(); + IocManager.Register(); + + Configuration.ReplaceService(DependencyLifeStyle.Transient); + + Configuration.Settings.Providers.Add(); + + Configuration.Localization.Sources.Add( + new DictionaryBasedLocalizationSource( + AbpZeroConsts.LocalizationSourceName, + new XmlEmbeddedFileLocalizationDictionaryProvider( + typeof(AbpZeroCommonModule).GetAssembly(), "Abp.Zero.Localization.Source" + ))); + + IocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered; + } + + public override void Initialize() + { + FillMissingEntityTypes(); + + IocManager.RegisterAssemblyByConvention(typeof(AbpZeroCommonModule).GetAssembly()); + IocManager.Register(DependencyLifeStyle.Transient); //could not register conventionally + + RegisterTenantCache(); + } + + private void Kernel_ComponentRegistered(string key, Castle.MicroKernel.IHandler handler) + { + if (typeof(IAbpZeroFeatureValueStore).IsAssignableFrom(handler.ComponentModel.Implementation) && !IocManager.IsRegistered()) + { + IocManager.IocContainer.Register( + Component.For().ImplementedBy(handler.ComponentModel.Implementation).Named("AbpZeroFeatureValueStore").LifestyleTransient() + ); + } + } + + private void FillMissingEntityTypes() + { + using (var entityTypes = IocManager.ResolveAsDisposable()) + { + if (entityTypes.Object.User != null && + entityTypes.Object.Role != null && + entityTypes.Object.Tenant != null) + { + return; + } + + using (var typeFinder = IocManager.ResolveAsDisposable()) + { + var types = typeFinder.Object.FindAll(); + entityTypes.Object.Tenant = types.FirstOrDefault(t => typeof(AbpTenantBase).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract); + entityTypes.Object.Role = types.FirstOrDefault(t => typeof(AbpRoleBase).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract); + entityTypes.Object.User = types.FirstOrDefault(t => typeof(AbpUserBase).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract); + } + } + } + + private void RegisterTenantCache() + { + if (IocManager.IsRegistered()) + { + return; + } + + using (var entityTypes = IocManager.ResolveAsDisposable()) + { + var implType = typeof (TenantCache<,>) + .MakeGenericType(entityTypes.Object.Tenant, entityTypes.Object.User); + + IocManager.Register(typeof (ITenantCache), implType, DependencyLifeStyle.Transient); + } + } + } +} diff --git a/src/Abp.Zero/Zero/AbpZeroConsts.cs b/src/Abp.Zero.Common/Zero/AbpZeroConsts.cs similarity index 100% rename from src/Abp.Zero/Zero/AbpZeroConsts.cs rename to src/Abp.Zero.Common/Zero/AbpZeroConsts.cs diff --git a/src/Abp.Zero/Zero/Configuration/AbpZeroConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/AbpZeroConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/AbpZeroConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/AbpZeroEntityTypes.cs b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroEntityTypes.cs similarity index 98% rename from src/Abp.Zero/Zero/Configuration/AbpZeroEntityTypes.cs rename to src/Abp.Zero.Common/Zero/Configuration/AbpZeroEntityTypes.cs index d070c67c..8752858c 100644 --- a/src/Abp.Zero/Zero/Configuration/AbpZeroEntityTypes.cs +++ b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroEntityTypes.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Abp.Authorization.Roles; using Abp.Authorization.Users; using Abp.MultiTenancy; diff --git a/src/Abp.Zero/Zero/Configuration/AbpZeroSettingNames.cs b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingNames.cs similarity index 66% rename from src/Abp.Zero/Zero/Configuration/AbpZeroSettingNames.cs rename to src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingNames.cs index 44e97286..759a1276 100644 --- a/src/Abp.Zero/Zero/Configuration/AbpZeroSettingNames.cs +++ b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingNames.cs @@ -49,6 +49,34 @@ public static class TwoFactorLogin /// public const string IsRememberBrowserEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsRememberBrowserEnabled"; } + + public static class PasswordComplexity + { + /// + /// "Abp.Zero.UserManagement.PasswordComplexity.RequiredLength" + /// + public const string RequiredLength = "Abp.Zero.UserManagement.PasswordComplexity.RequiredLength"; + + /// + /// "Abp.Zero.UserManagement.PasswordComplexity.RequireNonAlphanumeric" + /// + public const string RequireNonAlphanumeric = "Abp.Zero.UserManagement.PasswordComplexity.RequireNonAlphanumeric"; + + /// + /// "Abp.Zero.UserManagement.PasswordComplexity.RequireLowercase" + /// + public const string RequireLowercase = "Abp.Zero.UserManagement.PasswordComplexity.RequireLowercase"; + + /// + /// "Abp.Zero.UserManagement.PasswordComplexity.RequireUppercase" + /// + public const string RequireUppercase = "Abp.Zero.UserManagement.PasswordComplexity.RequireUppercase"; + + /// + /// "Abp.Zero.UserManagement.PasswordComplexity.RequireDigit" + /// + public const string RequireDigit = "Abp.Zero.UserManagement.PasswordComplexity.RequireDigit"; + } } public static class OrganizationUnits diff --git a/src/Abp.Zero/Zero/Configuration/AbpZeroSettingProvider.cs b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingProvider.cs similarity index 67% rename from src/Abp.Zero/Zero/Configuration/AbpZeroSettingProvider.cs rename to src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingProvider.cs index 81e4c652..b9036a6a 100644 --- a/src/Abp.Zero/Zero/Configuration/AbpZeroSettingProvider.cs +++ b/src/Abp.Zero.Common/Zero/Configuration/AbpZeroSettingProvider.cs @@ -81,6 +81,46 @@ public override IEnumerable GetSettingDefinitions(SettingDefi scopes: SettingScopes.Application | SettingScopes.Tenant, isVisibleToClients: true ), + + new SettingDefinition( + AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireDigit, + "false", + new FixedLocalizableString("Require digit."), + scopes: SettingScopes.Application | SettingScopes.Tenant, + isVisibleToClients: true + ), + + new SettingDefinition( + AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireLowercase, + "false", + new FixedLocalizableString("Require lowercase."), + scopes: SettingScopes.Application | SettingScopes.Tenant, + isVisibleToClients: true + ), + + new SettingDefinition( + AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireNonAlphanumeric, + "false", + new FixedLocalizableString("Require non alphanumeric."), + scopes: SettingScopes.Application | SettingScopes.Tenant, + isVisibleToClients: true + ), + + new SettingDefinition( + AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireUppercase, + "false", + new FixedLocalizableString("Require upper case."), + scopes: SettingScopes.Application | SettingScopes.Tenant, + isVisibleToClients: true + ), + + new SettingDefinition( + AbpZeroSettingNames.UserManagement.PasswordComplexity.RequiredLength, + "3", + new FixedLocalizableString("Required length."), + scopes: SettingScopes.Application | SettingScopes.Tenant, + isVisibleToClients: true + ) }; } } diff --git a/src/Abp.Zero/Zero/Configuration/IAbpZeroConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/IAbpZeroConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/IAbpZeroConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/IAbpZeroConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/IAbpZeroEntityTypes.cs b/src/Abp.Zero.Common/Zero/Configuration/IAbpZeroEntityTypes.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/IAbpZeroEntityTypes.cs rename to src/Abp.Zero.Common/Zero/Configuration/IAbpZeroEntityTypes.cs diff --git a/src/Abp.Zero/Zero/Configuration/ILanguageManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/ILanguageManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/ILanguageManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/ILanguageManagementConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/IRoleManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/IRoleManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/IRoleManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/IRoleManagementConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/IUserManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/IUserManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/IUserManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/IUserManagementConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/LanguageManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/LanguageManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/LanguageManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/LanguageManagementConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/ModuleZeroConfigurationExtensions.cs b/src/Abp.Zero.Common/Zero/Configuration/ModuleZeroConfigurationExtensions.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/ModuleZeroConfigurationExtensions.cs rename to src/Abp.Zero.Common/Zero/Configuration/ModuleZeroConfigurationExtensions.cs diff --git a/src/Abp.Zero/Zero/Configuration/RoleManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/RoleManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/RoleManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/RoleManagementConfig.cs diff --git a/src/Abp.Zero/Zero/Configuration/StaticRoleDefinition.cs b/src/Abp.Zero.Common/Zero/Configuration/StaticRoleDefinition.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/StaticRoleDefinition.cs rename to src/Abp.Zero.Common/Zero/Configuration/StaticRoleDefinition.cs diff --git a/src/Abp.Zero/Zero/Configuration/UserManagementConfig.cs b/src/Abp.Zero.Common/Zero/Configuration/UserManagementConfig.cs similarity index 100% rename from src/Abp.Zero/Zero/Configuration/UserManagementConfig.cs rename to src/Abp.Zero.Common/Zero/Configuration/UserManagementConfig.cs diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-de.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-de.xml new file mode 100644 index 00000000..423e4e1b --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-de.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fa.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fa.xml new file mode 100644 index 00000000..38e1e1a9 --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fa.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fr.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fr.xml new file mode 100644 index 00000000..34437bec --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-fr.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-it.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-it.xml new file mode 100644 index 00000000..47662ac7 --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-it.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lt.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lt.xml new file mode 100644 index 00000000..8b59443e --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lt.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lv.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lv.xml new file mode 100644 index 00000000..12444bac --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-lv.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-pt-BR.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-pt-BR.xml new file mode 100644 index 00000000..ac8913e6 --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-pt-BR.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-ru.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-ru.xml new file mode 100644 index 00000000..24249d71 --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-ru.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-tr.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-tr.xml new file mode 100644 index 00000000..155208ba --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-tr.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-zh-CN.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-zh-CN.xml new file mode 100644 index 00000000..c4054e6f --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero-zh-CN.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero.xml b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero.xml new file mode 100644 index 00000000..70165656 --- /dev/null +++ b/src/Abp.Zero.Common/Zero/Localization/Source/AbpZero.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.Zero.EntityFramework/Abp.Zero.EntityFramework.csproj b/src/Abp.Zero.EntityFramework/Abp.Zero.EntityFramework.csproj index 30459956..574d412a 100644 --- a/src/Abp.Zero.EntityFramework/Abp.Zero.EntityFramework.csproj +++ b/src/Abp.Zero.EntityFramework/Abp.Zero.EntityFramework.csproj @@ -3,7 +3,7 @@ - net452 + net46 true Abp.Zero.EntityFramework Abp.Zero.EntityFramework @@ -18,26 +18,23 @@ - - lib/net452/ - true - + - + + lib/net46/ + true + - + - + - - - diff --git a/src/Abp.Zero.EntityFramework/EntityFramework/Extensions/EntityFrameworkModelBuilderExtensions.cs b/src/Abp.Zero.EntityFramework/EntityFramework/Extensions/EntityFrameworkModelBuilderExtensions.cs deleted file mode 100644 index 70970e88..00000000 --- a/src/Abp.Zero.EntityFramework/EntityFramework/Extensions/EntityFrameworkModelBuilderExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; -using System.Data.Entity.Infrastructure.Annotations; -using System.Data.Entity.ModelConfiguration.Configuration; - -namespace Abp.EntityFramework.Extensions -{ - //TODO: MOVE TO ABP - //TODO: We can create simpler extension methods to create indexes - //TODO: Check https://github.com/mj1856/EntityFramework.IndexingExtensions for example - internal static class EntityFrameworkModelBuilderExtensions - { - public static PrimitivePropertyConfiguration CreateIndex(this PrimitivePropertyConfiguration propertyConfiguration) - { - return propertyConfiguration.HasColumnAnnotation( - IndexAnnotation.AnnotationName, - new IndexAnnotation( - new IndexAttribute() - ) - ); - } - - public static PrimitivePropertyConfiguration CreateIndex(this PrimitivePropertyConfiguration propertyConfiguration, string name) - { - return propertyConfiguration.HasColumnAnnotation( - IndexAnnotation.AnnotationName, - new IndexAnnotation( - new IndexAttribute(name) - ) - ); - } - - public static PrimitivePropertyConfiguration CreateIndex(this PrimitivePropertyConfiguration propertyConfiguration, string name, int order) - { - return propertyConfiguration.HasColumnAnnotation( - IndexAnnotation.AnnotationName, - new IndexAnnotation( - new IndexAttribute(name, order) - ) - ); - } - } -} \ No newline at end of file diff --git a/src/Abp.Zero.EntityFrameworkCore/Abp.Zero.EntityFrameworkCore.csproj b/src/Abp.Zero.EntityFrameworkCore/Abp.Zero.EntityFrameworkCore.csproj index f989fc1f..f4b0a819 100644 --- a/src/Abp.Zero.EntityFrameworkCore/Abp.Zero.EntityFrameworkCore.csproj +++ b/src/Abp.Zero.EntityFrameworkCore/Abp.Zero.EntityFrameworkCore.csproj @@ -3,7 +3,7 @@ - net461 + net46 true Abp.Zero.EntityFrameworkCore Abp.Zero.EntityFrameworkCore @@ -18,8 +18,8 @@ - - lib/net461/ + + lib/net46/ true @@ -29,16 +29,13 @@ - + - + - - - diff --git a/src/Abp.Zero.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs b/src/Abp.Zero.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs index 4965f9fc..743f9ac2 100644 --- a/src/Abp.Zero.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs +++ b/src/Abp.Zero.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs @@ -27,7 +27,7 @@ protected AbpZeroDbMigrator( _dbContextResolver = dbContextResolver; } - public void CreateOrMigrateForHost() + public virtual void CreateOrMigrateForHost() { CreateOrMigrateForHost(null); } @@ -37,7 +37,7 @@ public virtual void CreateOrMigrateForHost(Action seedAction) CreateOrMigrate(null, seedAction); } - public void CreateOrMigrateForTenant(AbpTenantBase tenant) + public virtual void CreateOrMigrateForTenant(AbpTenantBase tenant) { CreateOrMigrateForTenant(tenant, null); } diff --git a/src/Abp.Zero.Ldap/Abp.Zero.Ldap.csproj b/src/Abp.Zero.Ldap/Abp.Zero.Ldap.csproj index cbe50825..51a70ac3 100644 --- a/src/Abp.Zero.Ldap/Abp.Zero.Ldap.csproj +++ b/src/Abp.Zero.Ldap/Abp.Zero.Ldap.csproj @@ -3,7 +3,7 @@ - net452 + net46 true Abp.Zero.Ldap Abp.Zero.Ldap @@ -19,17 +19,14 @@ - - lib/net452/ + + + lib/net46/ true - - - - - + diff --git a/src/Abp.Zero.Ldap/Ldap/AbpZeroLdapModule.cs b/src/Abp.Zero.Ldap/Ldap/AbpZeroLdapModule.cs index e69861b9..948499e1 100644 --- a/src/Abp.Zero.Ldap/Ldap/AbpZeroLdapModule.cs +++ b/src/Abp.Zero.Ldap/Ldap/AbpZeroLdapModule.cs @@ -9,7 +9,7 @@ namespace Abp.Zero.Ldap /// /// This module extends module zero to add LDAP authentication. /// - [DependsOn(typeof (AbpZeroCoreModule))] + [DependsOn(typeof (AbpZeroCommonModule))] public class AbpZeroLdapModule : AbpModule { public override void PreInitialize() diff --git a/src/Abp.Zero.Ldap/Ldap/Authentication/LdapAuthenticationSource.cs b/src/Abp.Zero.Ldap/Ldap/Authentication/LdapAuthenticationSource.cs index 93013f06..2e59cc2e 100644 --- a/src/Abp.Zero.Ldap/Ldap/Authentication/LdapAuthenticationSource.cs +++ b/src/Abp.Zero.Ldap/Ldap/Authentication/LdapAuthenticationSource.cs @@ -17,7 +17,7 @@ namespace Abp.Zero.Ldap.Authentication /// User type public abstract class LdapAuthenticationSource : DefaultExternalAuthenticationSource, ITransientDependency where TTenant : AbpTenant - where TUser : AbpUser, new() + where TUser : AbpUserBase, new() { /// /// LDAP diff --git a/src/Abp.Zero.NHibernate/Abp.Zero.NHibernate.csproj b/src/Abp.Zero.NHibernate/Abp.Zero.NHibernate.csproj index 00f6d970..e283ce0a 100644 --- a/src/Abp.Zero.NHibernate/Abp.Zero.NHibernate.csproj +++ b/src/Abp.Zero.NHibernate/Abp.Zero.NHibernate.csproj @@ -3,7 +3,7 @@ - net452 + net46 true Abp.Zero.NHibernate Abp.Zero.NHibernate @@ -18,22 +18,22 @@ - - lib/net452/ + + lib/net46/ true - + + - - + - + diff --git a/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/EditionFeatureSettingMap.cs b/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/EditionFeatureSettingMap.cs index 08c4232d..f230a349 100644 --- a/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/EditionFeatureSettingMap.cs +++ b/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/EditionFeatureSettingMap.cs @@ -9,7 +9,7 @@ public EditionFeatureSettingMap() { DiscriminatorValue("EditionFeatureSetting"); - References(x => x.Edition).Column("EditionId").Not.Nullable(); //TODO: Need to Map EditionId column? + References(x => x.Edition).Column("EditionId").Not.Nullable(); } } } \ No newline at end of file diff --git a/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/UserLoginAttemptMap.cs b/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/UserLoginAttemptMap.cs index e26d205a..6f0a0798 100644 --- a/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/UserLoginAttemptMap.cs +++ b/src/Abp.Zero.NHibernate/Zero/NHibernate/EntityMappings/UserLoginAttemptMap.cs @@ -1,3 +1,4 @@ +using Abp.Authorization; using Abp.Authorization.Users; using Abp.NHibernate.EntityMappings; diff --git a/src/Abp.Zero.Owin/Abp.Zero.Owin.1.5.0.nupkg b/src/Abp.Zero.Owin/Abp.Zero.Owin.1.5.0.nupkg deleted file mode 100644 index 1f6d0781..00000000 Binary files a/src/Abp.Zero.Owin/Abp.Zero.Owin.1.5.0.nupkg and /dev/null differ diff --git a/src/Abp.Zero.Owin/Abp.Zero.Owin.csproj b/src/Abp.Zero.Owin/Abp.Zero.Owin.csproj index 8f7abcc8..056601c6 100644 --- a/src/Abp.Zero.Owin/Abp.Zero.Owin.csproj +++ b/src/Abp.Zero.Owin/Abp.Zero.Owin.csproj @@ -3,7 +3,7 @@ - net452 + net46 true Abp.Zero.Owin Abp.Zero.Owin @@ -15,8 +15,8 @@ - - lib/net452/ + + lib/net46/ true @@ -29,7 +29,7 @@ - + diff --git a/src/Abp.Zero.Owin/ClaimsIdentityExtensions.cs b/src/Abp.Zero.Owin/ClaimsIdentityExtensions.cs deleted file mode 100644 index 340901b1..00000000 --- a/src/Abp.Zero.Owin/ClaimsIdentityExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Security.Claims; -using System.Security.Principal; -using Abp.Runtime.Security; -using Microsoft.AspNet.Identity; - -namespace Abp -{ - //TODO: Use from ABP after ABP v1.0! - public static class ClaimsIdentityExtensions - { - public static int? GetTenantId(this IIdentity identity) - { - var claimsIdentity = identity as ClaimsIdentity; - - var tenantIdOrNull = claimsIdentity?.FindFirstValue(AbpClaimTypes.TenantId); - if (tenantIdOrNull == null) - { - return null; - } - - return Convert.ToInt32(tenantIdOrNull); - } - } -} \ No newline at end of file diff --git a/src/Abp.Zero/Abp.Zero.1.5.0.nupkg b/src/Abp.Zero/Abp.Zero.1.5.0.nupkg deleted file mode 100644 index 11e0cf5b..00000000 Binary files a/src/Abp.Zero/Abp.Zero.1.5.0.nupkg and /dev/null differ diff --git a/src/Abp.Zero/Abp.Zero.csproj b/src/Abp.Zero/Abp.Zero.csproj index 6394869c..7c3ef8c6 100644 --- a/src/Abp.Zero/Abp.Zero.csproj +++ b/src/Abp.Zero/Abp.Zero.csproj @@ -3,7 +3,7 @@ - net452 + net46 true Abp.Zero Abp.Zero @@ -18,23 +18,39 @@ - - - lib/net452/ + + + + + + + + + + + + + + + + lib/net46/ true - - + + + + + diff --git a/src/Abp.Zero/Authorization/AbpLoginManager.cs b/src/Abp.Zero/Authorization/AbpLoginManager.cs index 4ebb691f..c806d91a 100644 --- a/src/Abp.Zero/Authorization/AbpLoginManager.cs +++ b/src/Abp.Zero/Authorization/AbpLoginManager.cs @@ -20,8 +20,7 @@ namespace Abp.Authorization { - //SignInManager, - public class AbpLogInManager : ITransientDependency + public abstract class AbpLogInManager : ITransientDependency where TTenant : AbpTenant where TRole : AbpRole, new() where TUser : AbpUser @@ -38,7 +37,7 @@ public class AbpLogInManager : ITransientDependency protected IIocResolver IocResolver { get; } protected AbpRoleManager RoleManager { get; } - public AbpLogInManager( + protected AbpLogInManager( AbpUserManager userManager, IMultiTenancyConfig multiTenancyConfig, IRepository tenantRepository, diff --git a/src/Abp.Zero/Authorization/Roles/AbpRole.cs b/src/Abp.Zero/Authorization/Roles/AbpRole.cs index 91b5f6a5..36553661 100644 --- a/src/Abp.Zero/Authorization/Roles/AbpRole.cs +++ b/src/Abp.Zero/Authorization/Roles/AbpRole.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Authorization.Users; +using Abp.Authorization.Users; using Abp.Domain.Entities.Auditing; +using Microsoft.AspNet.Identity; namespace Abp.Authorization.Roles { @@ -12,85 +9,43 @@ namespace Abp.Authorization.Roles /// /// /// Application should use permissions to check if user is granted to perform an operation. - /// Checking 'if a user has a role' is not possible until the role is static (). + /// Checking 'if a user has a role' is not possible until the role is static (). /// Static roles can be used in the code and can not be deleted by users. /// Non-static (dynamic) roles can be added/removed by users and we can not know their name while coding. /// A user can have multiple roles. Thus, user will have all permissions of all assigned roles. /// - public abstract class AbpRole : AbpRoleBase, IFullAudited + public abstract class AbpRole : AbpRoleBase, IRole, IFullAudited where TUser : AbpUser { - /// - /// Maximum length of the property. - /// - public const int MaxDisplayNameLength = 64; - - /// - /// Display name of this role. - /// - [Required] - [StringLength(MaxDisplayNameLength)] - public virtual string DisplayName { get; set; } - - /// - /// Is this a static role? - /// Static roles can not be deleted, can not change their name. - /// They can be used programmatically. - /// - public virtual bool IsStatic { get; set; } - - /// - /// Is this role will be assigned to new users as default? - /// - public virtual bool IsDefault { get; set; } - - /// - /// List of permissions of the role. - /// - [ForeignKey("RoleId")] - public virtual ICollection Permissions { get; set; } - public virtual TUser DeleterUser { get; set; } public virtual TUser CreatorUser { get; set; } public virtual TUser LastModifierUser { get; set; } - /// - /// Creates a new object. - /// - public AbpRole() + protected AbpRole() { - Name = Guid.NewGuid().ToString("N"); } /// - /// Creates a new object. + /// Creates a new object. /// /// TenantId or null (if this is not a tenant-level role) /// Display name of the role - public AbpRole(int? tenantId, string displayName) - : this() + protected AbpRole(int? tenantId, string displayName) + : base(tenantId, displayName) { - TenantId = tenantId; - DisplayName = displayName; } /// - /// Creates a new object. + /// Creates a new object. /// /// TenantId or null (if this is not a tenant-level role) /// Unique role name /// Display name of the role - public AbpRole(int? tenantId, string name, string displayName) - : this(tenantId, displayName) - { - Name = name; - } - - public override string ToString() + protected AbpRole(int? tenantId, string name, string displayName) + : base(tenantId, name, displayName) { - return string.Format("[Role {0}, Name={1}]", Id, Name); } } -} +} \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Roles/AbpRoleBase.cs b/src/Abp.Zero/Authorization/Roles/AbpRoleBase.cs deleted file mode 100644 index a8a25d5f..00000000 --- a/src/Abp.Zero/Authorization/Roles/AbpRoleBase.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities; -using Abp.Domain.Entities.Auditing; -using Microsoft.AspNet.Identity; - -namespace Abp.Authorization.Roles -{ - /// - /// Base class for role. - /// - [Table("AbpRoles")] - public abstract class AbpRoleBase : FullAuditedEntity, IRole, IMayHaveTenant - { - /// - /// Maximum length of the property. - /// - public const int MaxNameLength = 32; - - /// - /// Tenant's Id, if this role is a tenant-level role. Null, if not. - /// - public virtual int? TenantId { get; set; } - - /// - /// Unique name of this role. - /// - [Required] - [StringLength(MaxNameLength)] - public virtual string Name { get; set; } - } -} \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Roles/AbpRoleManager.cs b/src/Abp.Zero/Authorization/Roles/AbpRoleManager.cs index 9e0394d7..f3578dfb 100644 --- a/src/Abp.Zero/Authorization/Roles/AbpRoleManager.cs +++ b/src/Abp.Zero/Authorization/Roles/AbpRoleManager.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -32,16 +31,16 @@ public abstract class AbpRoleManager public IRoleManagementConfig RoleManagementConfig { get; private set; } - private IRolePermissionStore RolePermissionStore + private IRolePermissionStore RolePermissionStore { get { - if (!(Store is IRolePermissionStore)) + if (!(Store is IRolePermissionStore)) { throw new AbpException("Store is not IRolePermissionStore"); } - return Store as IRolePermissionStore; + return Store as IRolePermissionStore; } } @@ -72,58 +71,6 @@ protected AbpRoleManager( LocalizationManager = NullLocalizationManager.Instance; } - #region Obsolete methods - - /// - /// Checks if a role has a permission. - /// - /// The role's name to check it's permission - /// Name of the permission - /// True, if the role has the permission - [Obsolete("Use IsGrantedAsync instead.")] - public virtual async Task HasPermissionAsync(string roleName, string permissionName) - { - return await IsGrantedAsync((await GetRoleByNameAsync(roleName)).Id, _permissionManager.GetPermission(permissionName)); - } - - /// - /// Checks if a role has a permission. - /// - /// The role's id to check it's permission - /// Name of the permission - /// True, if the role has the permission - [Obsolete("Use IsGrantedAsync instead.")] - public virtual async Task HasPermissionAsync(int roleId, string permissionName) - { - return await IsGrantedAsync(roleId, _permissionManager.GetPermission(permissionName)); - } - - /// - /// Checks if a role has a permission. - /// - /// The role - /// The permission - /// True, if the role has the permission - [Obsolete("Use IsGrantedAsync instead.")] - public Task HasPermissionAsync(TRole role, Permission permission) - { - return IsGrantedAsync(role.Id, permission); - } - - /// - /// Checks if a role has a permission. - /// - /// role id - /// The permission - /// True, if the role has the permission - [Obsolete("Use IsGrantedAsync instead.")] - public Task HasPermissionAsync(int roleId, Permission permission) - { - return IsGrantedAsync(roleId, permission); - } - - #endregion - /// /// Checks if a role is granted for a permission. /// diff --git a/src/Abp.Zero/Authorization/Roles/AbpRoleStore.cs b/src/Abp.Zero/Authorization/Roles/AbpRoleStore.cs index a8db7b93..29c07199 100644 --- a/src/Abp.Zero/Authorization/Roles/AbpRoleStore.cs +++ b/src/Abp.Zero/Authorization/Roles/AbpRoleStore.cs @@ -13,7 +13,7 @@ namespace Abp.Authorization.Roles /// public abstract class AbpRoleStore : IQueryableRoleStore, - IRolePermissionStore, + IRolePermissionStore, ITransientDependency diff --git a/src/Abp.Zero/Authorization/Users/AbpLoginResult.cs b/src/Abp.Zero/Authorization/Users/AbpLoginResult.cs index c04ee795..31c90a94 100644 --- a/src/Abp.Zero/Authorization/Users/AbpLoginResult.cs +++ b/src/Abp.Zero/Authorization/Users/AbpLoginResult.cs @@ -5,7 +5,7 @@ namespace Abp.Authorization.Users { public class AbpLoginResult where TTenant : AbpTenant - where TUser : AbpUser + where TUser : AbpUserBase { public AbpLoginResultType Result { get; private set; } diff --git a/src/Abp.Zero/Authorization/Users/AbpUser.cs b/src/Abp.Zero/Authorization/Users/AbpUser.cs index 3f7ebca2..b5d5eabf 100644 --- a/src/Abp.Zero/Authorization/Users/AbpUser.cs +++ b/src/Abp.Zero/Authorization/Users/AbpUser.cs @@ -1,212 +1,18 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Configuration; -using Abp.Domain.Entities; using Abp.Domain.Entities.Auditing; -using Abp.Extensions; +using Microsoft.AspNet.Identity; namespace Abp.Authorization.Users { /// /// Represents a user. /// - public abstract class AbpUser : AbpUserBase, IFullAudited, IPassivable + public abstract class AbpUser : AbpUserBase, IUser, IFullAudited where TUser : AbpUser { - /// - /// UserName of the admin. - /// admin can not be deleted and UserName of the admin can not be changed. - /// - public const string AdminUserName = "admin"; - - /// - /// Maximum length of the property. - /// - public const int MaxNameLength = 32; - - /// - /// Maximum length of the property. - /// - public const int MaxSurnameLength = 32; - - /// - /// Maximum length of the property. - /// - public const int MaxPasswordLength = 128; - - /// - /// Maximum length of the without hashed. - /// - public const int MaxPlainPasswordLength = 32; - - /// - /// Maximum length of the property. - /// - public const int MaxEmailConfirmationCodeLength = 328; - - /// - /// Maximum length of the property. - /// - public const int MaxPasswordResetCodeLength = 328; - - /// - /// Maximum length of the property. - /// - public const int MaxAuthenticationSourceLength = 64; - - /// - /// Authorization source name. - /// It's set to external authentication source name if created by an external source. - /// Default: null. - /// - [MaxLength(MaxAuthenticationSourceLength)] - public virtual string AuthenticationSource { get; set; } - - /// - /// Name of the user. - /// - [Required] - [StringLength(MaxNameLength)] - public virtual string Name { get; set; } - - /// - /// Surname of the user. - /// - [Required] - [StringLength(MaxSurnameLength)] - public virtual string Surname { get; set; } - - /// - /// Return full name (Name Surname ) - /// - [NotMapped] - public virtual string FullName { get { return this.Name + " " + this.Surname; } } - - /// - /// Password of the user. - /// - [Required] - [StringLength(MaxPasswordLength)] - public virtual string Password { get; set; } - - /// - /// Is the confirmed. - /// - public virtual bool IsEmailConfirmed { get; set; } - - /// - /// Confirmation code for email. - /// - [StringLength(MaxEmailConfirmationCodeLength)] - public virtual string EmailConfirmationCode { get; set; } - - /// - /// Reset code for password. - /// It's not valid if it's null. - /// It's for one usage and must be set to null after reset. - /// - [StringLength(MaxPasswordResetCodeLength)] - public virtual string PasswordResetCode { get; set; } - - /// - /// Lockout end date. - /// - public virtual DateTime? LockoutEndDateUtc { get; set; } - - /// - /// Gets or sets the access failed count. - /// - public virtual int AccessFailedCount { get; set; } - - /// - /// Gets or sets the lockout enabled. - /// - public virtual bool IsLockoutEnabled { get; set; } - - /// - /// Gets or sets the phone number. - /// - public virtual string PhoneNumber {get; set; } - - /// - /// Is the confirmed. - /// - public virtual bool IsPhoneNumberConfirmed { get; set; } - - /// - /// Gets or sets the security stamp. - /// - public virtual string SecurityStamp { get; set; } - - /// - /// Is two factor auth enabled. - /// - public virtual bool IsTwoFactorEnabled { get; set; } - - /// - /// Is this user active? - /// If as user is not active, he/she can not use the application. - /// - public virtual bool IsActive { get; set; } - - /// - /// Login definitions for this user. - /// - [ForeignKey("UserId")] - public virtual ICollection Logins { get; set; } - - /// - /// Roles of this user. - /// - [ForeignKey("UserId")] - public virtual ICollection Roles { get; set; } - - /// - /// Claims of this user. - /// - [ForeignKey("UserId")] - public virtual ICollection Claims { get; set; } - - /// - /// Permission definitions for this user. - /// - [ForeignKey("UserId")] - public virtual ICollection Permissions { get; set; } - - /// - /// Settings for this user. - /// - [ForeignKey("UserId")] - public virtual ICollection Settings { get; set; } - public virtual TUser DeleterUser { get; set; } public virtual TUser CreatorUser { get; set; } public virtual TUser LastModifierUser { get; set; } - - protected AbpUser() - { - IsActive = true; - IsLockoutEnabled = true; - SecurityStamp = SequentialGuidGenerator.Instance.Create().ToString(); - } - - public virtual void SetNewPasswordResetCode() - { - PasswordResetCode = Guid.NewGuid().ToString("N").Truncate(MaxPasswordResetCodeLength); - } - - public virtual void SetNewEmailConfirmationCode() - { - EmailConfirmationCode = Guid.NewGuid().ToString("N").Truncate(MaxEmailConfirmationCodeLength); - } - - public override string ToString() - { - return string.Format("[User {0}] {1}", Id, UserName); - } } } \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/AbpUserBase.cs b/src/Abp.Zero/Authorization/Users/AbpUserBase.cs deleted file mode 100644 index c22e3b6f..00000000 --- a/src/Abp.Zero/Authorization/Users/AbpUserBase.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using Abp.Domain.Entities; -using Abp.Domain.Entities.Auditing; -using Microsoft.AspNet.Identity; - -namespace Abp.Authorization.Users -{ - /// - /// Base class for user. - /// - [Table("AbpUsers")] - public abstract class AbpUserBase : FullAuditedEntity, IUser, IMayHaveTenant - { - /// - /// Maximum length of the property. - /// - public const int MaxUserNameLength = 32; - - /// - /// Maximum length of the property. - /// - public const int MaxEmailAddressLength = 256; - - /// - /// User name. - /// User name must be unique for it's tenant. - /// - [Required] - [StringLength(MaxUserNameLength)] - public virtual string UserName { get; set; } - - /// - /// Tenant Id of this user. - /// - public virtual int? TenantId { get; set; } - - /// - /// Email address of the user. - /// Email address must be unique for it's tenant. - /// - [Required] - [StringLength(MaxEmailAddressLength)] - public virtual string EmailAddress { get; set; } - - /// - /// The last time this user entered to the system. - /// - public virtual DateTime? LastLoginTime { get; set; } - - /// - /// Creates from this User. - /// - /// - public virtual UserIdentifier ToUserIdentifier() - { - return new UserIdentifier(TenantId, Id); - } - } -} \ No newline at end of file diff --git a/src/Abp.Zero/Authorization/Users/AbpUserManager.cs b/src/Abp.Zero/Authorization/Users/AbpUserManager.cs index 555104f3..53ebc615 100644 --- a/src/Abp.Zero/Authorization/Users/AbpUserManager.cs +++ b/src/Abp.Zero/Authorization/Users/AbpUserManager.cs @@ -162,16 +162,14 @@ public virtual async Task IsGrantedAsync(long userId, Permission permissio return false; } } - - //User not found - if (await FindByIdAsync(userId) == null) + + //Get cached user permissions + var cacheItem = await GetUserPermissionCacheItemAsync(userId); + if (cacheItem == null) { return false; } - //Get cached user permissions - var cacheItem = await GetUserPermissionCacheItemAsync(userId); - //Check for user-specific value if (cacheItem.GrantedPermissions.Contains(permission.Name)) { @@ -382,7 +380,7 @@ public virtual async Task CheckDuplicateUsernameOrEmailAddressAs var user = (await FindByNameAsync(userName)); if (user != null && user.Id != expectedUserId) { - return AbpIdentityResult.Failed(string.Format(L("Identity.DuplicateName"), userName)); + return AbpIdentityResult.Failed(string.Format(L("Identity.DuplicateUserName"), userName)); } user = (await FindByEmailAsync(emailAddress)); @@ -650,6 +648,12 @@ private async Task GetUserPermissionCacheItemAsync(long var cacheKey = userId + "@" + (GetCurrentTenantId() ?? 0); return await _cacheManager.GetUserPermissionCache().GetAsync(cacheKey, async () => { + var user = await FindByIdAsync(userId); + if (user == null) + { + return null; + } + var newCacheItem = new UserPermissionCacheItem(userId); foreach (var roleName in await GetRolesAsync(userId)) diff --git a/src/Abp.Zero/Authorization/Users/AbpUserManagerExtensions.cs b/src/Abp.Zero/Authorization/Users/AbpUserManagerExtensions.cs index 05f0ad82..fb957e68 100644 --- a/src/Abp.Zero/Authorization/Users/AbpUserManagerExtensions.cs +++ b/src/Abp.Zero/Authorization/Users/AbpUserManagerExtensions.cs @@ -1,6 +1,5 @@ using System; using Abp.Authorization.Roles; -using Abp.MultiTenancy; using Abp.Threading; namespace Abp.Authorization.Users diff --git a/src/Abp.Zero/IdentityFramework/IdentityResultExtensions.cs b/src/Abp.Zero/IdentityFramework/IdentityResultExtensions.cs index 2a592744..6ce3d417 100644 --- a/src/Abp.Zero/IdentityFramework/IdentityResultExtensions.cs +++ b/src/Abp.Zero/IdentityFramework/IdentityResultExtensions.cs @@ -23,7 +23,7 @@ private static readonly Dictionary IdentityLocalizations {"User name {0} is invalid, can only contain letters or digits.", "Identity.InvalidUserName"}, {"Passwords must be at least {0} characters.", "Identity.PasswordTooShort"}, {"{0} cannot be null or empty.", "Identity.PropertyTooShort"}, - {"Name {0} is already taken.", "Identity.DuplicateName"}, + {"Name {0} is already taken.", "Identity.DuplicateUserName"}, {"User already has a password set.", "Identity.UserAlreadyHasPassword"}, {"Passwords must have at least one non letter or digit character.", "Identity.PasswordRequireNonLetterOrDigit"}, {"UserId not found.", "Identity.UserIdNotFound"}, diff --git a/src/Abp.Zero/Zero/AbpZeroCoreModule.cs b/src/Abp.Zero/Zero/AbpZeroCoreModule.cs index 4a0a72a7..a8888d3e 100644 --- a/src/Abp.Zero/Zero/AbpZeroCoreModule.cs +++ b/src/Abp.Zero/Zero/AbpZeroCoreModule.cs @@ -1,104 +1,28 @@ -using System.Linq; -using System.Reflection; -using Abp.Application.Features; -using Abp.Authorization.Roles; -using Abp.Authorization.Users; -using Abp.Dependency; -using Abp.Localization; -using Abp.Localization.Dictionaries; +using System.Reflection; using Abp.Localization.Dictionaries.Xml; +using Abp.Localization.Sources; using Abp.Modules; -using Abp.MultiTenancy; -using Abp.Reflection; -using Abp.Zero.Configuration; -using Abp.Configuration.Startup; -using Castle.MicroKernel.Registration; namespace Abp.Zero { - /// - /// ABP zero core module. - /// - [DependsOn(typeof(AbpKernelModule))] + [DependsOn(typeof(AbpZeroCommonModule))] public class AbpZeroCoreModule : AbpModule { public override void PreInitialize() { - IocManager.Register(); - IocManager.Register(); - IocManager.Register(); - IocManager.Register(); - IocManager.Register(); - - Configuration.ReplaceService(DependencyLifeStyle.Transient); - - Configuration.Settings.Providers.Add(); - - Configuration.Localization.Sources.Add( - new DictionaryBasedLocalizationSource( + Configuration.Localization.Sources.Extensions.Add( + new LocalizationSourceExtensionInfo( AbpZeroConsts.LocalizationSourceName, new XmlEmbeddedFileLocalizationDictionaryProvider( - Assembly.GetExecutingAssembly(), "Abp.Zero.Localization.Source" - ))); - - IocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered; + Assembly.GetExecutingAssembly(), "Abp.Zero.Localization.SourceExt" + ) + ) + ); } public override void Initialize() { - FillMissingEntityTypes(); - IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); - IocManager.Register(DependencyLifeStyle.Transient); //could not register conventionally - - RegisterTenantCache(); - } - - private void Kernel_ComponentRegistered(string key, Castle.MicroKernel.IHandler handler) - { - if (typeof(IAbpZeroFeatureValueStore).IsAssignableFrom(handler.ComponentModel.Implementation) && !IocManager.IsRegistered()) - { - IocManager.IocContainer.Register( - Component.For().ImplementedBy(handler.ComponentModel.Implementation).Named("AbpZeroFeatureValueStore").LifestyleTransient() - ); - } - } - - private void FillMissingEntityTypes() - { - using (var entityTypes = IocManager.ResolveAsDisposable()) - { - if (entityTypes.Object.User != null && - entityTypes.Object.Role != null && - entityTypes.Object.Tenant != null) - { - return; - } - - using (var typeFinder = IocManager.ResolveAsDisposable()) - { - var types = typeFinder.Object.FindAll(); - entityTypes.Object.Tenant = types.FirstOrDefault(t => typeof(AbpTenantBase).IsAssignableFrom(t) && !t.IsAbstract); - entityTypes.Object.Role = types.FirstOrDefault(t => typeof(AbpRoleBase).IsAssignableFrom(t) && !t.IsAbstract); - entityTypes.Object.User = types.FirstOrDefault(t => typeof(AbpUserBase).IsAssignableFrom(t) && !t.IsAbstract); - } - } - } - - private void RegisterTenantCache() - { - if (IocManager.IsRegistered()) - { - return; - } - - using (var entityTypes = IocManager.ResolveAsDisposable()) - { - var implType = typeof (TenantCache<,>) - .MakeGenericType(entityTypes.Object.Tenant, entityTypes.Object.User); - - IocManager.Register(typeof (ITenantCache), implType, DependencyLifeStyle.Transient); - } } } } diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-de.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-de.xml similarity index 75% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-de.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-de.xml index a6a77072..6955837b 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-de.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-de.xml @@ -22,12 +22,5 @@ - - - - - - - \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-fa.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fa.xml similarity index 70% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-fa.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fa.xml index 82f16826..8e7850ba 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-fa.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fa.xml @@ -22,14 +22,6 @@ - - - - - - - - diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-fr.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fr.xml similarity index 68% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-fr.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fr.xml index e105ee0e..7d4bf0ad 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-fr.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-fr.xml @@ -22,13 +22,5 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-it.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-it.xml similarity index 69% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-it.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-it.xml index 9224eacb..3be07a03 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-it.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-it.xml @@ -22,13 +22,5 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-lt.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lt.xml similarity index 63% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-lt.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lt.xml index 0eec7fb4..48ec0df8 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-lt.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lt.xml @@ -1,39 +1,31 @@ - + - + - + - + - + - + - - - - - - - - El. paštas AbpZeroTemplate Saugumo Kodas Jūsų saugumo kodas: {0} Sms Jusu saugumo kodas: {0} - + \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-lv.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lv.xml similarity index 69% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-lv.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lv.xml index 924f3682..da622fda 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-lv.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-lv.xml @@ -22,13 +22,10 @@ - - - - - - - - + + + + + - + \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-pt-BR.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-pt-BR.xml similarity index 68% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-pt-BR.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-pt-BR.xml index a4082a74..ce31d175 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-pt-BR.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-pt-BR.xml @@ -22,13 +22,5 @@ - - - - - - - - diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-ru.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-ru.xml similarity index 71% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-ru.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-ru.xml index 3cb08c12..a0315945 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-ru.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-ru.xml @@ -22,18 +22,10 @@ - - - - - - - - Email Секретный код Ваш секретный код: {0} Смс Ваш секретный код: {0} - + \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-tr.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-tr.xml similarity index 71% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-tr.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-tr.xml index a8a5ef5d..c4d4e272 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-tr.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-tr.xml @@ -22,16 +22,8 @@ - - - - - - - - E-posta - AbpZeroTemplate Güvenlik Kodu + Güvenlik Kodu Güvenlik kodunuz: {0} Sms Güvenlik kodunuz: {0} diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero-zh-CN.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-zh-CN.xml similarity index 76% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero-zh-CN.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-zh-CN.xml index 53ecfbf9..d3db2b8f 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero-zh-CN.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero-zh-CN.xml @@ -22,12 +22,5 @@ - - - - - - - \ No newline at end of file diff --git a/src/Abp.Zero/Zero/Localization/Source/AbpZero.xml b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero.xml similarity index 69% rename from src/Abp.Zero/Zero/Localization/Source/AbpZero.xml rename to src/Abp.Zero/Zero/Localization/SourceExt/AbpZero.xml index 1bd75cbf..5df52705 100644 --- a/src/Abp.Zero/Zero/Localization/Source/AbpZero.xml +++ b/src/Abp.Zero/Zero/Localization/SourceExt/AbpZero.xml @@ -1,4 +1,4 @@ - + @@ -22,16 +22,8 @@ - - - - - - - - Email - AbpZeroTemplate Security Code + Security Code Your security code is: {0} Sms Your security code is: {0} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Abp.ZeroCore.EntityFrameworkCore.csproj b/src/Abp.ZeroCore.EntityFrameworkCore/Abp.ZeroCore.EntityFrameworkCore.csproj new file mode 100644 index 00000000..e023ab89 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Abp.ZeroCore.EntityFrameworkCore.csproj @@ -0,0 +1,26 @@ + + + + + + net46;netstandard1.6 + true + Abp + Abp.ZeroCore.EntityFrameworkCore + Abp.ZeroCore.EntityFrameworkCore + asp.net;asp.net mvc;boilerplate;application framework;web framework;framework;domain driven design;multitenancy;user management;role management;identity;entity framework core + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Data/ConnectionStringHelper.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Data/ConnectionStringHelper.cs new file mode 100644 index 00000000..c915f39d --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Data/ConnectionStringHelper.cs @@ -0,0 +1,25 @@ +#if NET46 +using System.Configuration; +#endif + +namespace Abp.Data +{ + public static class ConnectionStringHelper + { + /// + /// Gets connection string from given connection string or name. + /// + public static string GetConnectionString(string nameOrConnectionString) + { +#if NET46 + var connStrSection = ConfigurationManager.ConnectionStrings[nameOrConnectionString]; + if (connStrSection != null) + { + return connStrSection.ConnectionString; + } +#endif + + return nameOrConnectionString; + } + } +} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs new file mode 100644 index 00000000..5ce93a15 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs @@ -0,0 +1,260 @@ +using Abp.Auditing; +using Abp.Authorization; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.BackgroundJobs; +using Abp.Configuration; +using Abp.EntityFrameworkCore; +using Abp.Localization; +using Abp.Notifications; +using Abp.Organizations; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + public abstract class AbpZeroCommonDbContext : AbpDbContext + where TRole : AbpRole + where TUser : AbpUser + where TSelf: AbpZeroCommonDbContext + { + /// + /// Roles. + /// + public virtual DbSet Roles { get; set; } + + /// + /// Users. + /// + public virtual DbSet Users { get; set; } + + /// + /// User logins. + /// + public virtual DbSet UserLogins { get; set; } + + /// + /// User login attempts. + /// + public virtual DbSet UserLoginAttempts { get; set; } + + /// + /// User roles. + /// + public virtual DbSet UserRoles { get; set; } + + /// + /// User claims. + /// + public virtual DbSet UserClaims { get; set; } + + /// + /// User tokens. + /// + public virtual DbSet UserTokens { get; set; } + + /// + /// Role claims. + /// + public virtual DbSet RoleClaims { get; set; } + + /// + /// Permissions. + /// + public virtual DbSet Permissions { get; set; } + + /// + /// Role permissions. + /// + public virtual DbSet RolePermissions { get; set; } + + /// + /// User permissions. + /// + public virtual DbSet UserPermissions { get; set; } + + /// + /// Settings. + /// + public virtual DbSet Settings { get; set; } + + /// + /// Audit logs. + /// + public virtual DbSet AuditLogs { get; set; } + + /// + /// Languages. + /// + public virtual DbSet Languages { get; set; } + + /// + /// LanguageTexts. + /// + public virtual DbSet LanguageTexts { get; set; } + + /// + /// OrganizationUnits. + /// + public virtual DbSet OrganizationUnits { get; set; } + + /// + /// UserOrganizationUnits. + /// + public virtual DbSet UserOrganizationUnits { get; set; } + + /// + /// Tenant notifications. + /// + public virtual DbSet TenantNotifications { get; set; } + + /// + /// User notifications. + /// + public virtual DbSet UserNotifications { get; set; } + + /// + /// Notification subscriptions. + /// + public virtual DbSet NotificationSubscriptions { get; set; } + + /// + /// + /// + /// + protected AbpZeroCommonDbContext(DbContextOptions options) + :base(options) + { + + } + + /// + /// + /// + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken(); + + b.HasOne(p => p.DeleterUser) + .WithMany() + .HasForeignKey(p => p.DeleterUserId); + + b.HasOne(p => p.CreatorUser) + .WithMany() + .HasForeignKey(p => p.CreatorUserId); + + b.HasOne(p => p.LastModifierUser) + .WithMany() + .HasForeignKey(p => p.LastModifierUserId); + }); + + modelBuilder.Entity(b => + { + b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.ExecutionTime }); + b.HasIndex(e => new { e.TenantId, e.ExecutionDuration }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Source, e.LanguageName, e.Key }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.NotificationName, e.EntityTypeName, e.EntityId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.NotificationName, e.EntityTypeName, e.EntityId, e.UserId }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Code }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.RoleId }); + b.HasIndex(e => new { e.TenantId, e.ClaimType }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.NormalizedName }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.ClaimType }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenancyName, e.UserNameOrEmailAddress, e.Result }); + b.HasIndex(ula => new {ula.UserId, ula.TenantId}); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.LoginProvider, e.ProviderKey }); + b.HasIndex(e => new { e.TenantId, e.UserId }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.UserId, e.State, e.CreationTime }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.OrganizationUnitId }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.RoleId }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.NormalizedUserName }); + b.HasIndex(e => new { e.TenantId, e.NormalizedEmailAddress }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + }); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCoreEntityFrameworkCoreModule.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCoreEntityFrameworkCoreModule.cs new file mode 100644 index 00000000..b6786271 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCoreEntityFrameworkCoreModule.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using Abp.Domain.Uow; +using Abp.EntityFrameworkCore; +using Abp.Modules; +using Abp.MultiTenancy; +using Abp.Reflection.Extensions; +using Castle.MicroKernel.Registration; + +namespace Abp.Zero.EntityFrameworkCore +{ + /// + /// Entity framework integration module for ASP.NET Boilerplate Zero. + /// + [DependsOn(typeof(AbpZeroCoreModule), typeof(AbpEntityFrameworkCoreModule))] + public class AbpZeroCoreEntityFrameworkCoreModule : AbpModule + { + public override void PreInitialize() + { + Configuration.ReplaceService(typeof(IConnectionStringResolver), () => + { + IocManager.IocContainer.Register( + Component.For() + .ImplementedBy() + .LifestyleTransient() + ); + }); + } + + public override void Initialize() + { + IocManager.RegisterAssemblyByConvention(typeof(AbpZeroCoreEntityFrameworkCoreModule).GetAssembly()); + } + } +} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbContext.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbContext.cs new file mode 100644 index 00000000..14300a23 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbContext.cs @@ -0,0 +1,121 @@ +using Abp.Application.Editions; +using Abp.Application.Features; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.BackgroundJobs; +using Abp.MultiTenancy; +using Abp.Notifications; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + /// + /// Base DbContext for ABP zero. + /// Derive your DbContext from this class to have base entities. + /// + public abstract class AbpZeroDbContext : AbpZeroCommonDbContext + where TTenant : AbpTenant + where TRole : AbpRole + where TUser : AbpUser + where TSelf : AbpZeroDbContext + { + /// + /// Tenants + /// + public virtual DbSet Tenants { get; set; } + + /// + /// Editions. + /// + public virtual DbSet Editions { get; set; } + + /// + /// FeatureSettings. + /// + public virtual DbSet FeatureSettings { get; set; } + + /// + /// TenantFeatureSetting. + /// + public virtual DbSet TenantFeatureSettings { get; set; } + + /// + /// EditionFeatureSettings. + /// + public virtual DbSet EditionFeatureSettings { get; set; } + + /// + /// Background jobs. + /// + public virtual DbSet BackgroundJobs { get; set; } + + /// + /// User accounts + /// + public virtual DbSet UserAccounts { get; set; } + + /// + /// Notifications. + /// + public virtual DbSet Notifications { get; set; } + + /// + /// + /// + /// + protected AbpZeroDbContext(DbContextOptions options) + : base(options) + { + + } + /// + /// + /// + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.IsAbandoned, e.NextTryTime }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.EditionId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasOne(p => p.DeleterUser) + .WithMany() + .HasForeignKey(p => p.DeleterUserId); + + b.HasOne(p => p.CreatorUser) + .WithMany() + .HasForeignKey(p => p.CreatorUserId); + + b.HasOne(p => p.LastModifierUser) + .WithMany() + .HasForeignKey(p => p.LastModifierUserId); + + b.HasIndex(e => e.TenancyName); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.UserName }); + b.HasIndex(e => new { e.TenantId, e.EmailAddress }); + b.HasIndex(e => new { e.UserName }); + b.HasIndex(e => new { e.EmailAddress }); + }); + } + } +} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs new file mode 100644 index 00000000..743f9ac2 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbMigrator.cs @@ -0,0 +1,81 @@ +using System; +using System.Transactions; +using Abp.Data; +using Abp.Dependency; +using Abp.Domain.Uow; +using Abp.EntityFrameworkCore; +using Abp.Extensions; +using Abp.MultiTenancy; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + public abstract class AbpZeroDbMigrator : IAbpZeroDbMigrator, ITransientDependency + where TDbContext : DbContext + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IDbPerTenantConnectionStringResolver _connectionStringResolver; + private readonly IDbContextResolver _dbContextResolver; + + protected AbpZeroDbMigrator( + IUnitOfWorkManager unitOfWorkManager, + IDbPerTenantConnectionStringResolver connectionStringResolver, + IDbContextResolver dbContextResolver) + { + _unitOfWorkManager = unitOfWorkManager; + _connectionStringResolver = connectionStringResolver; + _dbContextResolver = dbContextResolver; + } + + public virtual void CreateOrMigrateForHost() + { + CreateOrMigrateForHost(null); + } + + public virtual void CreateOrMigrateForHost(Action seedAction) + { + CreateOrMigrate(null, seedAction); + } + + public virtual void CreateOrMigrateForTenant(AbpTenantBase tenant) + { + CreateOrMigrateForTenant(tenant, null); + } + + public virtual void CreateOrMigrateForTenant(AbpTenantBase tenant, Action seedAction) + { + if (tenant.ConnectionString.IsNullOrEmpty()) + { + return; + } + + CreateOrMigrate(tenant, seedAction); + } + + protected virtual void CreateOrMigrate(AbpTenantBase tenant, Action seedAction) + { + var args = new DbPerTenantConnectionStringResolveArgs( + tenant == null ? (int?) null : (int?) tenant.Id, + tenant == null ? MultiTenancySides.Host : MultiTenancySides.Tenant + ); + + args["DbContextType"] = typeof(TDbContext); + args["DbContextConcreteType"] = typeof(TDbContext); + + var nameOrConnectionString = ConnectionStringHelper.GetConnectionString( + _connectionStringResolver.GetNameOrConnectionString(args) + ); + + using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress)) + { + using (var dbContext = _dbContextResolver.Resolve(nameOrConnectionString, null)) + { + dbContext.Database.Migrate(); + seedAction?.Invoke(dbContext); + _unitOfWorkManager.Current.SaveChanges(); + uow.Complete(); + } + } + } + } +} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbModelBuilderExtensions.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbModelBuilderExtensions.cs new file mode 100644 index 00000000..c4a24e3c --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroDbModelBuilderExtensions.cs @@ -0,0 +1,80 @@ +using Abp.Application.Editions; +using Abp.Application.Features; +using Abp.Auditing; +using Abp.Authorization; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.BackgroundJobs; +using Abp.Configuration; +using Abp.Localization; +using Abp.MultiTenancy; +using Abp.Notifications; +using Abp.Organizations; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + /// + /// Extension methods for . + /// + public static class AbpZeroDbModelBuilderExtensions + { + /// + /// Changes prefix for ABP tables (which is "Abp" by default). + /// Can be null/empty string to clear the prefix. + /// + /// The type of the tenant entity. + /// The type of the role entity. + /// The type of the user entity. + /// Model builder. + /// Table prefix, or null to clear prefix. + public static void ChangeAbpTablePrefix(this ModelBuilder modelBuilder, string prefix, string schemaName = null) + where TTenant : AbpTenant + where TRole : AbpRole + where TUser : AbpUser + { + prefix = prefix ?? ""; + + SetTableName(modelBuilder, prefix + "AuditLogs", schemaName); + SetTableName(modelBuilder, prefix + "BackgroundJobs", schemaName); + SetTableName(modelBuilder, prefix + "Editions", schemaName); + SetTableName(modelBuilder, prefix + "Features", schemaName); + SetTableName(modelBuilder, prefix + "Features", schemaName); + SetTableName(modelBuilder, prefix + "Features", schemaName); + SetTableName(modelBuilder, prefix + "Languages", schemaName); + SetTableName(modelBuilder, prefix + "LanguageTexts", schemaName); + SetTableName(modelBuilder, prefix + "Notifications", schemaName); + SetTableName(modelBuilder, prefix + "NotificationSubscriptions", schemaName); + SetTableName(modelBuilder, prefix + "OrganizationUnits", schemaName); + SetTableName(modelBuilder, prefix + "Permissions", schemaName); + SetTableName(modelBuilder, prefix + "Permissions", schemaName); + SetTableName(modelBuilder, prefix + "Permissions", schemaName); + SetTableName(modelBuilder, prefix + "Roles", schemaName); + SetTableName(modelBuilder, prefix + "Settings", schemaName); + SetTableName(modelBuilder, prefix + "Tenants", schemaName); + SetTableName(modelBuilder, prefix + "UserLogins", schemaName); + SetTableName(modelBuilder, prefix + "UserLoginAttempts", schemaName); + SetTableName(modelBuilder, prefix + "TenantNotifications", schemaName); + SetTableName(modelBuilder, prefix + "UserNotifications", schemaName); + SetTableName(modelBuilder, prefix + "UserOrganizationUnits", schemaName); + SetTableName(modelBuilder, prefix + "UserRoles", schemaName); + SetTableName(modelBuilder, prefix + "Users", schemaName); + SetTableName(modelBuilder, prefix + "UserAccounts", schemaName); + SetTableName(modelBuilder, prefix + "UserClaims", schemaName); + SetTableName(modelBuilder, prefix + "RoleClaims", schemaName); + } + + private static void SetTableName(ModelBuilder modelBuilder, string tableName, string schemaName) + where TEntity : class + { + if (schemaName == null) + { + modelBuilder.Entity().ToTable(tableName); + } + else + { + modelBuilder.Entity().ToTable(tableName, schemaName); + } + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroHostDbContext.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroHostDbContext.cs new file mode 100644 index 00000000..5933b7a8 --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroHostDbContext.cs @@ -0,0 +1,130 @@ +using Abp.Application.Editions; +using Abp.Application.Features; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.BackgroundJobs; +using Abp.MultiTenancy; +using Abp.Notifications; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + [MultiTenancySide(MultiTenancySides.Host)] + public abstract class AbpZeroHostDbContext : AbpZeroCommonDbContext + where TTenant : AbpTenant + where TRole : AbpRole + where TUser : AbpUser + where TSelf : AbpZeroHostDbContext + { + /// + /// Tenants + /// + public virtual DbSet Tenants { get; set; } + + /// + /// Editions. + /// + public virtual DbSet Editions { get; set; } + + /// + /// FeatureSettings. + /// + public virtual DbSet FeatureSettings { get; set; } + + /// + /// TenantFeatureSetting. + /// + public virtual DbSet TenantFeatureSettings { get; set; } + + /// + /// EditionFeatureSettings. + /// + public virtual DbSet EditionFeatureSettings { get; set; } + + /// + /// Background jobs. + /// + public virtual DbSet BackgroundJobs { get; set; } + + /// + /// User accounts + /// + public virtual DbSet UserAccounts { get; set; } + + /// + /// Notifications. + /// + public virtual DbSet Notifications { get; set; } + + /// + /// + /// + /// + protected AbpZeroHostDbContext(DbContextOptions options) + :base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(u => + { + u.HasOne(p => p.DeleterUser) + .WithMany() + .HasForeignKey(p => p.DeleterUserId); + + u.HasOne(p => p.CreatorUser) + .WithMany() + .HasForeignKey(p => p.CreatorUserId); + + u.HasOne(p => p.LastModifierUser) + .WithMany() + .HasForeignKey(p => p.LastModifierUserId); + }); + + modelBuilder.Entity(b => + { + b.HasOne(p => p.DeleterUser) + .WithMany() + .HasForeignKey(p => p.DeleterUserId); + + b.HasOne(p => p.CreatorUser) + .WithMany() + .HasForeignKey(p => p.CreatorUserId); + + b.HasOne(p => p.LastModifierUser) + .WithMany() + .HasForeignKey(p => p.LastModifierUserId); + + b.HasIndex(e => e.TenancyName); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.IsAbandoned, e.NextTryTime }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.EditionId, e.Name }); + }); + + modelBuilder.Entity(b => + { + b.HasIndex(e => new { e.TenantId, e.UserId }); + b.HasIndex(e => new { e.TenantId, e.UserName }); + b.HasIndex(e => new { e.TenantId, e.EmailAddress }); + b.HasIndex(e => new { e.UserName }); + b.HasIndex(e => new { e.EmailAddress }); + }); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroTenantDbContext.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroTenantDbContext.cs new file mode 100644 index 00000000..8082f37e --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroTenantDbContext.cs @@ -0,0 +1,25 @@ +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.MultiTenancy; +using Microsoft.EntityFrameworkCore; + +namespace Abp.Zero.EntityFrameworkCore +{ + [MultiTenancySide(MultiTenancySides.Host)] + public abstract class AbpZeroTenantDbContext : AbpZeroCommonDbContext + where TRole : AbpRole + where TUser : AbpUser + where TSelf: AbpZeroTenantDbContext + { + + /// + /// + /// + /// + protected AbpZeroTenantDbContext(DbContextOptions options) + :base(options) + { + + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/DbPerTenantConnectionStringResolver.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/DbPerTenantConnectionStringResolver.cs new file mode 100644 index 00000000..c2d9b17a --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/DbPerTenantConnectionStringResolver.cs @@ -0,0 +1,73 @@ +using Abp.Configuration.Startup; +using Abp.Domain.Uow; +using Abp.Extensions; +using Abp.MultiTenancy; +using Abp.Runtime.Session; + +namespace Abp.Zero.EntityFrameworkCore +{ + /// + /// Implements to dynamically resolve + /// connection string for a multi tenant application. + /// + public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver + { + /// + /// Reference to the session. + /// + public IAbpSession AbpSession { get; set; } + + private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider; + private readonly ITenantCache _tenantCache; + + /// + /// Initializes a new instance of the class. + /// + public DbPerTenantConnectionStringResolver( + IAbpStartupConfiguration configuration, + ICurrentUnitOfWorkProvider currentUnitOfWorkProvider, + ITenantCache tenantCache) + : base(configuration) + { + _currentUnitOfWorkProvider = currentUnitOfWorkProvider; + _tenantCache = tenantCache; + + AbpSession = NullAbpSession.Instance; + } + + public override string GetNameOrConnectionString(ConnectionStringResolveArgs args) + { + if (args.MultiTenancySide == MultiTenancySides.Host) + { + return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args)); + } + + return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args)); + } + + public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args) + { + if (args.TenantId == null) + { + //Requested for host + return base.GetNameOrConnectionString(args); + } + + var tenantCacheItem = _tenantCache.Get(args.TenantId.Value); + if (tenantCacheItem.ConnectionString.IsNullOrEmpty()) + { + //Tenant has not dedicated database + return base.GetNameOrConnectionString(args); + } + + return tenantCacheItem.ConnectionString; + } + + protected virtual int? GetCurrentTenantId() + { + return _currentUnitOfWorkProvider.Current != null + ? _currentUnitOfWorkProvider.Current.GetTenantId() + : AbpSession.TenantId; + } + } +} diff --git a/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/IMultiTenantSeed.cs b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/IMultiTenantSeed.cs new file mode 100644 index 00000000..6201a18b --- /dev/null +++ b/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/IMultiTenantSeed.cs @@ -0,0 +1,9 @@ +using Abp.MultiTenancy; + +namespace Abp.Zero.EntityFrameworkCore +{ + public interface IMultiTenantSeed + { + AbpTenantBase Tenant { get; set; } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Abp.ZeroCore.csproj b/src/Abp.ZeroCore/Abp.ZeroCore.csproj new file mode 100644 index 00000000..41f342fc --- /dev/null +++ b/src/Abp.ZeroCore/Abp.ZeroCore.csproj @@ -0,0 +1,47 @@ + + + + + + net46;netstandard1.6 + true + Abp.ZeroCore + Abp.ZeroCore + asp.net;asp.net mvc;boilerplate;application framework;web framework;framework;domain driven design;multitenancy;user management;role management;identity + false + false + false + false + false + false + Abp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs b/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs new file mode 100644 index 00000000..47210feb --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using System.Transactions; +using Abp.Auditing; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Configuration; +using Abp.Configuration.Startup; +using Abp.Dependency; +using Abp.Domain.Repositories; +using Abp.Domain.Uow; +using Abp.Extensions; +using Abp.IdentityFramework; +using Abp.MultiTenancy; +using Abp.Timing; +using Abp.Zero.Configuration; +using Microsoft.AspNetCore.Identity; + +namespace Abp.Authorization +{ + public class AbpLogInManager : ITransientDependency + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + { + public IClientInfoProvider ClientInfoProvider { get; set; } + + protected IMultiTenancyConfig MultiTenancyConfig { get; } + protected IRepository TenantRepository { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected AbpUserManager UserManager { get; } + protected ISettingManager SettingManager { get; } + protected IRepository UserLoginAttemptRepository { get; } + protected IUserManagementConfig UserManagementConfig { get; } + protected IIocResolver IocResolver { get; } + protected AbpRoleManager RoleManager { get; } + + private readonly IPasswordHasher _passwordHasher; + + private readonly AbpUserClaimsPrincipalFactory _claimsPrincipalFactory; + + public AbpLogInManager( + AbpUserManager userManager, + IMultiTenancyConfig multiTenancyConfig, + IRepository tenantRepository, + IUnitOfWorkManager unitOfWorkManager, + ISettingManager settingManager, + IRepository userLoginAttemptRepository, + IUserManagementConfig userManagementConfig, + IIocResolver iocResolver, + IPasswordHasher passwordHasher, + AbpRoleManager roleManager, + AbpUserClaimsPrincipalFactory claimsPrincipalFactory) + { + _passwordHasher = passwordHasher; + _claimsPrincipalFactory = claimsPrincipalFactory; + MultiTenancyConfig = multiTenancyConfig; + TenantRepository = tenantRepository; + UnitOfWorkManager = unitOfWorkManager; + SettingManager = settingManager; + UserLoginAttemptRepository = userLoginAttemptRepository; + UserManagementConfig = userManagementConfig; + IocResolver = iocResolver; + RoleManager = roleManager; + UserManager = userManager; + + ClientInfoProvider = NullClientInfoProvider.Instance; + } + + [UnitOfWork] + public virtual async Task> LoginAsync(UserLoginInfo login, string tenancyName = null) + { + var result = await LoginAsyncInternal(login, tenancyName); + await SaveLoginAttempt(result, tenancyName, login.ProviderKey + "@" + login.LoginProvider); + return result; + } + + protected virtual async Task> LoginAsyncInternal(UserLoginInfo login, string tenancyName) + { + if (login == null || login.LoginProvider.IsNullOrEmpty() || login.ProviderKey.IsNullOrEmpty()) + { + throw new ArgumentException("login"); + } + + //Get and check tenant + TTenant tenant = null; + if (!MultiTenancyConfig.IsEnabled) + { + tenant = await GetDefaultTenantAsync(); + } + else if (!string.IsNullOrWhiteSpace(tenancyName)) + { + tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName); + if (tenant == null) + { + return new AbpLoginResult(AbpLoginResultType.InvalidTenancyName); + } + + if (!tenant.IsActive) + { + return new AbpLoginResult(AbpLoginResultType.TenantIsNotActive, tenant); + } + } + + int? tenantId = tenant == null ? (int?)null : tenant.Id; + using (UnitOfWorkManager.Current.SetTenantId(tenantId)) + { + var user = await UserManager.AbpStore.FindAsync(tenantId, login); + if (user == null) + { + return new AbpLoginResult(AbpLoginResultType.UnknownExternalLogin, tenant); + } + + return await CreateLoginResultAsync(user, tenant); + } + } + + [UnitOfWork] + public virtual async Task> LoginAsync(string userNameOrEmailAddress, string plainPassword, string tenancyName = null, bool shouldLockout = true) + { + var result = await LoginAsyncInternal(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout); + await SaveLoginAttempt(result, tenancyName, userNameOrEmailAddress); + return result; + } + + protected virtual async Task> LoginAsyncInternal(string userNameOrEmailAddress, string plainPassword, string tenancyName, bool shouldLockout) + { + if (userNameOrEmailAddress.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(userNameOrEmailAddress)); + } + + if (plainPassword.IsNullOrEmpty()) + { + throw new ArgumentNullException(nameof(plainPassword)); + } + + //Get and check tenant + TTenant tenant = null; + using (UnitOfWorkManager.Current.SetTenantId(null)) + { + if (!MultiTenancyConfig.IsEnabled) + { + tenant = await GetDefaultTenantAsync(); + } + else if (!string.IsNullOrWhiteSpace(tenancyName)) + { + tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName); + if (tenant == null) + { + return new AbpLoginResult(AbpLoginResultType.InvalidTenancyName); + } + + if (!tenant.IsActive) + { + return new AbpLoginResult(AbpLoginResultType.TenantIsNotActive, tenant); + } + } + } + + var tenantId = tenant == null ? (int?)null : tenant.Id; + using (UnitOfWorkManager.Current.SetTenantId(tenantId)) + { + await UserManager.InitializeOptionsAsync(tenantId); + + //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before AbpStore.FindByNameOrEmailAsync + var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSources(userNameOrEmailAddress, plainPassword, tenant); + + var user = await UserManager.AbpStore.FindByNameOrEmailAsync(tenantId, userNameOrEmailAddress); + if (user == null) + { + return new AbpLoginResult(AbpLoginResultType.InvalidUserNameOrEmailAddress, tenant); + } + + if (!loggedInFromExternalSource) + { + if (await UserManager.IsLockedOutAsync(user)) + { + return new AbpLoginResult(AbpLoginResultType.LockedOut, tenant, user); + } + + if (!await UserManager.CheckPasswordAsync(user, plainPassword)) + { + if (shouldLockout) + { + if (await TryLockOutAsync(tenantId, user.Id)) + { + return new AbpLoginResult(AbpLoginResultType.LockedOut, tenant, user); + } + } + + return new AbpLoginResult(AbpLoginResultType.InvalidPassword, tenant, user); + } + + await UserManager.ResetAccessFailedCountAsync(user); + } + + return await CreateLoginResultAsync(user, tenant); + } + } + + protected virtual async Task> CreateLoginResultAsync(TUser user, TTenant tenant = null) + { + if (!user.IsActive) + { + return new AbpLoginResult(AbpLoginResultType.UserIsNotActive); + } + + if (await IsEmailConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsEmailConfirmed) + { + return new AbpLoginResult(AbpLoginResultType.UserEmailIsNotConfirmed); + } + + if (await IsPhoneConfirmationRequiredForLoginAsync(user.TenantId) && !user.IsPhoneNumberConfirmed) + { + return new AbpLoginResult(AbpLoginResultType.UserPhoneNumberIsNotConfirmed); + } + + user.LastLoginTime = Clock.Now; + + await UserManager.AbpStore.UpdateAsync(user); + + await UnitOfWorkManager.Current.SaveChangesAsync(); + + var principal = await _claimsPrincipalFactory.CreateAsync(user); + + return new AbpLoginResult( + tenant, + user, + principal.Identity as ClaimsIdentity + ); + } + + protected virtual async Task SaveLoginAttempt(AbpLoginResult loginResult, string tenancyName, string userNameOrEmailAddress) + { + using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress)) + { + var tenantId = loginResult.Tenant != null ? loginResult.Tenant.Id : (int?)null; + using (UnitOfWorkManager.Current.SetTenantId(tenantId)) + { + var loginAttempt = new UserLoginAttempt + { + TenantId = tenantId, + TenancyName = tenancyName, + + UserId = loginResult.User != null ? loginResult.User.Id : (long?) null, + UserNameOrEmailAddress = userNameOrEmailAddress, + + Result = loginResult.Result, + + BrowserInfo = ClientInfoProvider.BrowserInfo, + ClientIpAddress = ClientInfoProvider.ClientIpAddress, + ClientName = ClientInfoProvider.ComputerName, + }; + + await UserLoginAttemptRepository.InsertAsync(loginAttempt); + await UnitOfWorkManager.Current.SaveChangesAsync(); + + await uow.CompleteAsync(); + } + } + } + + protected virtual async Task TryLockOutAsync(int? tenantId, long userId) + { + using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress)) + { + using (UnitOfWorkManager.Current.SetTenantId(tenantId)) + { + var user = await UserManager.FindByIdAsync(userId.ToString()); + + (await UserManager.AccessFailedAsync(user)).CheckErrors(); + + var isLockOut = await UserManager.IsLockedOutAsync(user); + + await UnitOfWorkManager.Current.SaveChangesAsync(); + + await uow.CompleteAsync(); + + return isLockOut; + } + } + } + + protected virtual async Task TryLoginFromExternalAuthenticationSources(string userNameOrEmailAddress, string plainPassword, TTenant tenant) + { + if (!UserManagementConfig.ExternalAuthenticationSources.Any()) + { + return false; + } + + foreach (var sourceType in UserManagementConfig.ExternalAuthenticationSources) + { + using (var source = IocResolver.ResolveAsDisposable>(sourceType)) + { + if (await source.Object.TryAuthenticateAsync(userNameOrEmailAddress, plainPassword, tenant)) + { + var tenantId = tenant == null ? (int?)null : tenant.Id; + using (UnitOfWorkManager.Current.SetTenantId(tenantId)) + { + var user = await UserManager.AbpStore.FindByNameOrEmailAsync(tenantId, userNameOrEmailAddress); + if (user == null) + { + user = await source.Object.CreateUserAsync(userNameOrEmailAddress, tenant); + + user.TenantId = tenantId; + user.AuthenticationSource = source.Object.Name; + user.Password = _passwordHasher.HashPassword(user, Guid.NewGuid().ToString("N").Left(16)); //Setting a random password since it will not be used + + if (user.Roles == null) + { + user.Roles = new List(); + foreach (var defaultRole in RoleManager.Roles.Where(r => r.TenantId == tenantId && r.IsDefault).ToList()) + { + user.Roles.Add(new UserRole(tenantId, user.Id, defaultRole.Id)); + } + } + + await UserManager.AbpStore.CreateAsync(user); + } + else + { + await source.Object.UpdateUserAsync(user, tenant); + + user.AuthenticationSource = source.Object.Name; + + await UserManager.AbpStore.UpdateAsync(user); + } + + await UnitOfWorkManager.Current.SaveChangesAsync(); + + return true; + } + } + } + } + + return false; + } + + protected virtual async Task GetDefaultTenantAsync() + { + var tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == AbpTenant.DefaultTenantName); + if (tenant == null) + { + throw new AbpException("There should be a 'Default' tenant if multi-tenancy is disabled!"); + } + + return tenant; + } + + protected virtual async Task IsEmailConfirmationRequiredForLoginAsync(int? tenantId) + { + if (tenantId.HasValue) + { + return await SettingManager.GetSettingValueForTenantAsync(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin, tenantId.Value); + } + + return await SettingManager.GetSettingValueForApplicationAsync(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin); + } + + protected virtual Task IsPhoneConfirmationRequiredForLoginAsync(int? tenantId) + { + return Task.FromResult(false); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/AbpLoginManagerExtensions.cs b/src/Abp.ZeroCore/Authorization/AbpLoginManagerExtensions.cs new file mode 100644 index 00000000..acceb390 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpLoginManagerExtensions.cs @@ -0,0 +1,30 @@ +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.MultiTenancy; +using Abp.Threading; + +namespace Abp.Authorization +{ + public static class AbpLogInManagerExtensions + { + public static AbpLoginResult Login( + this AbpLogInManager logInManager, + string userNameOrEmailAddress, + string plainPassword, + string tenancyName = null, + bool shouldLockout = true) + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + { + return AsyncHelper.RunSync( + () => logInManager.LoginAsync( + userNameOrEmailAddress, + plainPassword, + tenancyName, + shouldLockout + ) + ); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/AbpSecurityStampValidator.cs b/src/Abp.ZeroCore/Authorization/AbpSecurityStampValidator.cs new file mode 100644 index 00000000..1b5dad40 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpSecurityStampValidator.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Domain.Uow; +using Abp.MultiTenancy; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; + +namespace Abp.Authorization +{ + public abstract class AbpSecurityStampValidator : SecurityStampValidator + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + { + protected AbpSecurityStampValidator( + IOptions options, + AbpSignInManager signInManager) + : base( + options, + signInManager) + { + } + + [UnitOfWork] + public override Task ValidateAsync(CookieValidatePrincipalContext context) + { + return base.ValidateAsync(context); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/AbpSignInManager.cs b/src/Abp.ZeroCore/Authorization/AbpSignInManager.cs new file mode 100644 index 00000000..be5e0338 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpSignInManager.cs @@ -0,0 +1,172 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Configuration; +using Abp.Dependency; +using Abp.Domain.Uow; +using Abp.Extensions; +using Abp.MultiTenancy; +using Abp.Runtime.Security; +using Abp.Zero.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Abp.Authorization +{ + public abstract class AbpSignInManager : SignInManager, ITransientDependency + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly ISettingManager _settingManager; + + protected AbpSignInManager( + AbpUserManager userManager, + IHttpContextAccessor contextAccessor, + AbpUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger> logger, + IUnitOfWorkManager unitOfWorkManager, + ISettingManager settingManager) + : base( + userManager, + contextAccessor, + claimsFactory, + optionsAccessor, + logger) + { + _unitOfWorkManager = unitOfWorkManager; + _settingManager = settingManager; + } + + public virtual async Task SignInOrTwoFactorAsync(AbpLoginResult loginResult, bool isPersistent, bool? rememberBrowser = null, string loginProvider = null, bool bypassTwoFactor = false) + { + if (loginResult.Result != AbpLoginResultType.Success) + { + throw new ArgumentException("loginResult.Result should be success in order to sign in!"); + } + + using (_unitOfWorkManager.Current.SetTenantId(loginResult.Tenant?.Id)) + { + await UserManager.As>().InitializeOptionsAsync(loginResult.Tenant?.Id); + + if (!bypassTwoFactor && IsTrue(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled, loginResult.Tenant?.Id)) + { + if (await UserManager.GetTwoFactorEnabledAsync(loginResult.User)) + { + if ((await UserManager.GetValidTwoFactorProvidersAsync(loginResult.User)).Count > 0) + { + if (!await IsTwoFactorClientRememberedAsync(loginResult.User) || rememberBrowser == false) + { + await Context.Authentication.SignInAsync( + Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme, + StoreTwoFactorInfo(loginResult.User, loginProvider) + ); + + return SignInResult.TwoFactorRequired; + } + } + } + } + + if (loginProvider != null) + { + await Context.Authentication.SignOutAsync(Options.Cookies.ExternalCookieAuthenticationScheme); + } + + await SignInAsync(loginResult.User, isPersistent, loginProvider); + return SignInResult.Success; + } + } + + public virtual async Task SignOutAndSignInAsync(ClaimsIdentity identity, bool isPersistent) + { + await SignOutAsync(); + await SignInAsync(identity, isPersistent); + } + + public virtual async Task SignInAsync(ClaimsIdentity identity, bool isPersistent) + { + await Context.Authentication.SignInAsync(Options.Cookies.ApplicationCookieAuthenticationScheme, + new ClaimsPrincipal(identity), + new AuthenticationProperties {IsPersistent = isPersistent} + ); + } + + [UnitOfWork] + public override Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null) + { + return base.SignInAsync(user, authenticationProperties, authenticationMethod); + } + + protected virtual ClaimsPrincipal StoreTwoFactorInfo(TUser user, string loginProvider) + { + var identity = new ClaimsIdentity(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme); + + identity.AddClaim(new Claim(ClaimTypes.Name, user.Id.ToString())); + + if (user.TenantId.HasValue) + { + identity.AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.Value.ToString())); + } + + if (loginProvider != null) + { + identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, loginProvider)); + } + + return new ClaimsPrincipal(identity); + } + + public async Task GetVerifiedTenantIdAsync() + { + var principal = await Context.Authentication.AuthenticateAsync(Options.Cookies.TwoFactorUserIdCookieAuthenticationScheme); + + if (principal == null) + { + return null; + } + + return AbpZeroClaimsIdentityHelper.GetTenantId(principal); + } + + public override async Task IsTwoFactorClientRememberedAsync(TUser user) + { + var result = await Context.Authentication.AuthenticateAsync(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme); + + return result != null && + result.FindFirstValue(ClaimTypes.Name) == user.Id.ToString() && + AbpZeroClaimsIdentityHelper.GetTenantId(result) == user.TenantId; + } + + public override async Task RememberTwoFactorClientAsync(TUser user) + { + var rememberBrowserIdentity = new ClaimsIdentity(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme); + + rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.Name, user.Id.ToString())); + + if (user.TenantId.HasValue) + { + rememberBrowserIdentity.AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.Value.ToString())); + } + + await Context.Authentication.SignInAsync(Options.Cookies.TwoFactorRememberMeCookieAuthenticationScheme, + new ClaimsPrincipal(rememberBrowserIdentity), + new AuthenticationProperties { IsPersistent = true }); + } + + private bool IsTrue(string settingName, int? tenantId) + { + return tenantId == null + ? _settingManager.GetSettingValueForApplication(settingName) + : _settingManager.GetSettingValueForTenant(settingName, tenantId.Value); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/AbpUserClaimsPrincipalFactory.cs b/src/Abp.ZeroCore/Authorization/AbpUserClaimsPrincipalFactory.cs new file mode 100644 index 00000000..79ba788a --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpUserClaimsPrincipalFactory.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Dependency; +using Abp.Runtime.Security; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; + +namespace Abp.Authorization +{ + public abstract class AbpUserClaimsPrincipalFactory : UserClaimsPrincipalFactory, ITransientDependency + where TRole : AbpRole, new() + where TUser : AbpUser + { + protected AbpUserClaimsPrincipalFactory( + AbpUserManager userManager, + AbpRoleManager roleManager, + IOptions optionsAccessor + ) : base(userManager, roleManager, optionsAccessor) + { + + } + + public override async Task CreateAsync(TUser user) + { + var principal = await base.CreateAsync(user); + + if (user.TenantId.HasValue) + { + principal.Identities.First().AddClaim(new Claim(AbpClaimTypes.TenantId,user.TenantId.ToString())); + } + + return principal; + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/AbpZeroClaimsIdentityHelper.cs b/src/Abp.ZeroCore/Authorization/AbpZeroClaimsIdentityHelper.cs new file mode 100644 index 00000000..fe0d2a1d --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/AbpZeroClaimsIdentityHelper.cs @@ -0,0 +1,20 @@ +using System; +using System.Security.Claims; +using Abp.Runtime.Security; + +namespace Abp.Authorization +{ + internal static class AbpZeroClaimsIdentityHelper + { + public static int? GetTenantId(ClaimsPrincipal principal) + { + var tenantIdOrNull = principal?.FindFirstValue(AbpClaimTypes.TenantId); + if (tenantIdOrNull == null) + { + return null; + } + + return Convert.ToInt32(tenantIdOrNull); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/PermissionChecker.cs b/src/Abp.ZeroCore/Authorization/PermissionChecker.cs new file mode 100644 index 00000000..e3e88d4d --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/PermissionChecker.cs @@ -0,0 +1,68 @@ +using System.Threading.Tasks; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.Dependency; +using Abp.Domain.Uow; +using Abp.MultiTenancy; +using Abp.Runtime.Session; +using Castle.Core.Logging; + +namespace Abp.Authorization +{ + /// + /// Application should inherit this class to implement . + /// + /// + /// + /// + public abstract class PermissionChecker : IPermissionChecker, ITransientDependency, IIocManagerAccessor + where TRole : AbpRole, new() + where TUser : AbpUser + where TTenant : AbpTenant + { + private readonly AbpUserManager _userManager; + + public IIocManager IocManager { get; set; } + + public ILogger Logger { get; set; } + + public IAbpSession AbpSession { get; set; } + + public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; } + + /// + /// Constructor. + /// + protected PermissionChecker(AbpUserManager userManager) + { + _userManager = userManager; + + Logger = NullLogger.Instance; + AbpSession = NullAbpSession.Instance; + } + + public virtual async Task IsGrantedAsync(string permissionName) + { + return AbpSession.UserId.HasValue && await IsGrantedAsync(AbpSession.UserId.Value, permissionName); + } + + public virtual async Task IsGrantedAsync(long userId, string permissionName) + { + return await _userManager.IsGrantedAsync(userId, permissionName); + } + + [UnitOfWork] + public virtual async Task IsGrantedAsync(UserIdentifier user, string permissionName) + { + if (CurrentUnitOfWorkProvider?.Current == null) + { + return await IsGrantedAsync(user.UserId, permissionName); + } + + using (CurrentUnitOfWorkProvider.Current.SetTenantId(user.TenantId)) + { + return await IsGrantedAsync(user.UserId, permissionName); + } + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/Roles/AbpRole.cs b/src/Abp.ZeroCore/Authorization/Roles/AbpRole.cs new file mode 100644 index 00000000..ac9551cf --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Roles/AbpRole.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Authorization.Users; +using Abp.Domain.Entities.Auditing; + +namespace Abp.Authorization.Roles +{ + /// + /// Represents a role in an application. A role is used to group permissions. + /// + /// + /// Application should use permissions to check if user is granted to perform an operation. + /// Checking 'if a user has a role' is not possible until the role is static (). + /// Static roles can be used in the code and can not be deleted by users. + /// Non-static (dynamic) roles can be added/removed by users and we can not know their name while coding. + /// A user can have multiple roles. Thus, user will have all permissions of all assigned roles. + /// + public abstract class AbpRole : AbpRoleBase, IFullAudited + where TUser : AbpUser + { + /// + /// Unique name of this role. + /// + [Required] + [StringLength(MaxNameLength)] + public virtual string NormalizedName { get; set; } + + /// + /// Claims of this user. + /// + [ForeignKey("UserId")] + public virtual ICollection Claims { get; set; } + + /// + /// A random value that must change whenever a user is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString(); + + public virtual TUser DeleterUser { get; set; } + + public virtual TUser CreatorUser { get; set; } + + public virtual TUser LastModifierUser { get; set; } + + protected AbpRole() + { + SetNormalizedName(); + } + + /// + /// Creates a new object. + /// + /// TenantId or null (if this is not a tenant-level role) + /// Display name of the role + protected AbpRole(int? tenantId, string displayName) + : base(tenantId, displayName) + { + SetNormalizedName(); + } + + /// + /// Creates a new object. + /// + /// TenantId or null (if this is not a tenant-level role) + /// Unique role name + /// Display name of the role + protected AbpRole(int? tenantId, string name, string displayName) + : base(tenantId, name, displayName) + { + SetNormalizedName(); + } + + public void SetNormalizedName() + { + NormalizedName = Name.ToUpperInvariant(); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/Roles/AbpRoleManager.cs b/src/Abp.ZeroCore/Authorization/Roles/AbpRoleManager.cs new file mode 100644 index 00000000..9c7a7319 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Roles/AbpRoleManager.cs @@ -0,0 +1,424 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Abp.Authorization.Users; +using Abp.Domain.Services; +using Abp.Domain.Uow; +using Abp.Localization; +using Abp.MultiTenancy; +using Abp.Runtime.Caching; +using Abp.Runtime.Session; +using Abp.UI; +using Abp.Zero; +using Abp.Zero.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; + +namespace Abp.Authorization.Roles +{ + public abstract class AbpRoleManager : RoleManager, IDomainService + where TRole : AbpRole, new() + where TUser : AbpUser + { + public ILocalizationManager LocalizationManager { get; set; } + + public IAbpSession AbpSession { get; set; } + + public IRoleManagementConfig RoleManagementConfig { get; private set; } + + private IRolePermissionStore RolePermissionStore + { + get + { + if (!(Store is IRolePermissionStore)) + { + throw new AbpException("Store is not IRolePermissionStore"); + } + + return Store as IRolePermissionStore; + } + } + + protected AbpRoleStore AbpStore { get; private set; } + + private readonly IPermissionManager _permissionManager; + private readonly ICacheManager _cacheManager; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + protected AbpRoleManager( + AbpRoleStore store, + IEnumerable> roleValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + ILogger> logger, + IHttpContextAccessor contextAccessor, + IPermissionManager permissionManager, + ICacheManager cacheManager, + IUnitOfWorkManager unitOfWorkManager, + IRoleManagementConfig roleManagementConfig) + : base( + store, + roleValidators, + keyNormalizer, + errors, + logger, + contextAccessor) + { + _permissionManager = permissionManager; + _cacheManager = cacheManager; + _unitOfWorkManager = unitOfWorkManager; + + RoleManagementConfig = roleManagementConfig; + AbpStore = store; + AbpSession = NullAbpSession.Instance; + LocalizationManager = NullLocalizationManager.Instance; + } + + /// + /// Checks if a role is granted for a permission. + /// + /// The role's name to check it's permission + /// Name of the permission + /// True, if the role has the permission + public virtual async Task IsGrantedAsync(string roleName, string permissionName) + { + return await IsGrantedAsync((await GetRoleByNameAsync(roleName)).Id, _permissionManager.GetPermission(permissionName)); + } + + /// + /// Checks if a role has a permission. + /// + /// The role's id to check it's permission + /// Name of the permission + /// True, if the role has the permission + public virtual async Task IsGrantedAsync(int roleId, string permissionName) + { + return await IsGrantedAsync(roleId, _permissionManager.GetPermission(permissionName)); + } + + /// + /// Checks if a role is granted for a permission. + /// + /// The role + /// The permission + /// True, if the role has the permission + public Task IsGrantedAsync(TRole role, Permission permission) + { + return IsGrantedAsync(role.Id, permission); + } + + /// + /// Checks if a role is granted for a permission. + /// + /// role id + /// The permission + /// True, if the role has the permission + public virtual async Task IsGrantedAsync(int roleId, Permission permission) + { + //Get cached role permissions + var cacheItem = await GetRolePermissionCacheItemAsync(roleId); + + //Check the permission + return cacheItem.GrantedPermissions.Contains(permission.Name); + } + + /// + /// Gets granted permission names for a role. + /// + /// Role id + /// List of granted permissions + public virtual async Task> GetGrantedPermissionsAsync(int roleId) + { + return await GetGrantedPermissionsAsync(await GetRoleByIdAsync(roleId)); + } + + /// + /// Gets granted permission names for a role. + /// + /// Role name + /// List of granted permissions + public virtual async Task> GetGrantedPermissionsAsync(string roleName) + { + return await GetGrantedPermissionsAsync(await GetRoleByNameAsync(roleName)); + } + + /// + /// Gets granted permissions for a role. + /// + /// Role + /// List of granted permissions + public virtual async Task> GetGrantedPermissionsAsync(TRole role) + { + var permissionList = new List(); + + foreach (var permission in _permissionManager.GetAllPermissions()) + { + if (await IsGrantedAsync(role.Id, permission)) + { + permissionList.Add(permission); + } + } + + return permissionList; + } + + /// + /// Sets all granted permissions of a role at once. + /// Prohibits all other permissions. + /// + /// Role id + /// Permissions + public virtual async Task SetGrantedPermissionsAsync(int roleId, IEnumerable permissions) + { + await SetGrantedPermissionsAsync(await GetRoleByIdAsync(roleId), permissions); + } + + /// + /// Sets all granted permissions of a role at once. + /// Prohibits all other permissions. + /// + /// The role + /// Permissions + public virtual async Task SetGrantedPermissionsAsync(TRole role, IEnumerable permissions) + { + var oldPermissions = await GetGrantedPermissionsAsync(role); + var newPermissions = permissions.ToArray(); + + foreach (var permission in oldPermissions.Where(p => !newPermissions.Contains(p, PermissionEqualityComparer.Instance))) + { + await ProhibitPermissionAsync(role, permission); + } + + foreach (var permission in newPermissions.Where(p => !oldPermissions.Contains(p, PermissionEqualityComparer.Instance))) + { + await GrantPermissionAsync(role, permission); + } + } + + /// + /// Grants a permission for a role. + /// + /// Role + /// Permission + public async Task GrantPermissionAsync(TRole role, Permission permission) + { + if (await IsGrantedAsync(role.Id, permission)) + { + return; + } + + await RolePermissionStore.AddPermissionAsync(role, new PermissionGrantInfo(permission.Name, true)); + } + + /// + /// Prohibits a permission for a role. + /// + /// Role + /// Permission + public async Task ProhibitPermissionAsync(TRole role, Permission permission) + { + if (!await IsGrantedAsync(role.Id, permission)) + { + return; + } + + await RolePermissionStore.RemovePermissionAsync(role, new PermissionGrantInfo(permission.Name, true)); + } + + /// + /// Prohibits all permissions for a role. + /// + /// Role + public async Task ProhibitAllPermissionsAsync(TRole role) + { + foreach (var permission in _permissionManager.GetAllPermissions()) + { + await ProhibitPermissionAsync(role, permission); + } + } + + /// + /// Resets all permission settings for a role. + /// It removes all permission settings for the role. + /// Role will have permissions those have set to true. + /// + /// Role + public async Task ResetAllPermissionsAsync(TRole role) + { + await RolePermissionStore.RemoveAllPermissionSettingsAsync(role); + } + + /// + /// Creates a role. + /// + /// Role + public override async Task CreateAsync(TRole role) + { + var result = await CheckDuplicateRoleNameAsync(role.Id, role.Name, role.DisplayName); + if (!result.Succeeded) + { + return result; + } + + var tenantId = GetCurrentTenantId(); + if (tenantId.HasValue && !role.TenantId.HasValue) + { + role.TenantId = tenantId.Value; + } + + return await base.CreateAsync(role); + } + + public override async Task UpdateAsync(TRole role) + { + var result = await CheckDuplicateRoleNameAsync(role.Id, role.Name, role.DisplayName); + if (!result.Succeeded) + { + return result; + } + + return await base.UpdateAsync(role); + } + + /// + /// Deletes a role. + /// + /// Role + public override async Task DeleteAsync(TRole role) + { + if (role.IsStatic) + { + throw new UserFriendlyException(string.Format(L("CanNotDeleteStaticRole"), role.Name)); + } + + return await base.DeleteAsync(role); + } + + /// + /// Gets a role by given id. + /// Throws exception if no role with given id. + /// + /// Role id + /// Role + /// Throws exception if no role with given id + public virtual async Task GetRoleByIdAsync(int roleId) + { + var role = await FindByIdAsync(roleId.ToString()); + if (role == null) + { + throw new AbpException("There is no role with id: " + roleId); + } + + return role; + } + + /// + /// Gets a role by given name. + /// Throws exception if no role with given roleName. + /// + /// Role name + /// Role + /// Throws exception if no role with given roleName + public virtual async Task GetRoleByNameAsync(string roleName) + { + var role = await FindByNameAsync(roleName); + if (role == null) + { + throw new AbpException("There is no role with name: " + roleName); + } + + return role; + } + + public async Task GrantAllPermissionsAsync(TRole role) + { + var permissions = _permissionManager.GetAllPermissions(role.GetMultiTenancySide()); + await SetGrantedPermissionsAsync(role, permissions); + } + + [UnitOfWork] + public virtual async Task CreateStaticRoles(int tenantId) + { + var staticRoleDefinitions = RoleManagementConfig.StaticRoles.Where(sr => sr.Side == MultiTenancySides.Tenant); + + using (_unitOfWorkManager.Current.SetTenantId(tenantId)) + { + foreach (var staticRoleDefinition in staticRoleDefinitions) + { + var role = new TRole + { + TenantId = tenantId, + Name = staticRoleDefinition.RoleName, + DisplayName = staticRoleDefinition.RoleName, + IsStatic = true + }; + + var identityResult = await CreateAsync(role); + if (!identityResult.Succeeded) + { + return identityResult; + } + } + } + + return IdentityResult.Success; + } + + public virtual async Task CheckDuplicateRoleNameAsync(int? expectedRoleId, string name, string displayName) + { + var role = await FindByNameAsync(name); + if (role != null && role.Id != expectedRoleId) + { + throw new UserFriendlyException(string.Format(L("RoleNameIsAlreadyTaken"), name)); + } + + role = await FindByDisplayNameAsync(displayName); + if (role != null && role.Id != expectedRoleId) + { + throw new UserFriendlyException(string.Format(L("RoleDisplayNameIsAlreadyTaken"), displayName)); + } + + return IdentityResult.Success; + } + + private Task FindByDisplayNameAsync(string displayName) + { + return AbpStore.FindByDisplayNameAsync(displayName); + } + + private async Task GetRolePermissionCacheItemAsync(int roleId) + { + var cacheKey = roleId + "@" + (GetCurrentTenantId() ?? 0); + return await _cacheManager.GetRolePermissionCache().GetAsync(cacheKey, async () => + { + var newCacheItem = new RolePermissionCacheItem(roleId); + + foreach (var permissionInfo in await RolePermissionStore.GetPermissionsAsync(roleId)) + { + if (permissionInfo.IsGranted) + { + newCacheItem.GrantedPermissions.Add(permissionInfo.Name); + } + } + + return newCacheItem; + }); + } + + private string L(string name) + { + return LocalizationManager.GetString(AbpZeroConsts.LocalizationSourceName, name); + } + + private int? GetCurrentTenantId() + { + if (_unitOfWorkManager.Current != null) + { + return _unitOfWorkManager.Current.GetTenantId(); + } + + return AbpSession.TenantId; + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/Roles/AbpRoleStore.cs b/src/Abp.ZeroCore/Authorization/Roles/AbpRoleStore.cs new file mode 100644 index 00000000..cc046702 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Roles/AbpRoleStore.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Abp.Authorization.Users; +using Abp.Dependency; +using Abp.Domain.Repositories; +using Abp.Domain.Uow; +using Abp.Extensions; +using Abp.Zero; +using Castle.Core.Logging; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; + +namespace Abp.Authorization.Roles +{ + /// + /// Creates a new instance of a persistence store for roles. + /// + public abstract class AbpRoleStore : + IRoleStore, + IRoleClaimStore, + IRolePermissionStore, + IQueryableRoleStore, + ITransientDependency + + where TRole : AbpRole + where TUser : AbpUser + { + public ILogger Logger { get; set; } + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + public IQueryable Roles => _roleRepository.GetAll(); + + private readonly IRepository _roleRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IRepository _rolePermissionSettingRepository; + + protected AbpRoleStore( + IUnitOfWorkManager unitOfWorkManager, + IRepository roleRepository, + IRepository rolePermissionSettingRepository) + { + _unitOfWorkManager = unitOfWorkManager; + _roleRepository = roleRepository; + _rolePermissionSettingRepository = rolePermissionSettingRepository; + + ErrorDescriber = new IdentityErrorDescriber(); + Logger = NullLogger.Instance; + } + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + protected Task SaveChanges(CancellationToken cancellationToken) + { + if (!AutoSaveChanges || _unitOfWorkManager.Current == null) + { + return Task.CompletedTask; + } + + return _unitOfWorkManager.Current.SaveChangesAsync(); + } + + /// + /// Creates a new role in a store as an asynchronous operation. + /// + /// The role to create in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task CreateAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.InsertAsync(role); + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Updates a role in a store as an asynchronous operation. + /// + /// The role to update in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task UpdateAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + role.ConcurrencyStamp = Guid.NewGuid().ToString(); + await _roleRepository.UpdateAsync(role); + + try + { + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + Logger.Warn(ex.ToString(), ex); + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Deletes a role from the store as an asynchronous operation. + /// + /// The role to delete from the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task DeleteAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.DeleteAsync(role); + + try + { + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + Logger.Warn(ex.ToString(), ex); + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the ID of the role. + public Task GetRoleIdAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Id.ToString()); + } + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// The role whose name should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public Task GetRoleNameAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Name); + } + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public Task SetRoleNameAsync([NotNull] TRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + role.Name = roleName; + return Task.CompletedTask; + } + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _roleRepository.GetAsync(id.To()); + } + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByNameAsync([NotNull] string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(normalizedName, nameof(normalizedName)); + + return _roleRepository.FirstOrDefaultAsync(r => r.NormalizedName == normalizedName); + } + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetNormalizedRoleNameAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.NormalizedName); + } + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedRoleNameAsync([NotNull] TRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + role.NormalizedName = normalizedName; + + return Task.CompletedTask; + } + + /// + /// Dispose the stores + /// + public void Dispose() + { + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a role. + public virtual async Task> GetClaimsAsync([NotNull] TRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.EnsureLoadedAsync(role, u => u.Claims, cancellationToken); + + return role.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); + } + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task AddClaimAsync([NotNull] TRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + await _roleRepository.EnsureLoadedAsync(role, u => u.Claims, cancellationToken); + + role.Claims.Add(new RoleClaim(role, claim)); + } + + /// + /// Removes the given from the specified . + /// + /// The role to remove the claim from. + /// The claim to remove from the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task RemoveClaimAsync([NotNull] TRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + await _roleRepository.EnsureLoadedAsync(role, u => u.Claims, cancellationToken); + + role.Claims.RemoveAll(c => c.ClaimValue == claim.Value && c.ClaimType == claim.Type); + } + + public virtual async Task FindByDisplayNameAsync(string displayName) + { + return await _roleRepository.FirstOrDefaultAsync( + role => role.DisplayName == displayName + ); + } + + public virtual async Task AddPermissionAsync(TRole role, PermissionGrantInfo permissionGrant) + { + if (await HasPermissionAsync(role.Id, permissionGrant)) + { + return; + } + + await _rolePermissionSettingRepository.InsertAsync( + new RolePermissionSetting + { + TenantId = role.TenantId, + RoleId = role.Id, + Name = permissionGrant.Name, + IsGranted = permissionGrant.IsGranted + }); + } + + /// + public virtual async Task RemovePermissionAsync(TRole role, PermissionGrantInfo permissionGrant) + { + await _rolePermissionSettingRepository.DeleteAsync( + permissionSetting => permissionSetting.RoleId == role.Id && + permissionSetting.Name == permissionGrant.Name && + permissionSetting.IsGranted == permissionGrant.IsGranted + ); + } + + /// + public virtual Task> GetPermissionsAsync(TRole role) + { + return GetPermissionsAsync(role.Id); + } + + public async Task> GetPermissionsAsync(int roleId) + { + return (await _rolePermissionSettingRepository.GetAllListAsync(p => p.RoleId == roleId)) + .Select(p => new PermissionGrantInfo(p.Name, p.IsGranted)) + .ToList(); + } + + /// + public virtual async Task HasPermissionAsync(int roleId, PermissionGrantInfo permissionGrant) + { + return await _rolePermissionSettingRepository.FirstOrDefaultAsync( + p => p.RoleId == roleId && + p.Name == permissionGrant.Name && + p.IsGranted == permissionGrant.IsGranted + ) != null; + } + + /// + public virtual async Task RemoveAllPermissionSettingsAsync(TRole role) + { + await _rolePermissionSettingRepository.DeleteAsync(s => s.RoleId == role.Id); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/Users/AbpLoginResult.cs b/src/Abp.ZeroCore/Authorization/Users/AbpLoginResult.cs new file mode 100644 index 00000000..31c90a94 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/AbpLoginResult.cs @@ -0,0 +1,32 @@ +using System.Security.Claims; +using Abp.MultiTenancy; + +namespace Abp.Authorization.Users +{ + public class AbpLoginResult + where TTenant : AbpTenant + where TUser : AbpUserBase + { + public AbpLoginResultType Result { get; private set; } + + public TTenant Tenant { get; private set; } + + public TUser User { get; private set; } + + public ClaimsIdentity Identity { get; private set; } + + public AbpLoginResult(AbpLoginResultType result, TTenant tenant = null, TUser user = null) + { + Result = result; + Tenant = tenant; + User = user; + } + + public AbpLoginResult(TTenant tenant, TUser user, ClaimsIdentity identity) + : this(AbpLoginResultType.Success, tenant) + { + User = user; + Identity = identity; + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/Users/AbpUser.cs b/src/Abp.ZeroCore/Authorization/Users/AbpUser.cs new file mode 100644 index 00000000..5a885bed --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/AbpUser.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using Abp.Domain.Entities.Auditing; + +namespace Abp.Authorization.Users +{ + /// + /// Represents a user. + /// + public abstract class AbpUser : AbpUserBase, IFullAudited + where TUser : AbpUser + { + /// + /// User name. + /// User name must be unique for it's tenant. + /// + [Required] + [StringLength(MaxUserNameLength)] + public virtual string NormalizedUserName { get; set; } + + /// + /// Email address of the user. + /// Email address must be unique for it's tenant. + /// + [Required] + [StringLength(MaxEmailAddressLength)] + public virtual string NormalizedEmailAddress { get; set; } + + /// + /// A random value that must change whenever a user is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString(); + + public virtual ICollection Tokens { get; set; } + + public virtual TUser DeleterUser { get; set; } + + public virtual TUser CreatorUser { get; set; } + + public virtual TUser LastModifierUser { get; set; } + + protected AbpUser() + { + Tokens = new Collection(); + } + + public void SetNormalizedNames() + { + NormalizedUserName = AdminUserName.ToUpperInvariant(); + NormalizedEmailAddress = EmailAddress.ToUpperInvariant(); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/Users/AbpUserManager.cs b/src/Abp.ZeroCore/Authorization/Users/AbpUserManager.cs new file mode 100644 index 00000000..10d0c8e0 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/AbpUserManager.cs @@ -0,0 +1,657 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Abp.Application.Features; +using Abp.Authorization.Roles; +using Abp.Configuration; +using Abp.Domain.Repositories; +using Abp.Domain.Services; +using Abp.Domain.Uow; +using Abp.Localization; +using Abp.MultiTenancy; +using Abp.Organizations; +using Abp.Runtime.Caching; +using Abp.Runtime.Session; +using Abp.UI; +using Abp.Zero; +using Abp.Zero.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Abp.Authorization.Users +{ + public abstract class AbpUserManager : UserManager, IDomainService + where TRole : AbpRole, new() + where TUser : AbpUser + { + protected IUserPermissionStore UserPermissionStore + { + get + { + if (!(Store is IUserPermissionStore)) + { + throw new AbpException("Store is not IUserPermissionStore"); + } + + return Store as IUserPermissionStore; + } + } + + public ILocalizationManager LocalizationManager { get; set; } + + public IAbpSession AbpSession { get; set; } + + public FeatureDependencyContext FeatureDependencyContext { get; set; } + + protected AbpRoleManager RoleManager { get; } + + public AbpUserStore AbpStore { get; } + + private readonly IPermissionManager _permissionManager; + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly ICacheManager _cacheManager; + private readonly IRepository _organizationUnitRepository; + private readonly IRepository _userOrganizationUnitRepository; + private readonly IOrganizationUnitSettings _organizationUnitSettings; + private readonly ISettingManager _settingManager; + + protected AbpUserManager( + AbpRoleManager roleManager, + AbpUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, + ILogger> logger, + IPermissionManager permissionManager, + IUnitOfWorkManager unitOfWorkManager, + ICacheManager cacheManager, + IRepository organizationUnitRepository, + IRepository userOrganizationUnitRepository, + IOrganizationUnitSettings organizationUnitSettings, + ISettingManager settingManager) + : base( + store, + optionsAccessor, + passwordHasher, + userValidators, + passwordValidators, + keyNormalizer, + errors, + services, + logger) + { + _permissionManager = permissionManager; + _unitOfWorkManager = unitOfWorkManager; + _cacheManager = cacheManager; + _organizationUnitRepository = organizationUnitRepository; + _userOrganizationUnitRepository = userOrganizationUnitRepository; + _organizationUnitSettings = organizationUnitSettings; + _settingManager = settingManager; + + AbpStore = store; + RoleManager = roleManager; + } + + public override async Task CreateAsync(TUser user) + { + var result = await CheckDuplicateUsernameOrEmailAddressAsync(user.Id, user.UserName, user.EmailAddress); + if (!result.Succeeded) + { + return result; + } + + var tenantId = GetCurrentTenantId(); + if (tenantId.HasValue && !user.TenantId.HasValue) + { + user.TenantId = tenantId.Value; + } + + return await base.CreateAsync(user); + } + + /// + /// Check whether a user is granted for a permission. + /// + /// User id + /// Permission name + public virtual async Task IsGrantedAsync(long userId, string permissionName) + { + return await IsGrantedAsync( + userId, + _permissionManager.GetPermission(permissionName) + ); + } + + /// + /// Check whether a user is granted for a permission. + /// + /// User + /// Permission + public virtual Task IsGrantedAsync(TUser user, Permission permission) + { + return IsGrantedAsync(user.Id, permission); + } + + /// + /// Check whether a user is granted for a permission. + /// + /// User id + /// Permission + public virtual async Task IsGrantedAsync(long userId, Permission permission) + { + //Check for multi-tenancy side + if (!permission.MultiTenancySides.HasFlag(AbpSession.MultiTenancySide)) + { + return false; + } + + //Check for depended features + if (permission.FeatureDependency != null && AbpSession.MultiTenancySide == MultiTenancySides.Tenant) + { + if (!await permission.FeatureDependency.IsSatisfiedAsync(FeatureDependencyContext)) + { + return false; + } + } + + //Get cached user permissions + var cacheItem = await GetUserPermissionCacheItemAsync(userId); + if (cacheItem == null) + { + return false; + } + + //Check for user-specific value + if (cacheItem.GrantedPermissions.Contains(permission.Name)) + { + return true; + } + + if (cacheItem.ProhibitedPermissions.Contains(permission.Name)) + { + return false; + } + + //Check for roles + foreach (var roleId in cacheItem.RoleIds) + { + if (await RoleManager.IsGrantedAsync(roleId, permission)) + { + return true; + } + } + + return false; + } + + /// + /// Gets granted permissions for a user. + /// + /// Role + /// List of granted permissions + public virtual async Task> GetGrantedPermissionsAsync(TUser user) + { + var permissionList = new List(); + + foreach (var permission in _permissionManager.GetAllPermissions()) + { + if (await IsGrantedAsync(user.Id, permission)) + { + permissionList.Add(permission); + } + } + + return permissionList; + } + + /// + /// Sets all granted permissions of a user at once. + /// Prohibits all other permissions. + /// + /// The user + /// Permissions + public virtual async Task SetGrantedPermissionsAsync(TUser user, IEnumerable permissions) + { + var oldPermissions = await GetGrantedPermissionsAsync(user); + var newPermissions = permissions.ToArray(); + + foreach (var permission in oldPermissions.Where(p => !newPermissions.Contains(p))) + { + await ProhibitPermissionAsync(user, permission); + } + + foreach (var permission in newPermissions.Where(p => !oldPermissions.Contains(p))) + { + await GrantPermissionAsync(user, permission); + } + } + + /// + /// Prohibits all permissions for a user. + /// + /// User + public async Task ProhibitAllPermissionsAsync(TUser user) + { + foreach (var permission in _permissionManager.GetAllPermissions()) + { + await ProhibitPermissionAsync(user, permission); + } + } + + /// + /// Resets all permission settings for a user. + /// It removes all permission settings for the user. + /// User will have permissions according to his roles. + /// This method does not prohibit all permissions. + /// For that, use . + /// + /// User + public async Task ResetAllPermissionsAsync(TUser user) + { + await UserPermissionStore.RemoveAllPermissionSettingsAsync(user); + } + + /// + /// Grants a permission for a user if not already granted. + /// + /// User + /// Permission + public virtual async Task GrantPermissionAsync(TUser user, Permission permission) + { + await UserPermissionStore.RemovePermissionAsync(user, new PermissionGrantInfo(permission.Name, false)); + + if (await IsGrantedAsync(user.Id, permission)) + { + return; + } + + await UserPermissionStore.AddPermissionAsync(user, new PermissionGrantInfo(permission.Name, true)); + } + + /// + /// Prohibits a permission for a user if it's granted. + /// + /// User + /// Permission + public virtual async Task ProhibitPermissionAsync(TUser user, Permission permission) + { + await UserPermissionStore.RemovePermissionAsync(user, new PermissionGrantInfo(permission.Name, true)); + + if (!await IsGrantedAsync(user.Id, permission)) + { + return; + } + + await UserPermissionStore.AddPermissionAsync(user, new PermissionGrantInfo(permission.Name, false)); + } + + public virtual async Task FindByNameOrEmailAsync(string userNameOrEmailAddress) + { + return await AbpStore.FindByNameOrEmailAsync(userNameOrEmailAddress); + } + + public virtual Task> FindAllAsync(UserLoginInfo login) + { + return AbpStore.FindAllAsync(login); + } + + /// + /// Gets a user by given id. + /// Throws exception if no user found with given id. + /// + /// User id + /// User + /// Throws exception if no user found with given id + public virtual async Task GetUserByIdAsync(long userId) + { + var user = await FindByIdAsync(userId.ToString()); + if (user == null) + { + throw new AbpException("There is no user with id: " + userId); + } + + return user; + } + + public override async Task UpdateAsync(TUser user) + { + var result = await CheckDuplicateUsernameOrEmailAddressAsync(user.Id, user.UserName, user.EmailAddress); + if (!result.Succeeded) + { + return result; + } + + //Admin user's username can not be changed! + if (user.UserName != AbpUserBase.AdminUserName) + { + if ((await GetOldUserNameAsync(user.Id)) == AbpUserBase.AdminUserName) + { + throw new UserFriendlyException(string.Format(L("CanNotRenameAdminUser"), AbpUserBase.AdminUserName)); + } + } + + return await base.UpdateAsync(user); + } + + public override async Task DeleteAsync(TUser user) + { + if (user.UserName == AbpUserBase.AdminUserName) + { + throw new UserFriendlyException(string.Format(L("CanNotDeleteAdminUser"), AbpUserBase.AdminUserName)); + } + + return await base.DeleteAsync(user); + } + + public virtual async Task ChangePasswordAsync(TUser user, string newPassword) + { + var errors = new List(); + + foreach (var validator in PasswordValidators) + { + var validationResult = await validator.ValidateAsync(this, user, newPassword); + if (!validationResult.Succeeded) + { + errors.AddRange(validationResult.Errors); + } + } + + if (errors.Any()) + { + return IdentityResult.Failed(errors.ToArray()); + } + + await AbpStore.SetPasswordHashAsync(user, PasswordHasher.HashPassword(user, newPassword)); + return IdentityResult.Success; + } + + public virtual async Task CheckDuplicateUsernameOrEmailAddressAsync(long? expectedUserId, string userName, string emailAddress) + { + var user = (await FindByNameAsync(userName)); + if (user != null && user.Id != expectedUserId) + { + throw new UserFriendlyException(string.Format(L("Identity.DuplicateUserName"), userName)); + } + + user = (await FindByEmailAsync(emailAddress)); + if (user != null && user.Id != expectedUserId) + { + throw new UserFriendlyException(string.Format(L("Identity.DuplicateEmail"), emailAddress)); + } + + return IdentityResult.Success; + } + + public virtual async Task SetRoles(TUser user, string[] roleNames) + { + await AbpStore.UserRepository.EnsureLoadedAsync(user, u => u.Roles); + + //Remove from removed roles + foreach (var userRole in user.Roles.ToList()) + { + var role = await RoleManager.FindByIdAsync(userRole.RoleId.ToString()); + if (roleNames.All(roleName => role.Name != roleName)) + { + var result = await RemoveFromRoleAsync(user, role.Name); + if (!result.Succeeded) + { + return result; + } + } + } + + //Add to added roles + foreach (var roleName in roleNames) + { + var role = await RoleManager.GetRoleByNameAsync(roleName); + if (user.Roles.All(ur => ur.RoleId != role.Id)) + { + var result = await AddToRoleAsync(user, roleName); + if (!result.Succeeded) + { + return result; + } + } + } + + return IdentityResult.Success; + } + + public virtual async Task IsInOrganizationUnitAsync(long userId, long ouId) + { + return await IsInOrganizationUnitAsync( + await GetUserByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual async Task IsInOrganizationUnitAsync(TUser user, OrganizationUnit ou) + { + return await _userOrganizationUnitRepository.CountAsync(uou => + uou.UserId == user.Id && uou.OrganizationUnitId == ou.Id + ) > 0; + } + + public virtual async Task AddToOrganizationUnitAsync(long userId, long ouId) + { + await AddToOrganizationUnitAsync( + await GetUserByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual async Task AddToOrganizationUnitAsync(TUser user, OrganizationUnit ou) + { + var currentOus = await GetOrganizationUnitsAsync(user); + + if (currentOus.Any(cou => cou.Id == ou.Id)) + { + return; + } + + await CheckMaxUserOrganizationUnitMembershipCountAsync(user.TenantId, currentOus.Count + 1); + + await _userOrganizationUnitRepository.InsertAsync(new UserOrganizationUnit(user.TenantId, user.Id, ou.Id)); + } + + public virtual async Task RemoveFromOrganizationUnitAsync(long userId, long ouId) + { + await RemoveFromOrganizationUnitAsync( + await GetUserByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual async Task RemoveFromOrganizationUnitAsync(TUser user, OrganizationUnit ou) + { + await _userOrganizationUnitRepository.DeleteAsync(uou => uou.UserId == user.Id && uou.OrganizationUnitId == ou.Id); + } + + public virtual async Task SetOrganizationUnitsAsync(long userId, params long[] organizationUnitIds) + { + await SetOrganizationUnitsAsync( + await GetUserByIdAsync(userId), + organizationUnitIds + ); + } + + private async Task CheckMaxUserOrganizationUnitMembershipCountAsync(int? tenantId, int requestedCount) + { + var maxCount = await _organizationUnitSettings.GetMaxUserMembershipCountAsync(tenantId); + if (requestedCount > maxCount) + { + throw new AbpException($"Can not set more than {maxCount} organization unit for a user!"); + } + } + + public virtual async Task SetOrganizationUnitsAsync(TUser user, params long[] organizationUnitIds) + { + if (organizationUnitIds == null) + { + organizationUnitIds = new long[0]; + } + + await CheckMaxUserOrganizationUnitMembershipCountAsync(user.TenantId, organizationUnitIds.Length); + + var currentOus = await GetOrganizationUnitsAsync(user); + + //Remove from removed OUs + foreach (var currentOu in currentOus) + { + if (!organizationUnitIds.Contains(currentOu.Id)) + { + await RemoveFromOrganizationUnitAsync(user, currentOu); + } + } + + //Add to added OUs + foreach (var organizationUnitId in organizationUnitIds) + { + if (currentOus.All(ou => ou.Id != organizationUnitId)) + { + await AddToOrganizationUnitAsync( + user, + await _organizationUnitRepository.GetAsync(organizationUnitId) + ); + } + } + } + + [UnitOfWork] + public virtual Task> GetOrganizationUnitsAsync(TUser user) + { + var query = from uou in _userOrganizationUnitRepository.GetAll() + join ou in _organizationUnitRepository.GetAll() on uou.OrganizationUnitId equals ou.Id + where uou.UserId == user.Id + select ou; + + return Task.FromResult(query.ToList()); + } + + [UnitOfWork] + public virtual Task> GetUsersInOrganizationUnit(OrganizationUnit organizationUnit, bool includeChildren = false) + { + if (!includeChildren) + { + var query = from uou in _userOrganizationUnitRepository.GetAll() + join user in Users on uou.UserId equals user.Id + where uou.OrganizationUnitId == organizationUnit.Id + select user; + + return Task.FromResult(query.ToList()); + } + else + { + var query = from uou in _userOrganizationUnitRepository.GetAll() + join user in Users on uou.UserId equals user.Id + join ou in _organizationUnitRepository.GetAll() on uou.OrganizationUnitId equals ou.Id + where ou.Code.StartsWith(organizationUnit.Code) + select user; + + return Task.FromResult(query.ToList()); + } + } + + public virtual async Task InitializeOptionsAsync(int? tenantId) + { + Options = new IdentityOptions(); + + //Lockout + Options.Lockout.AllowedForNewUsers = await IsTrueAsync(AbpZeroSettingNames.UserManagement.UserLockOut.IsEnabled, tenantId); + Options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.UserLockOut.DefaultAccountLockoutSeconds, tenantId)); + Options.Lockout.MaxFailedAccessAttempts = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.UserLockOut.MaxFailedAccessAttemptsBeforeLockout, tenantId); + + //Password complexity + Options.Password.RequireDigit = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireDigit, tenantId); + Options.Password.RequireLowercase = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireLowercase, tenantId); + Options.Password.RequireNonAlphanumeric = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireNonAlphanumeric, tenantId); + Options.Password.RequireUppercase = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.PasswordComplexity.RequireUppercase, tenantId); + Options.Password.RequiredLength = await GetSettingValueAsync(AbpZeroSettingNames.UserManagement.PasswordComplexity.RequiredLength, tenantId); + } + + protected virtual Task GetOldUserNameAsync(long userId) + { + return AbpStore.GetUserNameFromDatabaseAsync(userId); + } + + private async Task GetUserPermissionCacheItemAsync(long userId) + { + var cacheKey = userId + "@" + (GetCurrentTenantId() ?? 0); + return await _cacheManager.GetUserPermissionCache().GetAsync(cacheKey, async () => + { + var user = await FindByIdAsync(userId.ToString()); + if (user == null) + { + return null; + } + + var newCacheItem = new UserPermissionCacheItem(userId); + + foreach (var roleName in await GetRolesAsync(user)) + { + newCacheItem.RoleIds.Add((await RoleManager.GetRoleByNameAsync(roleName)).Id); + } + + foreach (var permissionInfo in await UserPermissionStore.GetPermissionsAsync(userId)) + { + if (permissionInfo.IsGranted) + { + newCacheItem.GrantedPermissions.Add(permissionInfo.Name); + } + else + { + newCacheItem.ProhibitedPermissions.Add(permissionInfo.Name); + } + } + + return newCacheItem; + }); + } + + private bool IsTrue(string settingName, int? tenantId) + { + return GetSettingValue(settingName, tenantId); + } + + private Task IsTrueAsync(string settingName, int? tenantId) + { + return GetSettingValueAsync(settingName, tenantId); + } + + private T GetSettingValue(string settingName, int? tenantId) where T : struct + { + return tenantId == null + ? _settingManager.GetSettingValueForApplication(settingName) + : _settingManager.GetSettingValueForTenant(settingName, tenantId.Value); + } + + private Task GetSettingValueAsync(string settingName, int? tenantId) where T : struct + { + return tenantId == null + ? _settingManager.GetSettingValueForApplicationAsync(settingName) + : _settingManager.GetSettingValueForTenantAsync(settingName, tenantId.Value); + } + + private string L(string name) + { + return LocalizationManager.GetString(AbpZeroConsts.LocalizationSourceName, name); + } + + private int? GetCurrentTenantId() + { + if (_unitOfWorkManager.Current != null) + { + return _unitOfWorkManager.Current.GetTenantId(); + } + + return AbpSession.TenantId; + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/Users/AbpUserManagerExtensions.cs b/src/Abp.ZeroCore/Authorization/Users/AbpUserManagerExtensions.cs new file mode 100644 index 00000000..ffabab85 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/AbpUserManagerExtensions.cs @@ -0,0 +1,30 @@ +using System; +using Abp.Authorization.Roles; +using Abp.Threading; + +namespace Abp.Authorization.Users +{ + /// + /// Extension methods for . + /// + public static class AbpUserManagerExtensions + { + /// + /// Check whether a user is granted for a permission. + /// + /// User manager + /// User id + /// Permission name + public static bool IsGranted(AbpUserManager manager, long userId, string permissionName) + where TRole : AbpRole, new() + where TUser : AbpUser + { + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + return AsyncHelper.RunSync(() => manager.IsGrantedAsync(userId, permissionName)); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Authorization/Users/AbpUserStore.cs b/src/Abp.ZeroCore/Authorization/Users/AbpUserStore.cs new file mode 100644 index 00000000..4c3f4998 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/AbpUserStore.cs @@ -0,0 +1,1289 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Abp.Authorization.Roles; +using Abp.Dependency; +using Abp.Domain.Repositories; +using Abp.Domain.Uow; +using Abp.Extensions; +using Abp.Linq; +using Abp.Runtime.Session; +using Abp.Zero; +using Castle.Core.Logging; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; + +namespace Abp.Authorization.Users +{ + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + public class AbpUserStore : + IUserLoginStore, + IUserRoleStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserPermissionStore, + IQueryableUserStore, + ITransientDependency + + where TRole : AbpRole + where TUser : AbpUser + { + public ILogger Logger { get; set; } + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + public IAbpSession AbpSession { get; set; } + + public IQueryable Users => UserRepository.GetAll(); + + public IRepository UserRepository { get; } + + private readonly IRepository _roleRepository; + private readonly IRepository _userRoleRepository; + private readonly IAsyncQueryableExecuter _asyncQueryableExecuter; + private readonly IRepository _userLoginRepository; + private readonly IRepository _userClaimRepository; + private readonly IRepository _userPermissionSettingRepository; + + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public AbpUserStore( + IUnitOfWorkManager unitOfWorkManager, + IRepository userRepository, + IRepository roleRepository, + IAsyncQueryableExecuter asyncQueryableExecuter, + IRepository userRoleRepository, + IRepository userLoginRepository, + IRepository userClaimRepository, + IRepository userPermissionSettingRepository) + { + _unitOfWorkManager = unitOfWorkManager; + UserRepository = userRepository; + _roleRepository = roleRepository; + _asyncQueryableExecuter = asyncQueryableExecuter; + _userRoleRepository = userRoleRepository; + _userLoginRepository = userLoginRepository; + _userClaimRepository = userClaimRepository; + _userPermissionSettingRepository = userPermissionSettingRepository; + + AbpSession = NullAbpSession.Instance; + ErrorDescriber = new IdentityErrorDescriber(); + Logger = NullLogger.Instance; + } + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + protected Task SaveChanges(CancellationToken cancellationToken) + { + if (!AutoSaveChanges || _unitOfWorkManager.Current == null) + { + return Task.CompletedTask; + } + + return _unitOfWorkManager.Current.SaveChangesAsync(); + } + + /// + /// Gets the user identifier for the specified . + /// + /// The user whose identifier should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the identifier for the specified . + public virtual Task GetUserIdAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.Id.ToString()); + } + + /// + /// Gets the user name for the specified . + /// + /// The user whose name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the name for the specified . + public virtual Task GetUserNameAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.UserName); + } + + /// + /// Sets the given for the specified . + /// + /// The user whose name should be set. + /// The user name to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetUserNameAsync([NotNull] TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.UserName = userName; + + return Task.CompletedTask; + } + + /// + /// Gets the normalized user name for the specified . + /// + /// The user whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the normalized user name for the specified . + public virtual Task GetNormalizedUserNameAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.NormalizedUserName); + } + + /// + /// Sets the given normalized name for the specified . + /// + /// The user whose name should be set. + /// The normalized name to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedUserNameAsync([NotNull] TUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.NormalizedUserName = normalizedName; + + return Task.CompletedTask; + } + + /// + /// Creates the specified in the user store. + /// + /// The user to create. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the creation operation. + public virtual async Task CreateAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.InsertAsync(user); + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Updates the specified in the user store. + /// + /// The user to update. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public virtual async Task UpdateAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.ConcurrencyStamp = Guid.NewGuid().ToString(); + await UserRepository.UpdateAsync(user); + + try + { + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + Logger.Warn(ex.ToString(), ex); + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Deletes the specified from the user store. + /// + /// The user to delete. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public virtual async Task DeleteAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.DeleteAsync(user); + + try + { + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + Logger.Warn(ex.ToString(), ex); + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return UserRepository.GetAsync(userId.To()); + } + + /// + /// Finds and returns a user, if any, who has the specified normalized user name. + /// + /// The normalized user name to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public virtual Task FindByNameAsync([NotNull] string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(normalizedUserName, nameof(normalizedUserName)); + + return UserRepository.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName); + } + + /// + /// Sets the password hash for a user. + /// + /// The user to set the password hash for. + /// The password hash to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPasswordHashAsync([NotNull] TUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.Password = passwordHash; + + return Task.CompletedTask; + } + + /// + /// Gets the password hash for a user. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the password hash for the user. + public virtual Task GetPasswordHashAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.Password); + } + + /// + /// Returns a flag indicating if the specified user has a password. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user has a password. If the + /// user has a password the returned value with be true, otherwise it will be false. + public virtual Task HasPasswordAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.Password != null); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddToRoleAsync([NotNull] TUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(normalizedRoleName, nameof(normalizedRoleName)); + + if (await IsInRoleAsync(user, normalizedRoleName, cancellationToken)) + { + return; + } + + var role = await _roleRepository.FirstOrDefaultAsync(r => r.NormalizedName== normalizedRoleName); + + if (role == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Role {0} does not exist!", normalizedRoleName)); + } + + user.Roles.Add(new UserRole(user.TenantId, user.Id, role.Id)); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveFromRoleAsync([NotNull] TUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException(nameof(normalizedRoleName) + " can not be null or whitespace"); + } + + if (!await IsInRoleAsync(user, normalizedRoleName, cancellationToken)) + { + return; + } + + var role = await _roleRepository.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName); + if (role == null) + { + return; + } + + user.Roles.RemoveAll(r => r.RoleId == role.Id); + } + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + [UnitOfWork] + public virtual async Task> GetRolesAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + var query = from userRole in _userRoleRepository.GetAll() + join role in _roleRepository.GetAll() on userRole.RoleId equals role.Id + where userRole.UserId == user.Id + select role.Name; + + return await _asyncQueryableExecuter.ToListAsync(query); + } + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user is a member of the given group. If the + /// user is a member of the group the returned value with be true, otherwise it will be false. + public virtual async Task IsInRoleAsync([NotNull] TUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + if (string.IsNullOrWhiteSpace(normalizedRoleName)) + { + throw new ArgumentException(nameof(normalizedRoleName) + " can not be null or whitespace"); + } + + var role = await _roleRepository.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName); + if (role == null) + { + return false; + } + + await UserRepository.EnsureLoadedAsync(user, u => u.Roles, cancellationToken); + + return user.Roles.Any(r => r.RoleId == role.Id); + } + + /// + /// Dispose the store + /// + public void Dispose() + { + + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The user whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a user. + public virtual async Task> GetClaimsAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Claims, cancellationToken); + + return user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)).ToList(); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the claim to. + /// The claim to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddClaimsAsync([NotNull] TUser user, [NotNull] IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claims, nameof(claims)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Claims, cancellationToken); + + foreach (var claim in claims) + { + user.Claims.Add(new UserClaim(user, claim)); + } + } + + /// + /// Replaces the on the specified , with the . + /// + /// The user to replace the claim on. + /// The claim replace. + /// The new claim replacing the . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task ReplaceClaimAsync([NotNull] TUser user, [NotNull] Claim claim, [NotNull] Claim newClaim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claim, nameof(claim)); + Check.NotNull(newClaim, nameof(newClaim)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Claims, cancellationToken); + + var userClaims = user.Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); + foreach (var userClaim in userClaims) + { + userClaim.ClaimType = claim.Type; + userClaim.ClaimValue = claim.Value; + } + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the claims from. + /// The claim to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveClaimsAsync([NotNull] TUser user, [NotNull] IEnumerable claims, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claims, nameof(claims)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Claims, cancellationToken); + + foreach (var claim in claims) + { + user.Claims.RemoveAll(c => c.ClaimValue == claim.Value && c.ClaimType == claim.Type); + } + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the login to. + /// The login to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddLoginAsync([NotNull] TUser user, [NotNull] UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(login, nameof(login)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Logins, cancellationToken); + + user.Logins.Add(new UserLogin(user.TenantId, user.Id, login.LoginProvider, login.ProviderKey)); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the login from. + /// The login to remove from the user. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveLoginAsync([NotNull] TUser user, [NotNull] string loginProvider, [NotNull] string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Logins, cancellationToken); + + user.Logins.RemoveAll(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); + } + + /// + /// Retrieves the associated logins for the specified . + /// + /// The user whose associated logins to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing a list of for the specified , if any. + /// + public virtual async Task> GetLoginsAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Logins, cancellationToken); + + return user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList(); + } + + /// + /// Retrieves the user associated with the specified login provider and login provider key.. + /// + /// The login provider who provided the . + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. + /// + [UnitOfWork] + public virtual Task FindByLoginAsync([NotNull] string loginProvider, [NotNull] string providerKey, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + var query = from userLogin in _userLoginRepository.GetAll() + join user in UserRepository.GetAll() on userLogin.UserId equals user.Id + where userLogin.LoginProvider == loginProvider && + userLogin.ProviderKey == providerKey && + userLogin.TenantId == AbpSession.TenantId + select user; + + return _asyncQueryableExecuter.FirstOrDefaultAsync(query); + } + + /// + /// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise + /// false. + /// + /// The user whose email confirmation status should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified + /// has been confirmed or not. + /// + public virtual Task GetEmailConfirmedAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.IsEmailConfirmed); + } + + /// + /// Sets the flag indicating whether the specified 's email address has been confirmed or not. + /// + /// The user whose email confirmation status should be set. + /// A flag indicating if the email address has been confirmed, true if the address is confirmed otherwise false. + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetEmailConfirmedAsync([NotNull] TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.IsEmailConfirmed = confirmed; + + return Task.CompletedTask; + } + + /// + /// Sets the address for a . + /// + /// The user whose email should be set. + /// The email to set. + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetEmailAsync([NotNull] TUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.EmailAddress = email; + + return Task.CompletedTask; + } + + /// + /// Gets the email address for the specified . + /// + /// The user whose email should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// The task object containing the results of the asynchronous operation, the email address for the specified . + public virtual Task GetEmailAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.EmailAddress); + } + + /// + /// Returns the normalized email for the specified . + /// + /// The user whose email address to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any associated with the specified user. + /// + public virtual Task GetNormalizedEmailAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.NormalizedEmailAddress); + } + + /// + /// Sets the normalized email for the specified . + /// + /// The user whose email address to set. + /// The normalized email to set for the specified . + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetNormalizedEmailAsync([NotNull] TUser user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.NormalizedEmailAddress = normalizedEmail; + + return Task.CompletedTask; + } + + /// + /// Gets the user, if any, associated with the specified, normalized email address. + /// + /// The normalized email address to return the user for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. + /// + public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return UserRepository.FirstOrDefaultAsync(u => u.NormalizedEmailAddress == normalizedEmail); + } + + /// + /// Gets the last a user's last lockout expired, if any. + /// Any time in the past should be indicates a user is not locked out. + /// + /// The user whose lockout date should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// A that represents the result of the asynchronous query, a containing the last time + /// a user's lockout expired, if any. + /// + public virtual Task GetLockoutEndDateAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + if (!user.LockoutEndDateUtc.HasValue) + { + return Task.FromResult(null); + } + + var lockoutEndDate = DateTime.SpecifyKind(user.LockoutEndDateUtc.Value, DateTimeKind.Utc); + + return Task.FromResult(new DateTimeOffset(lockoutEndDate)); + } + + /// + /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. + /// + /// The user whose lockout date should be set. + /// The after which the 's lockout should end. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetLockoutEndDateAsync([NotNull] TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.LockoutEndDateUtc = lockoutEnd?.UtcDateTime; + + return Task.CompletedTask; + } + + /// + /// Records that a failed access has occurred, incrementing the failed access count. + /// + /// The user whose cancellation count should be incremented. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the incremented failed access count. + public virtual Task IncrementAccessFailedCountAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.AccessFailedCount++; + + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Resets a user's failed access count. + /// + /// The user whose failed access count should be reset. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + /// This is typically called after the account is successfully accessed. + public virtual Task ResetAccessFailedCountAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.AccessFailedCount = 0; + + return Task.CompletedTask; + } + + /// + /// Retrieves the current failed access count for the specified .. + /// + /// The user whose failed access count should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the failed access count. + public virtual Task GetAccessFailedCountAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Retrieves a flag indicating whether user lockout can enabled for the specified user. + /// + /// The user whose ability to be locked out should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, true if a user can be locked out, otherwise false. + /// + public virtual Task GetLockoutEnabledAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.IsLockoutEnabled); + } + + /// + /// Set the flag indicating if the specified can be locked out.. + /// + /// The user whose ability to be locked out should be set. + /// A flag indicating if lock out can be enabled for the specified . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetLockoutEnabledAsync([NotNull] TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.IsLockoutEnabled = enabled; + + return Task.CompletedTask; + } + + /// + /// Sets the telephone number for the specified . + /// + /// The user whose telephone number should be set. + /// The telephone number to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPhoneNumberAsync([NotNull] TUser user, string phoneNumber, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.PhoneNumber = phoneNumber; + + return Task.CompletedTask; + } + + /// + /// Gets the telephone number, if any, for the specified . + /// + /// The user whose telephone number should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the user's telephone number, if any. + public virtual Task GetPhoneNumberAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.PhoneNumber); + } + + /// + /// Gets a flag indicating whether the specified 's telephone number has been confirmed. + /// + /// The user to return a flag for, indicating whether their telephone number is confirmed. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, returning true if the specified has a confirmed + /// telephone number otherwise false. + /// + public virtual Task GetPhoneNumberConfirmedAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.IsPhoneNumberConfirmed); + } + + /// + /// Sets a flag indicating if the specified 's phone number has been confirmed.. + /// + /// The user whose telephone number confirmation status should be set. + /// A flag indicating whether the user's telephone number has been confirmed. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPhoneNumberConfirmedAsync([NotNull] TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.IsPhoneNumberConfirmed = confirmed; + + return Task.CompletedTask; + } + + /// + /// Sets the provided security for the specified . + /// + /// The user whose security stamp should be set. + /// The security stamp to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetSecurityStampAsync([NotNull] TUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.SecurityStamp = stamp; + + return Task.CompletedTask; + } + + /// + /// Get the security stamp for the specified . + /// + /// The user whose security stamp should be set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the security stamp for the specified . + public virtual Task GetSecurityStampAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.SecurityStamp); + } + + /// + /// Sets a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be set. + /// A flag indicating whether the specified has two factor authentication enabled. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetTwoFactorEnabledAsync([NotNull] TUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.IsTwoFactorEnabled = enabled; + + return Task.CompletedTask; + } + + /// + /// Returns a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be set. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing a flag indicating whether the specified + /// has two factor authentication enabled or not. + /// + public virtual Task GetTwoFactorEnabledAsync([NotNull] TUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.IsTwoFactorEnabled); + } + + /// + /// Retrieves all users with the specified claim. + /// + /// The claim whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that contain the specified claim. + /// + [UnitOfWork] + public virtual async Task> GetUsersForClaimAsync([NotNull] Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(claim, nameof(claim)); + + var query = from userclaims in _userClaimRepository.GetAll() + join user in UserRepository.GetAll() on userclaims.UserId equals user.Id + where userclaims.ClaimValue == claim.Value && userclaims.ClaimType == claim.Type && userclaims.TenantId == AbpSession.TenantId + select user; + + return await _asyncQueryableExecuter.ToListAsync(query); + } + + /// + /// Retrieves all users in the specified role. + /// + /// The role whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that are in the specified role. + /// + [UnitOfWork] + public virtual async Task> GetUsersInRoleAsync([NotNull] string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (string.IsNullOrEmpty(normalizedRoleName)) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + var role = await _roleRepository.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName); + + if (role == null) + { + return new List(); + } + + var query = from userrole in _userRoleRepository.GetAll() + join user in UserRepository.GetAll() on userrole.UserId equals user.Id + where userrole.RoleId.Equals(role.Id) + select user; + + return await _asyncQueryableExecuter.ToListAsync(query); + } + + /// + /// Sets the token value for a particular user. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The value of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task SetTokenAsync([NotNull] TUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Tokens, cancellationToken); + + var token = user.Tokens.FirstOrDefault(t => t.LoginProvider == loginProvider && t.Name == name); + if (token == null) + { + user.Tokens.Add(new UserToken(user, loginProvider, name, value)); + } + else + { + token.Value = value; + } + } + + /// + /// Deletes a token for a user. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task RemoveTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Tokens, cancellationToken); + + user.Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name); + } + + /// + /// Returns the token value. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await UserRepository.EnsureLoadedAsync(user, u => u.Tokens, cancellationToken); + + return user.Tokens.FirstOrDefault(t => t.LoginProvider == loginProvider && t.Name == name)?.Value; + } + + /// + /// Tries to find a user with user name or email address in current tenant. + /// + /// User name or email address + /// User or null + public virtual async Task FindByNameOrEmailAsync(string userNameOrEmailAddress) + { + return await UserRepository.FirstOrDefaultAsync( + user => (user.UserName == userNameOrEmailAddress || user.EmailAddress == userNameOrEmailAddress) + ); + } + + /// + /// Tries to find a user with user name or email address in given tenant. + /// + /// Tenant Id + /// User name or email address + /// User or null + [UnitOfWork] + public virtual async Task FindByNameOrEmailAsync(int? tenantId, string userNameOrEmailAddress) + { + using (_unitOfWorkManager.Current.SetTenantId(tenantId)) + { + return await FindByNameOrEmailAsync(userNameOrEmailAddress); + } + } + + public virtual async Task FindAsync(UserLoginInfo login) + { + var userLogin = await _userLoginRepository.FirstOrDefaultAsync( + ul => ul.LoginProvider == login.LoginProvider && ul.ProviderKey == login.ProviderKey + ); + + if (userLogin == null) + { + return null; + } + + return await UserRepository.FirstOrDefaultAsync(u => u.Id == userLogin.UserId); + } + + [UnitOfWork] + public virtual Task> FindAllAsync(UserLoginInfo login) + { + var query = from userLogin in _userLoginRepository.GetAll() + join user in UserRepository.GetAll() on userLogin.UserId equals user.Id + where userLogin.LoginProvider == login.LoginProvider && userLogin.ProviderKey == login.ProviderKey + select user; + + return Task.FromResult(query.ToList()); + } + + [UnitOfWork] + public virtual Task FindAsync(int? tenantId, UserLoginInfo login) + { + using (_unitOfWorkManager.Current.SetTenantId(tenantId)) + { + var query = from userLogin in _userLoginRepository.GetAll() + join user in UserRepository.GetAll() on userLogin.UserId equals user.Id + where userLogin.LoginProvider == login.LoginProvider && userLogin.ProviderKey == login.ProviderKey + select user; + + return Task.FromResult(query.FirstOrDefault()); + } + } + + public async Task GetUserNameFromDatabaseAsync(long userId) + { + //note: This workaround will not be needed after fixing https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1828 + var outerUow = _unitOfWorkManager.Current; + using (var uow = _unitOfWorkManager.Begin(new UnitOfWorkOptions + { + Scope = TransactionScopeOption.RequiresNew, + IsTransactional = false, + IsolationLevel = IsolationLevel.ReadUncommitted + })) + { + if (outerUow != null) + { + _unitOfWorkManager.Current.SetTenantId(outerUow.GetTenantId()); + } + + var user = await UserRepository.GetAsync(userId); + await uow.CompleteAsync(); + return user.UserName; + } + } + + public virtual async Task AddPermissionAsync(TUser user, PermissionGrantInfo permissionGrant) + { + if (await HasPermissionAsync(user.Id, permissionGrant)) + { + return; + } + + await _userPermissionSettingRepository.InsertAsync( + new UserPermissionSetting + { + TenantId = user.TenantId, + UserId = user.Id, + Name = permissionGrant.Name, + IsGranted = permissionGrant.IsGranted + }); + } + + public virtual async Task RemovePermissionAsync(TUser user, PermissionGrantInfo permissionGrant) + { + await _userPermissionSettingRepository.DeleteAsync( + permissionSetting => permissionSetting.UserId == user.Id && + permissionSetting.Name == permissionGrant.Name && + permissionSetting.IsGranted == permissionGrant.IsGranted + ); + } + + public virtual async Task> GetPermissionsAsync(long userId) + { + return (await _userPermissionSettingRepository.GetAllListAsync(p => p.UserId == userId)) + .Select(p => new PermissionGrantInfo(p.Name, p.IsGranted)) + .ToList(); + } + + public virtual async Task HasPermissionAsync(long userId, PermissionGrantInfo permissionGrant) + { + return await _userPermissionSettingRepository.FirstOrDefaultAsync( + p => p.UserId == userId && + p.Name == permissionGrant.Name && + p.IsGranted == permissionGrant.IsGranted + ) != null; + } + + public virtual async Task RemoveAllPermissionSettingsAsync(TUser user) + { + await _userPermissionSettingRepository.DeleteAsync(s => s.UserId == user.Id); + } + } +} diff --git a/src/Abp.ZeroCore/Authorization/Users/UserToken.cs b/src/Abp.ZeroCore/Authorization/Users/UserToken.cs new file mode 100644 index 00000000..99c7dca7 --- /dev/null +++ b/src/Abp.ZeroCore/Authorization/Users/UserToken.cs @@ -0,0 +1,54 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Abp.Domain.Entities; +using JetBrains.Annotations; + +namespace Abp.Authorization.Users +{ + /// + /// Represents an authentication token for a user. + /// + [Table("AbpUserTokens")] + public class UserToken : Entity, IMayHaveTenant + { + public const int MaxLoginProviderLength = 64; + + public virtual int? TenantId { get; set; } + + /// + /// Gets or sets the primary key of the user that the token belongs to. + /// + public virtual long UserId { get; set; } + + /// + /// Gets or sets the LoginProvider this token is from. + /// + public virtual string LoginProvider { get; set; } + + /// + /// Gets or sets the name of the token. + /// + public virtual string Name { get; set; } + + /// + /// Gets or sets the token value. + /// + public virtual string Value { get; set; } + + protected UserToken() + { + + } + + protected internal UserToken(AbpUserBase user, [NotNull] string loginProvider, [NotNull] string name, string value) + { + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(name, nameof(name)); + + TenantId = user.TenantId; + UserId = user.Id; + LoginProvider = loginProvider; + Name = name; + Value = value; + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/IdentityFramework/AbpZeroServiceCollectionExtensions.cs b/src/Abp.ZeroCore/IdentityFramework/AbpZeroServiceCollectionExtensions.cs new file mode 100644 index 00000000..be8b86bf --- /dev/null +++ b/src/Abp.ZeroCore/IdentityFramework/AbpZeroServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +using System; +using Abp.Authorization; +using Abp.Authorization.Roles; +using Abp.Authorization.Users; +using Abp.MultiTenancy; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection.Extensions; + +// ReSharper disable once CheckNamespace - This is done to add extension methods to Microsoft.Extensions.DependencyInjection namespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpZeroServiceCollectionExtensions + { + public static IdentityBuilder AddAbpIdentity(this IServiceCollection services) + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + where TSecurityStampValidator : AbpSecurityStampValidator + { + return services.AddAbpIdentity(setupAction: null); + } + + public static IdentityBuilder AddAbpIdentity( + this IServiceCollection services, + Action setupAction) + where TTenant : AbpTenant + where TRole : AbpRole, new() + where TUser : AbpUser + where TSecurityStampValidator : AbpSecurityStampValidator + { + services.TryAddScoped(); + + return services.AddIdentity(setupAction); + } + } +} diff --git a/src/Abp.ZeroCore/IdentityFramework/IdentityResultExtensions.cs b/src/Abp.ZeroCore/IdentityFramework/IdentityResultExtensions.cs new file mode 100644 index 00000000..ece3212d --- /dev/null +++ b/src/Abp.ZeroCore/IdentityFramework/IdentityResultExtensions.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Abp.Collections.Extensions; +using Abp.Localization; +using Abp.Localization.Sources; +using Abp.Text; +using Abp.UI; +using Abp.Zero; +using Microsoft.AspNetCore.Identity; + +namespace Abp.IdentityFramework +{ + public static class IdentityResultExtensions + { + private static readonly Dictionary IdentityLocalizations + = new Dictionary + { + {"Optimistic concurrency failure, object has been modified.", "Identity.ConcurrencyFailure"}, + {"An unknown failure has occurred.", "Identity.DefaultError"}, + {"Email '{0}' is already taken.", "Identity.DuplicateEmail"}, + {"Role name '{0}' is already taken.", "Identity.DuplicateRoleName"}, + {"User name '{0}' is already taken.", "Identity.DuplicateUserName"}, + {"Email '{0}' is invalid.", "Identity.InvalidEmail"}, + {"The provided PasswordHasherCompatibilityMode is invalid.", "Identity.InvalidPasswordHasherCompatibilityMode"}, + {"The iteration count must be a positive integer.", "Identity.InvalidPasswordHasherIterationCount"}, + {"Role name '{0}' is invalid.", "Identity.InvalidRoleName"}, + {"Invalid token.", "Identity.InvalidToken"}, + {"User name '{0}' is invalid, can only contain letters or digits.", "Identity.InvalidUserName"}, + {"A user with this login already exists.", "Identity.LoginAlreadyAssociated"}, + {"Incorrect password.", "Identity.PasswordMismatch"}, + {"Passwords must have at least one digit ('0'-'9').", "Identity.PasswordRequiresDigit"}, + {"Passwords must have at least one lowercase ('a'-'z').", "Identity.PasswordRequiresLower"}, + {"Passwords must have at least one non alphanumeric character.", "Identity.PasswordRequiresNonAlphanumeric"}, + {"Passwords must have at least one uppercase ('A'-'Z').", "Identity.PasswordRequiresUpper"}, + {"Passwords must be at least {0} characters.", "Identity.PasswordTooShort"}, + {"Role {0} does not exist.", "Identity.RoleNotFound"}, + {"User already has a password set.", "Identity.UserAlreadyHasPassword"}, + {"User already in role '{0}'.", "Identity.UserAlreadyInRole"}, + {"User is locked out.", "Identity.UserLockedOut"}, + {"Lockout is not enabled for this user.", "Identity.UserLockoutNotEnabled"}, + {"User {0} does not exist.", "Identity.UserNameNotFound"}, + {"User is not in role '{0}'.", "Identity.UserNotInRole"} + }; + + /// + /// Checks errors of given and throws if it's not succeeded. + /// + /// Identity result to check + public static void CheckErrors(this IdentityResult identityResult) + { + if (identityResult.Succeeded) + { + return; + } + + throw new UserFriendlyException(identityResult.Errors.JoinAsString(", ")); + } + + /// + /// Checks errors of given and throws if it's not succeeded. + /// + /// Identity result to check + /// Localization manager to localize error messages + public static void CheckErrors(this IdentityResult identityResult, ILocalizationManager localizationManager) + { + if (identityResult.Succeeded) + { + return; + } + + throw new UserFriendlyException(identityResult.LocalizeErrors(localizationManager)); + } + + public static string LocalizeErrors(this IdentityResult identityResult, ILocalizationManager localizationManager) + { + if (identityResult.Succeeded) + { + throw new ArgumentException("identityResult.Succeeded should be false in order to localize errors."); + } + + if (identityResult.Errors == null) + { + throw new ArgumentException("identityResult.Errors should not be null."); + } + + return identityResult.Errors.Select(err => LocalizeErrorMessage(err.Description, localizationManager)).JoinAsString(" "); + } + + private static string LocalizeErrorMessage(string identityErrorMessage, ILocalizationManager localizationManager) + { + var localizationSource = localizationManager.GetSource(AbpZeroConsts.LocalizationSourceName); + + foreach (var identityLocalization in IdentityLocalizations) + { + string[] values; + if (FormattedStringValueExtracter.IsMatch(identityErrorMessage, identityLocalization.Key, out values)) + { + return localizationSource.GetString(identityLocalization.Value, values.Cast().ToArray()); + } + } + + return localizationSource.GetString("Identity.DefaultError"); + } + } +} \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/AbpZeroCoreModule.cs b/src/Abp.ZeroCore/Zero/AbpZeroCoreModule.cs new file mode 100644 index 00000000..cc45b15d --- /dev/null +++ b/src/Abp.ZeroCore/Zero/AbpZeroCoreModule.cs @@ -0,0 +1,28 @@ +using Abp.Localization.Dictionaries.Xml; +using Abp.Localization.Sources; +using Abp.Modules; +using Abp.Reflection.Extensions; + +namespace Abp.Zero +{ + [DependsOn(typeof(AbpZeroCommonModule))] + public class AbpZeroCoreModule : AbpModule + { + public override void PreInitialize() + { + Configuration.Localization.Sources.Extensions.Add( + new LocalizationSourceExtensionInfo( + AbpZeroConsts.LocalizationSourceName, + new XmlEmbeddedFileLocalizationDictionaryProvider( + typeof(AbpZeroCoreModule).GetAssembly(), "Abp.Zero.Localization.SourceExt" + ) + ) + ); + } + + public override void Initialize() + { + IocManager.RegisterAssemblyByConvention(typeof(AbpZeroCoreModule).GetAssembly()); + } + } +} diff --git a/src/Abp.ZeroCore/Zero/CollectionExtensions.cs b/src/Abp.ZeroCore/Zero/CollectionExtensions.cs new file mode 100644 index 00000000..297e33c0 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/CollectionExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; + +namespace Abp.Zero +{ + internal static class CollectionExtensions + { + public static IList RemoveAll([NotNull] this ICollection source, Func predicate) + { + var items = source.Where(predicate).ToList(); + + foreach (var item in items) + { + source.Remove(item); + } + + return items; + } + } +} diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-de.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-de.xml new file mode 100644 index 00000000..f64ef332 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-de.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fa.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fa.xml new file mode 100644 index 00000000..c8046a45 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fa.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fr.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fr.xml new file mode 100644 index 00000000..162abab8 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-fr.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-it.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-it.xml new file mode 100644 index 00000000..77da6de1 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-it.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lt.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lt.xml new file mode 100644 index 00000000..1e6f1c15 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lt.xml @@ -0,0 +1,10 @@ + + + + El. paštas + AbpZeroTemplate Saugumo Kodas + Jūsų saugumo kodas: {0} + Sms + Jusu saugumo kodas: {0} + + diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lv.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lv.xml new file mode 100644 index 00000000..e152d51c --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-lv.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-pt-BR.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-pt-BR.xml new file mode 100644 index 00000000..1b0aa21f --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-pt-BR.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-ru.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-ru.xml new file mode 100644 index 00000000..2aeac5df --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-ru.xml @@ -0,0 +1,10 @@ + + + + Email + Секретный код + Ваш секретный код: {0} + Смс + Ваш секретный код: {0} + + diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-tr.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-tr.xml new file mode 100644 index 00000000..321a207b --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-tr.xml @@ -0,0 +1,35 @@ + + + + İyimser eşzamanlılık hatası. Nesne değiştirilmiş. + Bilinmeyen bir hata oluştu. + '{0}' email adresi zaten alınmış. + '{0}' rol ismi zaten alınmış. + '{0}' kullanıcı adı zaten alınmış. + '{0}' email adresi hatalı. + Belirtilen PasswordHasherCompatibilityMode geçersiz. + Iterasyon sayısı sıfırdan büyük bir sayı olmalı. + '{0}' rol ismi geçersizdir. + Geçersiz token. + '{0}' kullanıcı adı geçersiz, sadece harf ve rakamlardan oluşmalı. + Bu giriş bilgilerine sahip bir kullanıcı zaten var. + Hatalı şifre. + Şifre en az bir sayı içermeli ('0'-'9'). + Şifre en az bir küçük harf içermeli ('a'-'z'). + Şifre en az bir sayı ya da harf olmayan karakter içermeli. + Şifre en az bir büyük harf içermeli ('A'-'Z'). + Şifre en az {0} karakter uzunluğunda olmalı. + {0} rolü bulunamadı. + Kullanıcının zaten bir şifresi var. + Kullanıcı zaten '{0}' rolünde. + Kullanıcı hesabı kilitlenmiş. + Bu kullanıcı için hesap kilitleme etkin değil. + {0} kullanıcısı bulunamadı. + Kullanıcı '{0}' rolünde değil. + E-posta + Güvenlik Kodu + Güvenlik kodunuz: {0} + Sms + Güvenlik kodunuz: {0} + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-zh-CN.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-zh-CN.xml new file mode 100644 index 00000000..d64d1e9d --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero-zh-CN.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero.xml b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero.xml new file mode 100644 index 00000000..e2ceab28 --- /dev/null +++ b/src/Abp.ZeroCore/Zero/Localization/SourceExt/AbpZero.xml @@ -0,0 +1,35 @@ + + + + Optimistic concurrency failure, object has been modified. + An unknown failure has occurred. + Email '{0}' is already taken. + Role name '{0}' is already taken. + User name '{0}' is already taken. + Email '{0}' is invalid. + The provided PasswordHasherCompatibilityMode is invalid. + The iteration count must be a positive integer. + Role name '{0}' is invalid. + Invalid token. + User name '{0}' is invalid, can only contain letters or digits. + A user with this login already exists. + Incorrect password. + Passwords must have at least one digit ('0'-'9'). + Passwords must have at least one lowercase ('a'-'z'). + Passwords must have at least one non alphanumeric character. + Passwords must have at least one uppercase ('A'-'Z'). + Passwords must be at least {0} characters. + Role {0} does not exist. + User already has a password set. + User already in role '{0}'. + User is locked out. + Lockout is not enabled for this user. + User {0} does not exist. + User is not in role '{0}'. + Email + Security Code + Your security code is: {0} + Sms + Your security code is: {0} + + \ No newline at end of file diff --git a/test/Abp.Zero.SampleApp.EntityFramework/Abp.Zero.SampleApp.EntityFramework.csproj b/test/Abp.Zero.SampleApp.EntityFramework/Abp.Zero.SampleApp.EntityFramework.csproj index 1657588d..6d57f311 100644 --- a/test/Abp.Zero.SampleApp.EntityFramework/Abp.Zero.SampleApp.EntityFramework.csproj +++ b/test/Abp.Zero.SampleApp.EntityFramework/Abp.Zero.SampleApp.EntityFramework.csproj @@ -2,7 +2,7 @@ 1.5.0.0 - net452 + net46 Abp.Zero.SampleApp.EntityFramework Abp.Zero.SampleApp.EntityFramework false @@ -11,6 +11,7 @@ false false false + Abp.Zero.SampleApp @@ -18,7 +19,7 @@ - + diff --git a/test/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest.csproj b/test/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest.csproj index 3c177e55..95ef3316 100644 --- a/test/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest.csproj +++ b/test/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest/Abp.Zero.SampleApp.EntityFrameworkCore.ConsoleAppTest.csproj @@ -11,6 +11,12 @@ win7-x64 + + + + + + PreserveNewest @@ -21,12 +27,6 @@ - - - - - - diff --git a/test/Abp.Zero.SampleApp.EntityFrameworkCore.Tests/Abp.Zero.SampleApp.EntityFrameworkCore.Tests.csproj b/test/Abp.Zero.SampleApp.EntityFrameworkCore.Tests/Abp.Zero.SampleApp.EntityFrameworkCore.Tests.csproj index d71838aa..2a597661 100644 --- a/test/Abp.Zero.SampleApp.EntityFrameworkCore.Tests/Abp.Zero.SampleApp.EntityFrameworkCore.Tests.csproj +++ b/test/Abp.Zero.SampleApp.EntityFrameworkCore.Tests/Abp.Zero.SampleApp.EntityFrameworkCore.Tests.csproj @@ -13,14 +13,14 @@ + + - - diff --git a/test/Abp.Zero.SampleApp.NHibernateTests/Abp.Zero.SampleApp.NHibernateTests.csproj b/test/Abp.Zero.SampleApp.NHibernateTests/Abp.Zero.SampleApp.NHibernateTests.csproj index bb393445..7b0467b1 100644 --- a/test/Abp.Zero.SampleApp.NHibernateTests/Abp.Zero.SampleApp.NHibernateTests.csproj +++ b/test/Abp.Zero.SampleApp.NHibernateTests/Abp.Zero.SampleApp.NHibernateTests.csproj @@ -1,13 +1,14 @@  - net452 + net46 Abp.Zero.SampleApp.NHibernateTests Abp.Zero.SampleApp.NHibernateTests true false false false + Abp.Zero.SampleApp @@ -22,17 +23,17 @@ + - - + - + diff --git a/test/Abp.Zero.SampleApp.NHibernateTests/NHibernate/TestDatas/InitialTestDataBuilder.cs b/test/Abp.Zero.SampleApp.NHibernateTests/NHibernate/TestDatas/InitialTestDataBuilder.cs index 42f95c9e..f79a6cf7 100644 --- a/test/Abp.Zero.SampleApp.NHibernateTests/NHibernate/TestDatas/InitialTestDataBuilder.cs +++ b/test/Abp.Zero.SampleApp.NHibernateTests/NHibernate/TestDatas/InitialTestDataBuilder.cs @@ -13,8 +13,6 @@ public InitialTestDataBuilder(ISession session) public void Build() { - //_session.DisableAllFilters(); //TODO: Needs? - new InitialTenantsBuilder(_session).Build(); new InitialUsersBuilder(_session).Build(); new InitialTestLanguagesBuilder(_session).Build(); diff --git a/test/Abp.Zero.SampleApp.Tests/Abp.Zero.SampleApp.Tests.csproj b/test/Abp.Zero.SampleApp.Tests/Abp.Zero.SampleApp.Tests.csproj index 3ad08cc6..39a55aac 100644 --- a/test/Abp.Zero.SampleApp.Tests/Abp.Zero.SampleApp.Tests.csproj +++ b/test/Abp.Zero.SampleApp.Tests/Abp.Zero.SampleApp.Tests.csproj @@ -1,7 +1,7 @@  - net452 + net46 Abp.Zero.SampleApp.Tests Abp.Zero.SampleApp.Tests true @@ -20,17 +20,17 @@ + - - + diff --git a/test/Abp.Zero.SampleApp.Tests/Ldap/LdapAuthenticationSource_Tests.cs b/test/Abp.Zero.SampleApp.Tests/Ldap/LdapAuthenticationSource_Tests.cs index b9c75182..351588bf 100644 --- a/test/Abp.Zero.SampleApp.Tests/Ldap/LdapAuthenticationSource_Tests.cs +++ b/test/Abp.Zero.SampleApp.Tests/Ldap/LdapAuthenticationSource_Tests.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Abp.Authorization; using Abp.Authorization.Users; using Abp.Configuration; using Abp.Dependency; diff --git a/test/Abp.Zero.SampleApp.Tests/MultiTenancy/TenantManager_Tests.cs b/test/Abp.Zero.SampleApp.Tests/MultiTenancy/TenantManager_Tests.cs index 6c18d3a6..96328109 100644 --- a/test/Abp.Zero.SampleApp.Tests/MultiTenancy/TenantManager_Tests.cs +++ b/test/Abp.Zero.SampleApp.Tests/MultiTenancy/TenantManager_Tests.cs @@ -17,12 +17,14 @@ public TenantManager_Tests() [Fact] public async Task Should_Not_Create_Duplicate_Tenant() { - (await _tenantManager.CreateAsync(new Tenant("Tenant-X", "Tenant X"))).Succeeded.ShouldBe(true); + await _tenantManager.CreateAsync(new Tenant("Tenant-X", "Tenant X")); //Trying to re-create with same tenancy name - - var result = (await _tenantManager.CreateAsync(new Tenant("Tenant-X", "Tenant X"))); - result.Succeeded.ShouldBe(false); + + await Assert.ThrowsAnyAsync(async () => + { + await _tenantManager.CreateAsync(new Tenant("Tenant-X", "Tenant X")); + }); } } } diff --git a/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_ExternalAuthenticationSources_Test.cs b/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_ExternalAuthenticationSources_Test.cs index 3df700b9..2b1d38b3 100644 --- a/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_ExternalAuthenticationSources_Test.cs +++ b/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_ExternalAuthenticationSources_Test.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Abp.Authorization; using Abp.Authorization.Users; using Abp.Dependency; using Abp.Modules; diff --git a/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_Tests.cs b/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_Tests.cs index 7cb21403..d4265ba5 100644 --- a/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_Tests.cs +++ b/test/Abp.Zero.SampleApp.Tests/Users/UserLogin_Tests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Abp.Authorization; using Abp.Authorization.Users; using Abp.Configuration; using Abp.Configuration.Startup; diff --git a/test/Abp.Zero.SampleApp.Tests/Users/UserManager_Lockout_Tests.cs b/test/Abp.Zero.SampleApp.Tests/Users/UserManager_Lockout_Tests.cs index de53967d..c7db5cc6 100644 --- a/test/Abp.Zero.SampleApp.Tests/Users/UserManager_Lockout_Tests.cs +++ b/test/Abp.Zero.SampleApp.Tests/Users/UserManager_Lockout_Tests.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Abp.Authorization; using Abp.Authorization.Users; using Abp.Configuration; using Abp.Threading; diff --git a/test/Abp.Zero.SampleApp/Abp.Zero.SampleApp.csproj b/test/Abp.Zero.SampleApp/Abp.Zero.SampleApp.csproj index b9fd1e4c..736b2012 100644 --- a/test/Abp.Zero.SampleApp/Abp.Zero.SampleApp.csproj +++ b/test/Abp.Zero.SampleApp/Abp.Zero.SampleApp.csproj @@ -2,7 +2,7 @@ 1.5.0.0 - net452 + net46 Abp.Zero.SampleApp Abp.Zero.SampleApp false @@ -18,7 +18,7 @@ - +