diff --git a/.gitignore b/.gitignore index f1e3d20..5566f35 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.user *.userosscache *.sln.docstates +*.sln.DotSettings # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/README.md b/README.md index ca0c90e..a0ef203 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ Light weight REST service framework for ASP.NET Core ### Branch Status -[![NuGet](https://img.shields.io/nuget/v/RService.IO.svg)](https://www.nuget.org/packages/RService.IO/) [![NuGet](https://img.shields.io/nuget/v/RService.IO.Abstractions.svg)](https://www.nuget.org/packages/RService.IO.Abstractions/) +[![NuGet](https://img.shields.io/nuget/v/RService.IO.svg)](https://www.nuget.org/packages/RService.IO/) +[![NuGet](https://img.shields.io/nuget/v/RService.IO.Abstractions.svg)](https://www.nuget.org/packages/RService.IO.Abstractions/) +[![NuGet](https://img.shields.io/nuget/v/RService.IO.Authorization.svg)](https://www.nuget.org/packages/RService.IO.Authorization/) #### Master [![Build status](https://ci.appveyor.com/api/projects/status/t65h3ok91ljwm30t/branch/master?svg=true)](https://ci.appveyor.com/project/Stoom/rservice-io/branch/master) -[![Coverage Status](https://coveralls.io/repos/github/Stoom/RService.IO/badge.svg?branch=master)](https://coveralls.io/github/Stoom/RService.IO?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/Stoom/RService.IO/badge.svg?branch=master)](https://coveralls.io/github/Stoom/RService.IO?branch=master) [![Codecov branch](https://img.shields.io/codecov/c/github/Stoom/RService.IO/master.svg)](https://codecov.io/gh/Stoom/RService.IO/branch/master) #### Develop [![Build status](https://ci.appveyor.com/api/projects/status/t65h3ok91ljwm30t/branch/develop?svg=true)](https://ci.appveyor.com/project/Stoom/rservice-io/branch/develop) diff --git a/RService.IO.sln b/RService.IO.sln index 845eb91..da3f652 100644 --- a/RService.IO.sln +++ b/RService.IO.sln @@ -1,59 +1,73 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B748CB97-7861-4FC7-828D-11C44E540630}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64F366F9-AA78-455D-B3C7-94B9F14814AA}" - ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml - global.json = global.json - LICENSE = LICENSE - NuGet.config = NuGet.config - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Tests", "test\RService.IO.Tests\RService.IO.Tests.xproj", "{29E250BF-7D21-4B2D-81DD-F527C0CC3286}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO", "src\RService.IO\RService.IO.xproj", "{80003DA0-A02B-487A-8E22-973B5A4BF0CD}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Rservice.IO.Tests.Integration", "test\Rservice.IO.Tests.Integration\Rservice.IO.Tests.Integration.xproj", "{CE9393A2-ED69-4748-913B-3C11143957F5}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Abstractions", "src\RService.IO.Abstractions\RService.IO.Abstractions.xproj", "{4474DEEE-06F5-4706-97A2-A437E258F485}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Release|Any CPU.Build.0 = Release|Any CPU - {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Release|Any CPU.Build.0 = Release|Any CPU - {CE9393A2-ED69-4748-913B-3C11143957F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE9393A2-ED69-4748-913B-3C11143957F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE9393A2-ED69-4748-913B-3C11143957F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE9393A2-ED69-4748-913B-3C11143957F5}.Release|Any CPU.Build.0 = Release|Any CPU - {4474DEEE-06F5-4706-97A2-A437E258F485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4474DEEE-06F5-4706-97A2-A437E258F485}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4474DEEE-06F5-4706-97A2-A437E258F485}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4474DEEE-06F5-4706-97A2-A437E258F485}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {29E250BF-7D21-4B2D-81DD-F527C0CC3286} = {1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4} - {80003DA0-A02B-487A-8E22-973B5A4BF0CD} = {B748CB97-7861-4FC7-828D-11C44E540630} - {CE9393A2-ED69-4748-913B-3C11143957F5} = {1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4} - {4474DEEE-06F5-4706-97A2-A437E258F485} = {B748CB97-7861-4FC7-828D-11C44E540630} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B748CB97-7861-4FC7-828D-11C44E540630}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64F366F9-AA78-455D-B3C7-94B9F14814AA}" + ProjectSection(SolutionItems) = preProject + appveyor.yml = appveyor.yml + global.json = global.json + LICENSE = LICENSE + NuGet.config = NuGet.config + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Tests", "test\RService.IO.Tests\RService.IO.Tests.xproj", "{29E250BF-7D21-4B2D-81DD-F527C0CC3286}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO", "src\RService.IO\RService.IO.xproj", "{80003DA0-A02B-487A-8E22-973B5A4BF0CD}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Rservice.IO.Tests.Integration", "test\Rservice.IO.Tests.Integration\Rservice.IO.Tests.Integration.xproj", "{CE9393A2-ED69-4748-913B-3C11143957F5}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Abstractions", "src\RService.IO.Abstractions\RService.IO.Abstractions.xproj", "{4474DEEE-06F5-4706-97A2-A437E258F485}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Authorization", "src\RService.IO.Authorization\RService.IO.Authorization.xproj", "{4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RService.IO.Authorization.Tests", "test\RService.IO.Authorization.Tests\RService.IO.Authorization.Tests.xproj", "{44B66719-E3DC-4F39-A67D-196BCB9A0FA7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29E250BF-7D21-4B2D-81DD-F527C0CC3286}.Release|Any CPU.Build.0 = Release|Any CPU + {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80003DA0-A02B-487A-8E22-973B5A4BF0CD}.Release|Any CPU.Build.0 = Release|Any CPU + {CE9393A2-ED69-4748-913B-3C11143957F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE9393A2-ED69-4748-913B-3C11143957F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE9393A2-ED69-4748-913B-3C11143957F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE9393A2-ED69-4748-913B-3C11143957F5}.Release|Any CPU.Build.0 = Release|Any CPU + {4474DEEE-06F5-4706-97A2-A437E258F485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4474DEEE-06F5-4706-97A2-A437E258F485}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4474DEEE-06F5-4706-97A2-A437E258F485}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4474DEEE-06F5-4706-97A2-A437E258F485}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B}.Release|Any CPU.Build.0 = Release|Any CPU + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {29E250BF-7D21-4B2D-81DD-F527C0CC3286} = {1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4} + {80003DA0-A02B-487A-8E22-973B5A4BF0CD} = {B748CB97-7861-4FC7-828D-11C44E540630} + {CE9393A2-ED69-4748-913B-3C11143957F5} = {1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4} + {4474DEEE-06F5-4706-97A2-A437E258F485} = {B748CB97-7861-4FC7-828D-11C44E540630} + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B} = {B748CB97-7861-4FC7-828D-11C44E540630} + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7} = {1F1CF499-A7DD-4BEE-B9B5-E5A87E0ADDA4} + EndGlobalSection +EndGlobal diff --git a/appveyor.yml b/appveyor.yml index 526b190..e548647 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,5 @@ version: '0.1.1-{build}' configuration: -- Debug - Release platform: Any CPU environment: @@ -19,18 +18,29 @@ before_build: build_script: - dotnet build "src\RService.IO" -c %CONFIGURATION% --version-suffix %LABEL% - dotnet build "src\RService.IO.Abstractions" -c %CONFIGURATION% --version-suffix %LABEL% +- dotnet build "src\RService.IO.Authorization" -c %CONFIGURATION% --version-suffix %LABEL% test_script: - ps: '& ${env:homedrive}${env:homepath}\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe "-target:C:\Program Files\dotnet\dotnet.exe" -targetargs:"test .\test\RService.IO.Tests" -register:user -filter:"+[RService.IO*]* -[RService.IO.Tests*]*" -output:coverage.xml -oldStyle' + - ps: '& ${env:homedrive}${env:homepath}\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe "-target:C:\Program Files\dotnet\dotnet.exe" -targetargs:"test .\test\RService.IO.Authorization.Tests" -register:user -filter:"+[RService.IO*]* -[RService.IO.Authorization.Tests*]*" -mergeoutput -output:coverage.xml -oldStyle' - ps: '& ${env:homedrive}${env:homepath}\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe "-target:C:\Program Files\dotnet\dotnet.exe" -targetargs:"test .\test\RService.IO.Tests.Integration" -register:user -filter:"+[RService.IO*]* -[RService.IO.Tests*]*" -mergeoutput -output:coverage.xml -oldStyle' - ps: '& ${env:homedrive}${env:homepath}\.nuget\packages\coveralls.io\1.3.4\tools\coveralls.net.exe --opencover coverage.xml' - ps: pip install codecov; codecov -f coverage.xml -X gcov after_test: -#- dotnet pack "src\LibNETStandard10" -c %CONFIGURATION% --no-build --version-suffix %LABEL% -o artifacts -#- dotnet publish "src\ConsoleApplication" -c %CONFIGURATION% --no-build --version-suffix %LABEL% -o artifacts\ConsoleApplication - ps: dotnet pack "src\RService.IO" -c ${env:CONFIGUREATION} --no-build --version-suffix ${env:LABEL} -o artifacts - ps: dotnet pack "src\RService.IO.Abstractions" -c ${env:CONFIGUREATION} --no-build --version-suffix ${env:LABEL} -o artifacts + - ps: dotnet pack "src\RService.IO.Authorization" -c ${env:CONFIGUREATION} --no-build --version-suffix ${env:LABEL} -o artifacts artifacts: - path: '**/RService.IO.*.nupkg' name: NuGet Package +deploy: + - provider: NuGet + api_key: + secure: TafSc421VlrLm7iCk6Xm2/ss0aZT8EXfcU2DEarrIeoWxg+NxXH8apUKAV+oT8Kp + skip_symbols: false + artifact: /.*\.nupkg/ + on: + branch: master + configuration: Release + appveyor_repo_tag: true #cache: #- '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/src/RService.IO.Abstractions/Delegate.cs b/src/RService.IO.Abstractions/Delegate.cs index fce4321..5db5da6 100644 --- a/src/RService.IO.Abstractions/Delegate.cs +++ b/src/RService.IO.Abstractions/Delegate.cs @@ -12,10 +12,10 @@ public static class Delegate /// The service's response. public delegate object Activator(object target, params object[] args); /// - /// Creates a DTO based on JSON. + /// Creates a DTO based on a request body. /// - /// JSON to create DTO from. + /// The request body to create DTO from. /// The DTO. - public delegate object DtoCtor(string json); + public delegate object DtoCtor(string body); } } \ No newline at end of file diff --git a/src/RService.IO/DelegateFactory.cs b/src/RService.IO.Abstractions/DelegateFactory.cs similarity index 88% rename from src/RService.IO/DelegateFactory.cs rename to src/RService.IO.Abstractions/DelegateFactory.cs index 0f22fb2..2a14f9b 100644 --- a/src/RService.IO/DelegateFactory.cs +++ b/src/RService.IO.Abstractions/DelegateFactory.cs @@ -3,11 +3,10 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Delegate = RService.IO.Abstractions.Delegate; -namespace RService.IO +namespace RService.IO.Abstractions { - public sealed class DelegateFactory + public static class DelegateFactory { /// /// Generates a dynamic method call. @@ -60,18 +59,17 @@ public static Delegate.Activator GenerateMethodCall(MethodInfo method) /// Generates a dynamic DTO. /// /// The of the DTO to create. + /// The of the generic deserializer. /// A delegate. /// The results from this function should be cached. - public static Delegate.DtoCtor GenerateDtoCtor(Type dtoType) + public static Delegate.DtoCtor GenerateDtoCtor(Type dtoType, MethodInfo deserializerMethod) { // Methods - var deserializeMethod = typeof(NetJSON.NetJSON) - .GetMethod("Deserialize", new[] { typeof(string) }) - .MakeGenericMethod(dtoType); + var deserializeMethod = deserializerMethod.MakeGenericMethod(dtoType); var dtoCtor = dtoType.GetConstructors().First(); // Properties and fields - var jsonParam = Expression.Parameter(typeof(string), "Json Body"); + var bodyParam = Expression.Parameter(typeof(string), "Body"); var reqDtoVar = Expression.Variable(dtoType, "Request Dto"); // Return @@ -86,7 +84,7 @@ public static Delegate.DtoCtor GenerateDtoCtor(Type dtoType) var callExpressions = new List { // Deserialize or ctor - Expression.Assign(reqDtoVar, Expression.Call(deserializeMethod, jsonParam)), + Expression.Assign(reqDtoVar, Expression.Call(deserializeMethod, bodyParam)), Expression.IfThen( Expression.Equal(reqDtoVar, nullConst), Expression.Assign(reqDtoVar, Expression.New(dtoCtor)) @@ -100,7 +98,7 @@ public static Delegate.DtoCtor GenerateDtoCtor(Type dtoType) var lambda = Expression.Lambda( Expression.Convert(call, typeof(object)), - jsonParam); + bodyParam); return lambda.Compile(); } diff --git a/src/RService.IO.Abstractions/Properties/AssemblyInfo.cs b/src/RService.IO.Abstractions/Properties/AssemblyInfo.cs index 3226436..b6f5c40 100644 --- a/src/RService.IO.Abstractions/Properties/AssemblyInfo.cs +++ b/src/RService.IO.Abstractions/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/RService.IO.Abstractions/IAuthProvider.cs b/src/RService.IO.Abstractions/Providers/IAuthProvider.cs similarity index 95% rename from src/RService.IO.Abstractions/IAuthProvider.cs rename to src/RService.IO.Abstractions/Providers/IAuthProvider.cs index b17b499..6c69a25 100644 --- a/src/RService.IO.Abstractions/IAuthProvider.cs +++ b/src/RService.IO.Abstractions/Providers/IAuthProvider.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -namespace RService.IO.Abstractions +namespace RService.IO.Abstractions.Providers { /// /// Determines if a service endpoint is authenticated and authorized to be executed. diff --git a/src/RService.IO.Abstractions/ISerializationProvider.cs b/src/RService.IO.Abstractions/Providers/ISerializationProvider.cs similarity index 93% rename from src/RService.IO.Abstractions/ISerializationProvider.cs rename to src/RService.IO.Abstractions/Providers/ISerializationProvider.cs index 41f1d6a..e9aa926 100644 --- a/src/RService.IO.Abstractions/ISerializationProvider.cs +++ b/src/RService.IO.Abstractions/Providers/ISerializationProvider.cs @@ -1,7 +1,7 @@ using System; using Microsoft.AspNetCore.Http; -namespace RService.IO.Abstractions +namespace RService.IO.Abstractions.Providers { /// /// Describes how to hydrate (deserialize), and dehydrate (serialize) diff --git a/src/RService.IO.Abstractions/IServiceProvider.cs b/src/RService.IO.Abstractions/Providers/IServiceProvider.cs similarity index 89% rename from src/RService.IO.Abstractions/IServiceProvider.cs rename to src/RService.IO.Abstractions/Providers/IServiceProvider.cs index 2efb527..c15300c 100644 --- a/src/RService.IO.Abstractions/IServiceProvider.cs +++ b/src/RService.IO.Abstractions/Providers/IServiceProvider.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -namespace RService.IO.Abstractions +namespace RService.IO.Abstractions.Providers { /// /// Supports reading request and calling user service endpoints. diff --git a/src/RService.IO.Abstractions/project.json b/src/RService.IO.Abstractions/project.json index d233710..6e5fa07 100644 --- a/src/RService.IO.Abstractions/project.json +++ b/src/RService.IO.Abstractions/project.json @@ -1,6 +1,6 @@ { "title": "RService.IO.Abstractions", - "version": "0.3.0", + "version": "0.4.0", "packOptions": { "summary": "The abstractions required for implementing a RService.IO service and routes.", @@ -24,8 +24,9 @@ }, "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", - "Microsoft.AspNetCore.Routing.Abstractions": "1.0.0" + "Microsoft.AspNetCore.Http.Abstractions": "1.1.0", + "Microsoft.AspNetCore.Routing.Abstractions": "1.1.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0" }, "frameworks": { diff --git a/src/RService.IO.Authorization/DependencyInjection/ServiceCollectionExtensions.cs b/src/RService.IO.Authorization/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..09bb758 --- /dev/null +++ b/src/RService.IO.Authorization/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RService.IO.Abstractions.Providers; +using RService.IO.Authorization.Providers; + +namespace RService.IO.Authorization.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddRServiceIoAuthorization( + this IServiceCollection services) + { + services.AddOptions(); + + services.TryAddTransient(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/RService.IO.Authorization/Properties/AssemblyInfo.cs b/src/RService.IO.Authorization/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a4f791d --- /dev/null +++ b/src/RService.IO.Authorization/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("RService.IO.Authorization")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RService.IO.Authorization")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B")] \ No newline at end of file diff --git a/src/RService.IO/Providers/AuthProvider.cs b/src/RService.IO.Authorization/Providers/AuthProvider.cs similarity index 97% rename from src/RService.IO/Providers/AuthProvider.cs rename to src/RService.IO.Authorization/Providers/AuthProvider.cs index 53183ed..ebb09c3 100644 --- a/src/RService.IO/Providers/AuthProvider.cs +++ b/src/RService.IO.Authorization/Providers/AuthProvider.cs @@ -1,108 +1,109 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Internal; -using RService.IO.Abstractions; - -namespace RService.IO.Providers -{ - /// - /// Default implementation of . - /// - public class AuthProvider : IAuthProvider - { - private readonly IAuthorizationPolicyProvider _policyProvider; - private readonly ConcurrentDictionary> _cachedAuthAttributes; - - /// - /// Constructs the default auth provider. - /// - /// - /// The to check authorization to specified methods and classes. - /// - public AuthProvider(IAuthorizationPolicyProvider provider) - { - if (provider == null) - throw new ArgumentNullException(nameof(provider)); - - _policyProvider = provider; - _cachedAuthAttributes = new ConcurrentDictionary>(); - } - - /// - public Task IsAuthorizedAsync(HttpContext ctx, ServiceMetadata metadata) - { - if (ctx == null) - throw new ArgumentNullException(nameof(ctx)); - if (metadata == null) - throw new ArgumentNullException(nameof(metadata)); - - IEnumerable authenticationAttributes; - - if (!_cachedAuthAttributes.TryGetValue(metadata.Ident, out authenticationAttributes)) - { - var serviceAuthAttributes = new List(); - serviceAuthAttributes.AddRange(metadata.Service.GetCustomAttributes()); - serviceAuthAttributes.AddRange(metadata.Service.GetCustomAttributes()); - - serviceAuthAttributes.AddRange(metadata.Method.GetCustomAttributes()); - serviceAuthAttributes.AddRange(metadata.Method.GetCustomAttributes()); - - _cachedAuthAttributes.TryAdd(metadata.Ident, serviceAuthAttributes); - authenticationAttributes = serviceAuthAttributes; - } - - return IsAuthorizedAsync(ctx, authenticationAttributes); - } - - /// - public async Task IsAuthorizedAsync(HttpContext ctx, IEnumerable authorizationFilters) - { - if (ctx == null) - throw new ArgumentNullException(nameof(ctx)); - if (authorizationFilters == null) - throw new ArgumentNullException(nameof(authorizationFilters)); - - var authFilters = authorizationFilters.ToList(); - - // Allow Anonymous skips all authorization - if (authFilters.Any(x => x is IAllowAnonymous)) - return true; - - // Get the authorization policy - var authData = authFilters.Where(x => x is IAuthorizeData).Cast().ToList(); - var effectivePolicy = await AuthorizationPolicy.CombineAsync(_policyProvider, authData); - - if (effectivePolicy == null) - return true; - - // Build a ClaimsPrincipal with the policy's required authentication types - if (effectivePolicy.AuthenticationSchemes?.Any() ?? false) - { - ClaimsPrincipal newPrincipal = null; - foreach (var scheme in effectivePolicy.AuthenticationSchemes) - { - var result = await ctx.Authentication.AuthenticateAsync(scheme); - if (result != null) - { - newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result); - } - } - // If all schemes failed authentication, provide a default identity anyways - ctx.User = newPrincipal ?? new ClaimsPrincipal(new ClaimsIdentity()); - } - - var authService = ctx.RequestServices.GetRequiredService(); - - // Note: Default Anonymous User is new ClaimsPrincipal(new ClaimsIdentity()) - return await authService.AuthorizeAsync(ctx.User, effectivePolicy); - } - } +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; + +namespace RService.IO.Authorization.Providers +{ + /// + /// Default implementation of . + /// + public class AuthProvider : IAuthProvider + { + private readonly IAuthorizationPolicyProvider _policyProvider; + private readonly ConcurrentDictionary> _cachedAuthAttributes; + + /// + /// Constructs the default auth provider. + /// + /// + /// The to check authorization to specified methods and classes. + /// + public AuthProvider(IAuthorizationPolicyProvider provider) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + _policyProvider = provider; + _cachedAuthAttributes = new ConcurrentDictionary>(); + } + + /// + public Task IsAuthorizedAsync(HttpContext ctx, ServiceMetadata metadata) + { + if (ctx == null) + throw new ArgumentNullException(nameof(ctx)); + if (metadata == null) + throw new ArgumentNullException(nameof(metadata)); + + IEnumerable authenticationAttributes; + + if (!_cachedAuthAttributes.TryGetValue(metadata.Ident, out authenticationAttributes)) + { + var serviceAuthAttributes = new List(); + serviceAuthAttributes.AddRange(metadata.Service.GetCustomAttributes()); + serviceAuthAttributes.AddRange(metadata.Service.GetCustomAttributes()); + + serviceAuthAttributes.AddRange(metadata.Method.GetCustomAttributes()); + serviceAuthAttributes.AddRange(metadata.Method.GetCustomAttributes()); + + _cachedAuthAttributes.TryAdd(metadata.Ident, serviceAuthAttributes); + authenticationAttributes = serviceAuthAttributes; + } + + return IsAuthorizedAsync(ctx, authenticationAttributes); + } + + /// + public async Task IsAuthorizedAsync(HttpContext ctx, IEnumerable authorizationFilters) + { + if (ctx == null) + throw new ArgumentNullException(nameof(ctx)); + if (authorizationFilters == null) + throw new ArgumentNullException(nameof(authorizationFilters)); + + var authFilters = authorizationFilters.ToList(); + + // Allow Anonymous skips all authorization + if (authFilters.Any(x => x is IAllowAnonymous)) + return true; + + // Get the authorization policy + var authData = authFilters.Where(x => x is IAuthorizeData).Cast().ToList(); + var effectivePolicy = await AuthorizationPolicy.CombineAsync(_policyProvider, authData); + + if (effectivePolicy == null) + return true; + + // Build a ClaimsPrincipal with the policy's required authentication types + if (effectivePolicy.AuthenticationSchemes?.Any() ?? false) + { + ClaimsPrincipal newPrincipal = null; + foreach (var scheme in effectivePolicy.AuthenticationSchemes) + { + var result = await ctx.Authentication.AuthenticateAsync(scheme); + if (result != null) + { + newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result); + } + } + // If all schemes failed authentication, provide a default identity anyways + ctx.User = newPrincipal ?? new ClaimsPrincipal(new ClaimsIdentity()); + } + + var authService = ctx.RequestServices.GetRequiredService(); + + // Note: Default Anonymous User is new ClaimsPrincipal(new ClaimsIdentity()) + return await authService.AuthorizeAsync(ctx.User, effectivePolicy); + } + } } \ No newline at end of file diff --git a/src/RService.IO.Authorization/RService.IO.Authorization.xproj b/src/RService.IO.Authorization/RService.IO.Authorization.xproj new file mode 100644 index 0000000..7c01d12 --- /dev/null +++ b/src/RService.IO.Authorization/RService.IO.Authorization.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + {4FA0A7F5-B8AC-4BE0-BD20-7E2CDB16AE8B} + {8BB2217D-0F2D-49D1-97BC-3654ED321F3B} + RService.IO.Authorization + .\obj + .\bin\ + v4.5.1 + + + + 2.0 + + + diff --git a/src/RService.IO.Authorization/project.json b/src/RService.IO.Authorization/project.json new file mode 100644 index 0000000..b6e497d --- /dev/null +++ b/src/RService.IO.Authorization/project.json @@ -0,0 +1,37 @@ +{ + "title": "RService.IO.Authorization", + "version": "0.4.0", + + "packOptions": { + "summary": "RService.IO authorization provider.", + "releaseNotes": "Initial alpha release", + "owners": [ "James Stumme" ], + "tags": [ + "microservice", + "webservice", + "dotnet core", + "api", + "RESTful", + "RService.IO" + ], + "licenseUrl": "https://github.com/Stoom/RService.IO/blob/master/LICENSE", + "projectUrl": "https://rservice.io", + "requireLicenseAcceptance": false, + "repository": { + "type": "git", + "url": "https://github.com/Stoom/RService.IO" + } + }, + + "dependencies": { + "Microsoft.AspNetCore.Authorization": "1.1.0", + "Microsoft.Extensions.SecurityHelper.Sources": "1.0.0-rtm-21431", + "RService.IO.Abstractions": "0.4.*" + }, + + "frameworks": { + "netstandard1.6": { + "imports": ["dotnet5.6", "portable-net45+win8"] + } + } +} diff --git a/src/RService.IO/DependencyIngection/ServiceCollectionExtensions.cs b/src/RService.IO/DependencyIngection/ServiceCollectionExtensions.cs index fd0dcf4..f489cd1 100644 --- a/src/RService.IO/DependencyIngection/ServiceCollectionExtensions.cs +++ b/src/RService.IO/DependencyIngection/ServiceCollectionExtensions.cs @@ -3,8 +3,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; using RService.IO.Providers; -using IServiceProvider = RService.IO.Abstractions.IServiceProvider; +using IServiceProvider = RService.IO.Abstractions.Providers.IServiceProvider; namespace RService.IO.DependencyIngection { @@ -46,17 +47,6 @@ public static IServiceCollection AddRServiceIo( services.AddTransient(serviceType); } - - return services; - } - - public static IServiceCollection AddRServiceIoAuthorization( - this IServiceCollection services) - { - services.AddOptions(); - - services.TryAddTransient(); - return services; } } diff --git a/src/RService.IO/Providers/NetJsonProvider.cs b/src/RService.IO/Providers/NetJsonProvider.cs index 1919aa8..51518f6 100644 --- a/src/RService.IO/Providers/NetJsonProvider.cs +++ b/src/RService.IO/Providers/NetJsonProvider.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; using Delegate = RService.IO.Abstractions.Delegate; namespace RService.IO.Providers @@ -19,7 +20,8 @@ public class NetJsonProvider : ISerializationProvider { private static readonly Dictionary> CachedDtoProps = new Dictionary>(); private static readonly Dictionary CachedDtoCtors = new Dictionary(); - private static readonly Regex JsonNoQuotes = new Regex(@"(^[\d.]+)|(^[Tt][Rr][Uu][Ee])|(^[Ff][Aa][Ll][Ss][Ee])|(^[Nn][Uu][Ll]{2})", RegexOptions.Compiled); + private static readonly Regex JsonNoQuotes = new Regex(@"(^-?\d+[.\d]*)|(^true)|(^false)|(^null)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly MethodInfo JsonDeserializer = typeof(NetJSON.NetJSON).GetMethod("Deserialize", new[] { typeof(string) }); /// public string ContentType { get; } = HttpContentTypes.ApplicationJson; @@ -118,7 +120,7 @@ private static void AddRouteParams(HttpContext ctx, Dictionary - /// Default implementation of the + /// Default implementation of the /// public class RServiceProvider : IServiceProvider { diff --git a/src/RService.IO/RServiceMiddleware.cs b/src/RService.IO/RServiceMiddleware.cs index 05e65e3..fd59684 100644 --- a/src/RService.IO/RServiceMiddleware.cs +++ b/src/RService.IO/RServiceMiddleware.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using RService.IO.Abstractions; -using IServiceProvider = RService.IO.Abstractions.IServiceProvider; +using IServiceProvider = RService.IO.Abstractions.Providers.IServiceProvider; namespace RService.IO { @@ -19,19 +19,18 @@ public class RServiceMiddleware private readonly IServiceProvider _serviceProvider; private readonly RServiceOptions _options; - public RServiceMiddleware(RequestDelegate next, ILoggerFactory logFactory, RService service, IServiceProvider serviceProvider, IOptions options) + public RServiceMiddleware(RequestDelegate next, ILoggerFactory logFactory, RService service, + IServiceProvider serviceProvider, IOptions options) { _next = next; _logger = logFactory.CreateLogger(); _service = service; _serviceProvider = serviceProvider; - _options = options?.Value; + _options = options.Value; } public async Task Invoke(HttpContext context) { - var exceptionFilter = context.RequestServices.GetService(); - var routeData = (context.GetRouteData()?.Routers.Count >= 3) ? (context.GetRouteData()?.Routers[1] as Route) : null; @@ -77,7 +76,7 @@ public async Task Invoke(HttpContext context) await context.Response.WriteAsync(exc.Message); } - exceptionFilter?.OnException(context, exc); + context.RequestServices.GetService()?.OnException(context, exc); } } } diff --git a/src/RService.IO/Router/RServiceRouterMiddleware.cs b/src/RService.IO/Router/RServiceRouterMiddleware.cs index 34462a8..418a7d6 100644 --- a/src/RService.IO/Router/RServiceRouterMiddleware.cs +++ b/src/RService.IO/Router/RServiceRouterMiddleware.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; -using RService.IO.Abstractions; +using RoutingFeature = RService.IO.Abstractions.RoutingFeature; namespace RService.IO.Router { diff --git a/src/RService.IO/project.json b/src/RService.IO/project.json index 472ab47..59e01b1 100644 --- a/src/RService.IO/project.json +++ b/src/RService.IO/project.json @@ -1,6 +1,6 @@ { "title": "RService.IO", - "version": "0.3.0", + "version": "0.4.0", "packOptions": { "summary": "A web service framework for dotnet core loosely based on ServiceStack v3", "releaseNotes": "Initial alpha release", @@ -25,17 +25,16 @@ } }, "dependencies": { - "Microsoft.AspNetCore.Authorization": "1.0.0", - "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.0", - "Microsoft.AspNetCore.Http": "1.0.0", - "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", - "Microsoft.AspNetCore.Routing": "1.0.0", - "Microsoft.Extensions.DependencyInjection": "1.0.0", - "Microsoft.Extensions.SecurityHelper.Sources": "1.0.0-rtm-21431", + "Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "Microsoft.AspNetCore.Http.Abstractions": "1.1.0", + "Microsoft.AspNetCore.Routing": "1.1.0", + "Microsoft.Extensions.DependencyInjection": "1.1.0", "NetJSON": "1.2.1.4", - "RService.IO.Abstractions": "0.3.*", + "RService.IO.Abstractions": "0.4.*", + "RService.IO.Authorization": "0.4.*", "System.ComponentModel.Primitives": "4.1.0", - "System.Reflection.Emit.Lightweight": "4.0.1" + "System.Reflection.Emit.Lightweight": "4.3.0" }, "frameworks": { "netstandard1.6": { diff --git a/test/RService.IO.Tests/Providers/AuthProviderTests.cs b/test/RService.IO.Authorization.Tests/AuthProviderTests.cs similarity index 96% rename from test/RService.IO.Tests/Providers/AuthProviderTests.cs rename to test/RService.IO.Authorization.Tests/AuthProviderTests.cs index c4338ce..a4f4401 100644 --- a/test/RService.IO.Tests/Providers/AuthProviderTests.cs +++ b/test/RService.IO.Authorization.Tests/AuthProviderTests.cs @@ -1,550 +1,551 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using FluentAssertions; -using Microsoft.AspNetCore.Authorization; -using RService.IO.Abstractions; -using RService.IO.Providers; -using Xunit; - -namespace RService.IO.Tests.Providers -{ - public class AuthProviderTests - { - private readonly HttpContext _anonymousContext; - private readonly HttpContext _authorizedContext; - private readonly IAuthProvider _anonymousAuthProvider; - private readonly IAuthProvider _authorizedAuthProvider; - - public AuthProviderTests() - { - _anonymousContext = GetContext(s => s.AddTransient(), true); - _authorizedContext = GetContext(s => s.AddTransient()); - _anonymousAuthProvider = GetAuthProvider(_anonymousContext); - _authorizedAuthProvider = GetAuthProvider(_authorizedContext); - } - - [Fact] - public void InvalidUser() - { - var context = GetContext(service => service.AddAuthorization()); - context.User.Identities.Any(x => x.IsAuthenticated).Should().BeTrue(); - } - - [Fact] - public void Ctor__ThrowsExceptionIfNullProvider() - { - // ReSharper disable once ObjectCreationAsStatement - Action act = () => new AuthProvider(null); - - act.ShouldThrow().And.Message.Should().Contain("provider"); - } - - [Fact] - public void IsAuthorizedAsync_Filter__ThrowsExceptionIfNullContext() - { - Func act = async () => await _authorizedAuthProvider.IsAuthorizedAsync(null, (IEnumerable)null); - - act.ShouldThrow().And.Message.Should().Contain("ctx"); - } - - [Fact] - public void IsAuthorizedAsync_Filter__ThrowsExceptionIfNullFilters() - { - Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, (IEnumerable)null); - - act.ShouldThrow().And.Message.Should().Contain("authorizationFilter"); - } - - [Fact] - public void IsAuthorizedAsync_Metadata__ThrowsExceptionIfNullContext() - { - Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(null, (ServiceMetadata)null); - - act.ShouldThrow().And.Message.Should().Contain("ctx"); - } - - [Fact] - public void IsAuthorizedAsync_Metadata__ThrowsExceptionIfNullMetadata() - { - Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, (ServiceMetadata)null); - - act.ShouldThrow().And.Message.Should().Contain("metadata"); - } - - [Fact] - public async void IsAuthorizedAsync__AnonymousReturnsTrueForAnonymous() - { - var attributes = new[] { new AllowAnonymousAttribute() }; - - var results = await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync__AnonymousReturnsFalseForAuthorized() - { - var attributes = new[] { new AuthorizeAttribute() }; - - var results = await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync__AuthorizedRoleReturnsTrue() - { - var attributes = new[] { new AuthorizeAttribute { Roles = "Administrator" } }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync__UnauthorizedRoleReturnsFalse() - { - var authProvider = GetAuthProvider(_authorizedContext); - var attributes = new[] { new AuthorizeAttribute { Roles = "Poweruser" } }; - - var results = await authProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync__AuthoriedWithNoAttributes() - { - var attributes = new object[] { }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync__AuthorizedWithSpecifiedScheme() - { - var attributes = new[] - { - new AuthorizeAttribute - { - Roles = "Administrator", - ActiveAuthenticationSchemes = "Basic" - } - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync__NotAuthorizedWithSpecifiedMissingScheme() - { - var attributes = new[] - { - new AuthorizeAttribute - { - Roles = "Administrator", - ActiveAuthenticationSchemes = "FizzBuzzScheme" - } - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync__ProvideDefaultIdentityIfClaimsFails() - { - var attributes = new[] - { - new AuthorizeAttribute - { - Roles = "Administrator", - ActiveAuthenticationSchemes = "Basic" - } - }; - - _anonymousContext.User.Should().BeNull(); - await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); - _anonymousContext.User.Should().NotBeNull(); - } - - [Fact] - public async void IsAuthorizedAsync__AuthorizedWhenSpecifingMultipleRolesInSingleAttr() - { - var attributes = new[] { new AuthorizeAttribute { Roles = "Administrator, PowerUser" } }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync__AuthorizedRequireAllAttrToBeAuthorized() - { - var attributes = new[] - { - new AuthorizeAttribute { Roles = "Administrator" }, - new AuthorizeAttribute { Roles = "PowerUser" } - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesSingleSuccessfulMethod() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(ServiceMetadata).GetTypeInfo(), - Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "Single") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesSingleFailedMethod() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(ServiceMetadata).GetTypeInfo(), - Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "SingleFail") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesMultipleSuccessfulMethod() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(ServiceMetadata).GetTypeInfo(), - Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "Multiple") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesMultipleFailedMethod() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(ServiceMetadata).GetTypeInfo(), - Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "MultipleFail") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesSingleSuccessfulClass() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(SingleClassAuth).GetTypeInfo(), - Method = typeof(SingleClassAuth).GetPublicMethods().Single(x => x.Name == "Any") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesSingleFailedClass() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(SingleFailClassAuth).GetTypeInfo(), - Method = typeof(SingleFailClassAuth).GetPublicMethods().Single(x => x.Name == "Any") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesMultipleSuccessfulClass() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(MultipleClassAuth).GetTypeInfo(), - Method = typeof(MultipleClassAuth).GetPublicMethods().Single(x => x.Name == "Any") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesMultipleFailedClass() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(MultipleFailClassAuth).GetTypeInfo(), - Method = typeof(MultipleFailClassAuth).GetPublicMethods().Single(x => x.Name == "Any") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesSuccessfulHybrid() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(HybridAuth).GetTypeInfo(), - Method = typeof(HybridAuth).GetPublicMethods().Single(x => x.Name == "Any") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesMethodFailedHybrid() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(HybridAuth).GetTypeInfo(), - Method = typeof(HybridAuth).GetPublicMethods().Single(x => x.Name == "AnyFail") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesClassFailedHybrid() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(HybridFailAuth).GetTypeInfo(), - Method = typeof(HybridFailAuth).GetPublicMethods().Single(x => x.Name == "Fail") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeFalse(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesAnonymousMethodSuccessfulHybrid() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(HybridFailAuth).GetTypeInfo(), - Method = typeof(HybridFailAuth).GetPublicMethods().Single(x => x.Name == "Anonymous") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - [Fact] - public async void IsAuthorizedAsync_Metadata__HandlesAnonymousClassSuccessfulHybrid() - { - var metadata = new ServiceMetadata - { - Ident = Guid.NewGuid().ToString(), - Service = typeof(HybridAnonymousFailAuth).GetTypeInfo(), - Method = typeof(HybridAnonymousFailAuth).GetPublicMethods().Single(x => x.Name == "Anonymous") - }; - - var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); - results.Should().BeTrue(); - } - - private static HttpContext GetContext(Action registerServices, bool anonymous = false) - { - var basicPrincipal = new ClaimsPrincipal( - new ClaimsIdentity(new[] - { - new Claim("Permission", "CanViewPage"), - new Claim(ClaimTypes.Role, "Administrator"), - new Claim(ClaimTypes.Role, "User"), - new Claim(ClaimTypes.NameIdentifier, "John"), - }, - "Basic" - )); - - var validUser = basicPrincipal; - - var bearerIdentity = new ClaimsIdentity(new[] - { - new Claim("Permission", "CupBearer"), - new Claim(ClaimTypes.Role, "Token"), - new Claim(ClaimTypes.NameIdentifier, "Jon Bear") - }, - "Bearer" - ); - var bearerPrincipal = new ClaimsPrincipal(bearerIdentity); - - validUser.AddIdentity(bearerIdentity); - - var serviceCollection = new ServiceCollection(); - if (registerServices != null) - { - serviceCollection.AddOptions(); - serviceCollection.AddLogging(); - serviceCollection.AddAuthorization(); - - registerServices.Invoke(serviceCollection); - } - - var serviceProvider = serviceCollection.BuildServiceProvider(); - - var context = new Mock(); - var auth = new Mock(); - context.Setup(x => x.Authentication).Returns(auth.Object); - context.SetupProperty(x => x.User); - if (!anonymous) - context.Object.User = validUser; - context.SetupGet(x => x.RequestServices).Returns(serviceProvider); - auth.Setup(x => x.AuthenticateAsync("Bearer")).ReturnsAsync(bearerPrincipal); - auth.Setup(x => x.AuthenticateAsync("Basic")).ReturnsAsync(basicPrincipal); - auth.Setup(x => x.AuthenticateAsync("Fails")).ReturnsAsync(null); - - return context.Object; - } - - private static IAuthProvider GetAuthProvider(HttpContext ctx) - { - return ctx.RequestServices.GetRequiredService(); - } - - #region Test Classes - // ReSharper disable UnusedMember.Local - private class MethodAuth - { - [Authorize(Roles = "Administrator")] - public object Single() - { - return null; - } - [Authorize(Roles = "FakeRole")] - public object SingleFail() - { - return null; - } - - [Authorize(Roles = "Administrator")] - [Authorize(Roles = "User")] - public object Multiple() - { - return null; - } - [Authorize(Roles = "Administrator")] - [Authorize(Roles = "FakeRole")] - public object MultipleFail() - { - return null; - } - } - - [Authorize(Roles = "Administrator")] - private class SingleClassAuth - { - public object Any() - { - return null; - } - } - - [Authorize(Roles = "FakeRole")] - private class SingleFailClassAuth - { - public object Any() - { - return null; - } - } - - [Authorize(Roles = "Administrator")] - [Authorize(Roles = "User")] - private class MultipleClassAuth - { - public object Any() - { - return null; - } - } - - [Authorize(Roles = "Administrator")] - [Authorize(Roles = "FakeRole")] - private class MultipleFailClassAuth - { - public object Any() - { - return null; - } - } - - [Authorize(Roles = "Administrator")] - private class HybridAuth - { - [Authorize(Roles = "User")] - public object Any() - { - return null; - } - - [Authorize(Roles = "FakeRole")] - public object AnyFail() - { - return null; - } - } - - [Authorize(Roles = "FakeRole")] - private class HybridFailAuth - { - [AllowAnonymous] - public object Anonymous() - { - return null; - } - - [Authorize(Roles = "Administrator")] - public object Fail() - { - return null; - } - } - - [AllowAnonymous] - private class HybridAnonymousFailAuth - { - [Authorize(Roles = "FakeRole")] - public object Anonymous() - { - return null; - } - } - // ReSharper restore UnusedMember.Local - #endregion - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Claims; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; +using RService.IO.Authorization.Providers; +using Xunit; + +namespace RService.IO.Authorization.Tests +{ + public class AuthProviderTests + { + private readonly HttpContext _anonymousContext; + private readonly HttpContext _authorizedContext; + private readonly IAuthProvider _anonymousAuthProvider; + private readonly IAuthProvider _authorizedAuthProvider; + + public AuthProviderTests() + { + _anonymousContext = GetContext(s => s.AddTransient(), true); + _authorizedContext = GetContext(s => s.AddTransient()); + _anonymousAuthProvider = GetAuthProvider(_anonymousContext); + _authorizedAuthProvider = GetAuthProvider(_authorizedContext); + } + + [Fact] + public void InvalidUser() + { + var context = GetContext(service => service.AddAuthorization()); + context.User.Identities.Any(x => x.IsAuthenticated).Should().BeTrue(); + } + + [Fact] + public void Ctor__ThrowsExceptionIfNullProvider() + { + // ReSharper disable once ObjectCreationAsStatement + Action act = () => new AuthProvider(null); + + act.ShouldThrow().And.Message.Should().Contain("provider"); + } + + [Fact] + public void IsAuthorizedAsync_Filter__ThrowsExceptionIfNullContext() + { + Func act = async () => await _authorizedAuthProvider.IsAuthorizedAsync(null, (IEnumerable)null); + + act.ShouldThrow().And.Message.Should().Contain("ctx"); + } + + [Fact] + public void IsAuthorizedAsync_Filter__ThrowsExceptionIfNullFilters() + { + Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, (IEnumerable)null); + + act.ShouldThrow().And.Message.Should().Contain("authorizationFilter"); + } + + [Fact] + public void IsAuthorizedAsync_Metadata__ThrowsExceptionIfNullContext() + { + Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(null, (ServiceMetadata)null); + + act.ShouldThrow().And.Message.Should().Contain("ctx"); + } + + [Fact] + public void IsAuthorizedAsync_Metadata__ThrowsExceptionIfNullMetadata() + { + Func act = async () => await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, (ServiceMetadata)null); + + act.ShouldThrow().And.Message.Should().Contain("metadata"); + } + + [Fact] + public async void IsAuthorizedAsync__AnonymousReturnsTrueForAnonymous() + { + var attributes = new[] { new AllowAnonymousAttribute() }; + + var results = await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync__AnonymousReturnsFalseForAuthorized() + { + var attributes = new[] { new AuthorizeAttribute() }; + + var results = await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync__AuthorizedRoleReturnsTrue() + { + var attributes = new[] { new AuthorizeAttribute { Roles = "Administrator" } }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync__UnauthorizedRoleReturnsFalse() + { + var authProvider = GetAuthProvider(_authorizedContext); + var attributes = new[] { new AuthorizeAttribute { Roles = "Poweruser" } }; + + var results = await authProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync__AuthoriedWithNoAttributes() + { + var attributes = new object[] { }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync__AuthorizedWithSpecifiedScheme() + { + var attributes = new[] + { + new AuthorizeAttribute + { + Roles = "Administrator", + ActiveAuthenticationSchemes = "Basic" + } + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync__NotAuthorizedWithSpecifiedMissingScheme() + { + var attributes = new[] + { + new AuthorizeAttribute + { + Roles = "Administrator", + ActiveAuthenticationSchemes = "FizzBuzzScheme" + } + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync__ProvideDefaultIdentityIfClaimsFails() + { + var attributes = new[] + { + new AuthorizeAttribute + { + Roles = "Administrator", + ActiveAuthenticationSchemes = "Basic" + } + }; + + _anonymousContext.User.Should().BeNull(); + await _anonymousAuthProvider.IsAuthorizedAsync(_anonymousContext, attributes); + _anonymousContext.User.Should().NotBeNull(); + } + + [Fact] + public async void IsAuthorizedAsync__AuthorizedWhenSpecifingMultipleRolesInSingleAttr() + { + var attributes = new[] { new AuthorizeAttribute { Roles = "Administrator, PowerUser" } }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync__AuthorizedRequireAllAttrToBeAuthorized() + { + var attributes = new[] + { + new AuthorizeAttribute { Roles = "Administrator" }, + new AuthorizeAttribute { Roles = "PowerUser" } + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, attributes); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesSingleSuccessfulMethod() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(ServiceMetadata).GetTypeInfo(), + Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "Single") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesSingleFailedMethod() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(ServiceMetadata).GetTypeInfo(), + Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "SingleFail") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesMultipleSuccessfulMethod() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(ServiceMetadata).GetTypeInfo(), + Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "Multiple") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesMultipleFailedMethod() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(ServiceMetadata).GetTypeInfo(), + Method = typeof(MethodAuth).GetPublicMethods().Single(x => x.Name == "MultipleFail") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesSingleSuccessfulClass() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(SingleClassAuth).GetTypeInfo(), + Method = typeof(SingleClassAuth).GetPublicMethods().Single(x => x.Name == "Any") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesSingleFailedClass() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(SingleFailClassAuth).GetTypeInfo(), + Method = typeof(SingleFailClassAuth).GetPublicMethods().Single(x => x.Name == "Any") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesMultipleSuccessfulClass() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(MultipleClassAuth).GetTypeInfo(), + Method = typeof(MultipleClassAuth).GetPublicMethods().Single(x => x.Name == "Any") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesMultipleFailedClass() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(MultipleFailClassAuth).GetTypeInfo(), + Method = typeof(MultipleFailClassAuth).GetPublicMethods().Single(x => x.Name == "Any") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesSuccessfulHybrid() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(HybridAuth).GetTypeInfo(), + Method = typeof(HybridAuth).GetPublicMethods().Single(x => x.Name == "Any") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesMethodFailedHybrid() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(HybridAuth).GetTypeInfo(), + Method = typeof(HybridAuth).GetPublicMethods().Single(x => x.Name == "AnyFail") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesClassFailedHybrid() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(HybridFailAuth).GetTypeInfo(), + Method = typeof(HybridFailAuth).GetPublicMethods().Single(x => x.Name == "Fail") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeFalse(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesAnonymousMethodSuccessfulHybrid() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(HybridFailAuth).GetTypeInfo(), + Method = typeof(HybridFailAuth).GetPublicMethods().Single(x => x.Name == "Anonymous") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + [Fact] + public async void IsAuthorizedAsync_Metadata__HandlesAnonymousClassSuccessfulHybrid() + { + var metadata = new ServiceMetadata + { + Ident = Guid.NewGuid().ToString(), + Service = typeof(HybridAnonymousFailAuth).GetTypeInfo(), + Method = typeof(HybridAnonymousFailAuth).GetPublicMethods().Single(x => x.Name == "Anonymous") + }; + + var results = await _authorizedAuthProvider.IsAuthorizedAsync(_authorizedContext, metadata); + results.Should().BeTrue(); + } + + private static HttpContext GetContext(Action registerServices, bool anonymous = false) + { + var basicPrincipal = new ClaimsPrincipal( + new ClaimsIdentity(new[] + { + new Claim("Permission", "CanViewPage"), + new Claim(ClaimTypes.Role, "Administrator"), + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.NameIdentifier, "John"), + }, + "Basic" + )); + + var validUser = basicPrincipal; + + var bearerIdentity = new ClaimsIdentity(new[] + { + new Claim("Permission", "CupBearer"), + new Claim(ClaimTypes.Role, "Token"), + new Claim(ClaimTypes.NameIdentifier, "Jon Bear") + }, + "Bearer" + ); + var bearerPrincipal = new ClaimsPrincipal(bearerIdentity); + + validUser.AddIdentity(bearerIdentity); + + var serviceCollection = new ServiceCollection(); + if (registerServices != null) + { + serviceCollection.AddOptions(); + serviceCollection.AddLogging(); + serviceCollection.AddAuthorization(); + + registerServices.Invoke(serviceCollection); + } + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var context = new Mock(); + var auth = new Mock(); + context.Setup(x => x.Authentication).Returns(auth.Object); + context.SetupProperty(x => x.User); + if (!anonymous) + context.Object.User = validUser; + context.SetupGet(x => x.RequestServices).Returns(serviceProvider); + auth.Setup(x => x.AuthenticateAsync("Bearer")).ReturnsAsync(bearerPrincipal); + auth.Setup(x => x.AuthenticateAsync("Basic")).ReturnsAsync(basicPrincipal); + auth.Setup(x => x.AuthenticateAsync("Fails")).ReturnsAsync(null); + + return context.Object; + } + + private static IAuthProvider GetAuthProvider(HttpContext ctx) + { + return ctx.RequestServices.GetRequiredService(); + } + + #region Test Classes + // ReSharper disable UnusedMember.Local + private class MethodAuth + { + [Authorize(Roles = "Administrator")] + public object Single() + { + return null; + } + [Authorize(Roles = "FakeRole")] + public object SingleFail() + { + return null; + } + + [Authorize(Roles = "Administrator")] + [Authorize(Roles = "User")] + public object Multiple() + { + return null; + } + [Authorize(Roles = "Administrator")] + [Authorize(Roles = "FakeRole")] + public object MultipleFail() + { + return null; + } + } + + [Authorize(Roles = "Administrator")] + private class SingleClassAuth + { + public object Any() + { + return null; + } + } + + [Authorize(Roles = "FakeRole")] + private class SingleFailClassAuth + { + public object Any() + { + return null; + } + } + + [Authorize(Roles = "Administrator")] + [Authorize(Roles = "User")] + private class MultipleClassAuth + { + public object Any() + { + return null; + } + } + + [Authorize(Roles = "Administrator")] + [Authorize(Roles = "FakeRole")] + private class MultipleFailClassAuth + { + public object Any() + { + return null; + } + } + + [Authorize(Roles = "Administrator")] + private class HybridAuth + { + [Authorize(Roles = "User")] + public object Any() + { + return null; + } + + [Authorize(Roles = "FakeRole")] + public object AnyFail() + { + return null; + } + } + + [Authorize(Roles = "FakeRole")] + private class HybridFailAuth + { + [AllowAnonymous] + public object Anonymous() + { + return null; + } + + [Authorize(Roles = "Administrator")] + public object Fail() + { + return null; + } + } + + [AllowAnonymous] + private class HybridAnonymousFailAuth + { + [Authorize(Roles = "FakeRole")] + public object Anonymous() + { + return null; + } + } + // ReSharper restore UnusedMember.Local + #endregion + } } \ No newline at end of file diff --git a/test/RService.IO.Authorization.Tests/Properties/AssemblyInfo.cs b/test/RService.IO.Authorization.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f29ae72 --- /dev/null +++ b/test/RService.IO.Authorization.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("RService.IO.Authorization.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RService.IO.Authorization.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("44B66719-E3DC-4F39-A67D-196BCB9A0FA7")] \ No newline at end of file diff --git a/test/RService.IO.Authorization.Tests/RService.IO.Authorization.Tests.xproj b/test/RService.IO.Authorization.Tests/RService.IO.Authorization.Tests.xproj new file mode 100644 index 0000000..a7115f7 --- /dev/null +++ b/test/RService.IO.Authorization.Tests/RService.IO.Authorization.Tests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + {44B66719-E3DC-4F39-A67D-196BCB9A0FA7} + {8BB2217D-0F2D-49D1-97BC-3654ED321F3B} + RService.IO.Authorization.Tests + .\obj + .\bin\ + v4.5.1 + + + + 2.0 + + + diff --git a/test/RService.IO.Authorization.Tests/RServiceIoCollectionExtensionTests.cs b/test/RService.IO.Authorization.Tests/RServiceIoCollectionExtensionTests.cs new file mode 100644 index 0000000..4e448fe --- /dev/null +++ b/test/RService.IO.Authorization.Tests/RServiceIoCollectionExtensionTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; +using RService.IO.Authorization.DependencyInjection; +using RService.IO.Authorization.Providers; +using Xunit; + +namespace RService.IO.Authorization.Tests +{ + public class RServiceIoCollectionExtensionTests + { + [Fact] + public void AddRServiceIoAuthorization__AddsRServiceProviderForIAuthProvider() + { + var services = new ServiceCollection(); + + services.AddAuthorization() + .AddRServiceIoAuthorization(); + + var app = BuildApplicationBuilder(services); + var provider = app.ApplicationServices.GetService(); + + provider.Should().NotBeNull().And.BeOfType(); + } + + [Fact] + public void AddRServiceIoAuthorization__UserImplementationForIAuthProviderTakesPrecedence() + { + var services = new ServiceCollection(); + + services.AddTransient() + .AddAuthorization() + .AddRServiceIoAuthorization(); + + var app = BuildApplicationBuilder(services); + var provider = app.ApplicationServices.GetService(); + + provider.Should().NotBeNull().And.BeOfType(); + } + + private static IApplicationBuilder BuildApplicationBuilder(IServiceCollection services) + { + var builder = new Mock(); + builder.SetupAllProperties(); + builder.Object.ApplicationServices = services.BuildServiceProvider(); + + return builder.Object; + } + + // ReSharper disable ClassNeverInstantiated.Local + private class AuthorizationProvider : IAuthProvider + { + public Task IsAuthorizedAsync(HttpContext ctx, ServiceMetadata metadata) + { + throw new NotImplementedException(); + } + + public Task IsAuthorizedAsync(HttpContext ctx, IEnumerable authorizationFilters) + { + throw new NotImplementedException(); + } + } + // ReSharper restore ClassNeverInstantiated.Local + } +} \ No newline at end of file diff --git a/test/RService.IO.Authorization.Tests/project.json b/test/RService.IO.Authorization.Tests/project.json new file mode 100644 index 0000000..03da633 --- /dev/null +++ b/test/RService.IO.Authorization.Tests/project.json @@ -0,0 +1,35 @@ +{ + "version": "1.0.0", + + "testRunner": "xunit", + + "dependencies": { + "coveralls.io": "1.3.4", + "dotnet-test-xunit": "2.2.0-*", + "OpenCover": "4.6.519", + "RService.IO": "0.4.*", + "RService.IO.Authorization": "0.4.*", + "Microsoft.Extensions.Logging": "1.1.0-*", + "Microsoft.Extensions.Logging.Testing": "1.1.0-*", + "xunit": "2.2.0-*" + }, + + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dotnet5.6", + "portable-net45+win8" + ], + + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.1.0-*", + "type": "platform" + }, + "moq.netcore": "4.4.0-*", + "System.Diagnostics.TraceSource": "4.0.0-*", + "FluentAssertions": "4.13.0" + } + } + } +} diff --git a/test/RService.IO.Tests/Abstractions/RoutingHttpContextExtensionsTests.cs b/test/RService.IO.Tests/Abstractions/RoutingHttpContextExtensionsTests.cs index 422fb50..ac71bd7 100644 --- a/test/RService.IO.Tests/Abstractions/RoutingHttpContextExtensionsTests.cs +++ b/test/RService.IO.Tests/Abstractions/RoutingHttpContextExtensionsTests.cs @@ -7,6 +7,7 @@ using RService.IO.Abstractions; using Xunit; using RoutingHttpContextExtensions = RService.IO.Abstractions.RoutingHttpContextExtensions; +using RoutingFeature = RService.IO.Abstractions.RoutingFeature; namespace RService.IO.Tests.Abstractions { diff --git a/test/RService.IO.Tests/ApiExceptionTests.cs b/test/RService.IO.Tests/ApiExceptionTests.cs index be2ea53..2c4a611 100644 --- a/test/RService.IO.Tests/ApiExceptionTests.cs +++ b/test/RService.IO.Tests/ApiExceptionTests.cs @@ -1,6 +1,5 @@ using System; using System.Net; -using System.Security.Cryptography.X509Certificates; using FluentAssertions; using RService.IO.Abstractions; using Xunit; diff --git a/test/RService.IO.Tests/DependencyIngection/ServiceCollectionExtensionTests.cs b/test/RService.IO.Tests/DependencyIngection/ServiceCollectionExtensionTests.cs index 8538b7a..e08c6d1 100644 --- a/test/RService.IO.Tests/DependencyIngection/ServiceCollectionExtensionTests.cs +++ b/test/RService.IO.Tests/DependencyIngection/ServiceCollectionExtensionTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Reflection; using FluentAssertions; using Microsoft.AspNetCore.Builder; @@ -11,10 +10,11 @@ using RService.IO.Abstractions; using RService.IO.DependencyIngection; using RService.IO.Providers; -using IServiceProvider = RService.IO.Abstractions.IServiceProvider; +using IServiceProvider = RService.IO.Abstractions.Providers.IServiceProvider; using Xunit; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; +using RService.IO.Abstractions.Providers; namespace RService.IO.Tests.DependencyIngection { @@ -220,35 +220,6 @@ public void AddRServiceIo__DoesNotRegistersExceptionFilterWithIoCIfNull() globalExceptionFilter.Should().BeNull(); } - [Fact] - public void AddRServiceIoAuthorization__AddsRServiceProviderForIAuthProvider() - { - var services = new ServiceCollection(); - - services.AddAuthorization() - .AddRServiceIoAuthorization(); - - var app = BuildApplicationBuilder(services); - var provider = app.ApplicationServices.GetService(); - - provider.Should().NotBeNull().And.BeOfType(); - } - - [Fact] - public void AddRServiceIoAuthorization__UserImplementationForIAuthProviderTakesPrecedence() - { - var services = new ServiceCollection(); - - services.AddTransient() - .AddAuthorization() - .AddRServiceIoAuthorization(); - - var app = BuildApplicationBuilder(services); - var provider = app.ApplicationServices.GetService(); - - provider.Should().NotBeNull().And.BeOfType(); - } - private static IApplicationBuilder BuildApplicationBuilder(IServiceCollection services) { var builder = new Mock(); @@ -280,19 +251,6 @@ public string DehydrateResponse(object resDto) throw new NotImplementedException(); } } - - private class AuthorizationProvider : IAuthProvider - { - public Task IsAuthorizedAsync(HttpContext ctx, ServiceMetadata metadata) - { - throw new NotImplementedException(); - } - - public Task IsAuthorizedAsync(HttpContext ctx, IEnumerable authorizationFilters) - { - throw new NotImplementedException(); - } - } // ReSharper restore ClassNeverInstantiated.Local } } \ No newline at end of file diff --git a/test/RService.IO.Tests/Providers/NetJsonProviderTests.cs b/test/RService.IO.Tests/Providers/NetJsonProviderTests.cs index c30d7d4..08018f3 100644 --- a/test/RService.IO.Tests/Providers/NetJsonProviderTests.cs +++ b/test/RService.IO.Tests/Providers/NetJsonProviderTests.cs @@ -8,10 +8,11 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using Moq; -using RService.IO.Abstractions; using RService.IO.Providers; using Xunit; using FluentAssertions; +using RService.IO.Abstractions.Providers; +using RoutingFeature = RService.IO.Abstractions.RoutingFeature; namespace RService.IO.Tests.Providers { diff --git a/test/RService.IO.Tests/Providers/RServiceProviderTests.cs b/test/RService.IO.Tests/Providers/RServiceProviderTests.cs index 01766d7..3a46d47 100644 --- a/test/RService.IO.Tests/Providers/RServiceProviderTests.cs +++ b/test/RService.IO.Tests/Providers/RServiceProviderTests.cs @@ -11,8 +11,10 @@ using Microsoft.Extensions.Options; using Moq; using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; using RService.IO.Providers; using Xunit; +using RoutingFeature = RService.IO.Abstractions.RoutingFeature; namespace RService.IO.Tests.Providers { diff --git a/test/RService.IO.Tests/RServiceMiddlewareTests.cs b/test/RService.IO.Tests/RServiceMiddlewareTests.cs index cc0f130..dce8f33 100644 --- a/test/RService.IO.Tests/RServiceMiddlewareTests.cs +++ b/test/RService.IO.Tests/RServiceMiddlewareTests.cs @@ -14,8 +14,9 @@ using RService.IO.Abstractions; using Xunit; using Delegate = RService.IO.Abstractions.Delegate; -using IServiceProvider = RService.IO.Abstractions.IServiceProvider; +using IServiceProvider = RService.IO.Abstractions.Providers.IServiceProvider; using IRoutingFeature = Microsoft.AspNetCore.Routing.IRoutingFeature; +using RoutingFeature = RService.IO.Abstractions.RoutingFeature; namespace RService.IO.Tests { @@ -139,7 +140,7 @@ public async void Invoke__LogsWhenFeatureNotAdded() } [Fact] - public async void Invoke_CallsProviderIfActivatorFound() + public async void Invoke__CallsProviderIfActivatorFound() { var routePath = "/Foobar".Substring(1); Delegate.Activator routeActivator = (target, args) => null; diff --git a/test/RService.IO.Tests/project.json b/test/RService.IO.Tests/project.json index 2a75c15..7adc79c 100644 --- a/test/RService.IO.Tests/project.json +++ b/test/RService.IO.Tests/project.json @@ -6,13 +6,13 @@ "dependencies": { "coveralls.io": "1.3.4", "dotnet-test-xunit": "2.2.0-*", - "Microsoft.AspNetCore.Http": "1.0.0-*", - "Microsoft.Extensions.DependencyInjection": "1.0.0-*", - "Microsoft.Extensions.Logging": "1.0.0-*", - "Microsoft.Extensions.Logging.Testing": "1.0.0-*", + "Microsoft.AspNetCore.Http": "1.1.0-*", + "Microsoft.Extensions.DependencyInjection": "1.1.0-*", + "Microsoft.Extensions.Logging": "1.1.0-*", + "Microsoft.Extensions.Logging.Testing": "1.1.0-*", "OpenCover": "4.6.519", - "RService.IO": "0.3.*", - "RService.IO.Abstractions": "0.3.*", + "RService.IO": "0.4.*", + "RService.IO.Abstractions": "0.4.*", "xunit": "2.2.0-*" }, @@ -25,7 +25,7 @@ "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0-*", + "version": "1.1.0-*", "type": "platform" }, "moq.netcore": "4.4.0-*", diff --git a/test/Rservice.IO.Tests.Integration/MiddlewareTests.cs b/test/Rservice.IO.Tests.Integration/MiddlewareTests.cs index da3b82b..b6b3f18 100644 --- a/test/Rservice.IO.Tests.Integration/MiddlewareTests.cs +++ b/test/Rservice.IO.Tests.Integration/MiddlewareTests.cs @@ -13,7 +13,8 @@ using Microsoft.Extensions.Logging; using Moq; using RService.IO; -using RService.IO.Abstractions; +using RService.IO.Abstractions.Providers; +using RService.IO.Authorization.DependencyInjection; using RService.IO.Tests; using Xunit; using RService.IO.DependencyIngection; diff --git a/test/Rservice.IO.Tests.Integration/project.json b/test/Rservice.IO.Tests.Integration/project.json index 984c816..6f1448b 100644 --- a/test/Rservice.IO.Tests.Integration/project.json +++ b/test/Rservice.IO.Tests.Integration/project.json @@ -6,9 +6,10 @@ "dependencies": { "xunit": "2.2.0-*", "dotnet-test-xunit": "2.2.0-*", - "RService.IO": "0.3.*", + "RService.IO": "0.4.*", + "RService.IO.Authorization": "0.4.*", "RService.IO.Tests": "1.0.0-*", - "Microsoft.AspNetCore.TestHost": "1.0.0", + "Microsoft.AspNetCore.TestHost": "1.1.0", "FluentAssertions": "4.13.0" }, @@ -21,7 +22,7 @@ "dependencies": { "Microsoft.NETCore.App": { - "version": "1.0.0-*", + "version": "1.1.0-*", "type": "platform" } }