From c8ca2a21e68549e8f83eb4003f1228f385f6f3ce Mon Sep 17 00:00:00 2001 From: "Simon S. Pedersen" Date: Thu, 16 May 2024 14:57:12 +0200 Subject: [PATCH] Added functionality --- .github/dependabot.yml | 11 +++ .github/workflows/.releaserc | 22 ++++++ .github/workflows/build.yml | 33 ++++++++ .github/workflows/release.yml | 61 +++++++++++++++ Directory.Build.props | 10 +++ ...xtensions.EasyAuth.MicrosoftEntraId.csproj | 8 +- .../MicrosoftEntraEasyAuthProvider.cs | 75 +++++++++++++------ .../MicrosoftEntraIdEasyAuthExtensions.cs | 15 +++- .../MicrosoftEntraIdEasyAuthOptions.cs | 18 ++++- 9 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/.releaserc create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 Directory.Build.props diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..654b3d1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# Basic dependabot.yml file with + +version: 2 +updates: + - package-ecosystem: nuget + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 2 + allow: + - dependency-name: "EAVFramework" \ No newline at end of file diff --git a/.github/workflows/.releaserc b/.github/workflows/.releaserc new file mode 100644 index 0000000..43302f1 --- /dev/null +++ b/.github/workflows/.releaserc @@ -0,0 +1,22 @@ +branches: + - name: main + - name: dev + prerelease: dev + +plugins: + - - "@semantic-release/commit-analyzer" + - preset: conventionalcommits + + - - "@semantic-release/release-notes-generator" + - preset: conventionalcommits + + - - "@semantic-release/github" + - assets: + - path: ../../artifacts/*.nupkg + label: Parser DLL + + - - "@semantic-release/exec" + - publishCmd: "dotnet nuget push ..\\..\\artifacts\\*.nupkg --source https://nuget.pkg.github.com/eavfw/index.json --api-key ${process.env.GITHUB_TOKEN}" + + - - "@semantic-release/exec" + - publishCmd: "dotnet nuget push ..\\..\\artifacts\\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${process.env.CI_NUGET_API_KEY}" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1c618ce --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ + +on: + pull_request: + types: [ assigned, opened, synchronize, reopened ] + push: + branches: + - 'feature/**' + +jobs: + build: + runs-on: windows-latest + name: Building + steps: + - name: Checkout code base + uses: actions/checkout@v2 + + - uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0.x' + + - name: Cleaning + run: dotnet clean + + - name: Build solution + run: dotnet build -c Release + + #- name: Archive build to artifacts + # uses: actions/upload-artifact@v2 + # with: + # name: build + # path: | + # build/* + # retention-days: 5 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9d5c4ea --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + branches: + - dev + - main + +jobs: + release: + name: Releasing + runs-on: windows-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0.x' + + - uses: actions/setup-node@v2 + with: + node-version: '20' + + - name: Add plugin for conventional commits + run: npm install conventional-changelog-conventionalcommits@7.0.2 + working-directory: ./.github/workflows + + - name: Add plugin for executing bash commands + run: npm install @semantic-release/exec -D + working-directory: ./.github/workflows + + - name: Dry Run Semantic to get next Version nummber + working-directory: ./.github/workflows + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: pksorensen + GIT_AUTHOR_EMAIL: poul@kjeldager.com + run: | + echo "RELEASE_VERSION=$((npx semantic-release --dry-run).Where({ $_ -like '*Release note*' }) | Out-String | Select-String '[0-9]+\.[0-9]+\.[0-9]+([-][a-zA-z]+[.][0-9]*)?' | % { $_.Matches } | % { $_.Value })" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Print release verison + run: echo ${env:RELEASE_VERSION} + + - name: Cleaning + run: dotnet clean + + - name: Restore NuGet packages + run: dotnet restore + + - name: Package Parser + run: dotnet pack -c Release -p:PackageVersion=${env:RELEASE_VERSION} -o ./artifacts + if: ${{ env.RELEASE_VERSION }} + + - name: Release to GitHub and NuGet + working-directory: .\\.github\\workflows + env: + CI_NUGET_API_KEY: ${{ secrets.CI_NUGET_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: pksorensen + GIT_AUTHOR_EMAIL: poul@kjeldager.com + run: npx semantic-release \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..fd4b3e2 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + 10.0 + 4.2.8 + true + $(MSBuildThisFileDirectory)/external/EAVFramework + $(MSBuildThisFileDirectory)/external + + \ No newline at end of file diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/EAVFW.Extensions.EasyAuth.MicrosoftEntraId.csproj b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/EAVFW.Extensions.EasyAuth.MicrosoftEntraId.csproj index 80d65e7..491b88b 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/EAVFW.Extensions.EasyAuth.MicrosoftEntraId.csproj +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/EAVFW.Extensions.EasyAuth.MicrosoftEntraId.csproj @@ -18,9 +18,13 @@ - + + + + + + - diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs index 8f74ff6..6177054 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraEasyAuthProvider.cs @@ -1,12 +1,26 @@ -using EAVFramework.Authentication; +using EAVFramework.Authentication; +using EAVFramework.Extensions; +using IdentityModel; using IdentityModel.Client; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Sockets; using System.Security.Claims; +using System.Text; using System.Threading.Tasks; +using System.Web; using static IdentityModel.OidcConstants; +using static System.Net.WebRequestMethods; namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId { @@ -14,6 +28,7 @@ namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId public class MicrosoftEntraEasyAuthProvider : IEasyAuthProvider { private readonly IOptions _options; + private readonly IHttpClientFactory _clientFactory; public string AuthenticationName => "MicrosoftEntraId"; @@ -23,38 +38,56 @@ public class MicrosoftEntraEasyAuthProvider : IEasyAuthProvider public MicrosoftEntraEasyAuthProvider() { } - public MicrosoftEntraEasyAuthProvider(IOptions options) + public MicrosoftEntraEasyAuthProvider(IOptions options, IHttpClientFactory clientFactory) { _options = options ?? throw new System.ArgumentNullException(nameof(options)); + _clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); } public async Task OnAuthenticate(HttpContext httpcontext, string handleId, string redirectUrl) { var email = httpcontext.Request.Query["email"].FirstOrDefault(); var redirectUri = httpcontext.Request.Query["redirectUri"].FirstOrDefault(); - - // var url =$"{oauthEndpoint}" + var ru = new RequestUrl(_options.Value.AuthorizationUrl); - - //var authUri = ru.CreateAuthorizeUrl(_options.Value.ClientI - // responseType: ResponseTypes.Code, - // redirectUri: _options.Value.RedirectUri, - // responseMode: ResponseModes.FormPost , - - // // extra: new Parameters { { "consentId", provider.ExternalId } }, - // // codeChallenge: challenge, - // // nonce: nonce, - // // responseMode: ResponseModes.FormPost, - // //scope: "payments:inbound payments:outbound accounts offline_access", - - // state: handleId); - - + var authUri = ru.CreateAuthorizeUrl( + clientId: _options.Value.ClientId, + redirectUri: _options.Value.RedirectUrl, + responseType: ResponseTypes.Code, + responseMode: ResponseModes.FormPost, + scope: _options.Value.Scope, + state: handleId + "&" + redirectUri); + httpcontext.Response.Redirect(authUri); } - public Task<(ClaimsPrincipal, string, string)> OnCallback(HttpContext httpcontext) + public async Task<(ClaimsPrincipal, string, string)> OnCallback(HttpContext httpcontext) { - throw new System.NotImplementedException(); + var m = new IdentityModel.Client.AuthorizeResponse(await new StreamReader(httpcontext.Request.Body).ReadToEndAsync()); + var state = m.State.Split(new char[] { '&' }, 2); + var handleId = state[0]; + var redirectUri = state[1]; + var http = _clientFactory.CreateClient(); + var response = await http.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest + { + Address = _options.Value.TokenEndpoint, + ClientId = _options.Value.ClientId, + ClientSecret = _options.Value.ClientSecret, + Code = m.Code, + RedirectUri = _options.Value.RedirectUrl, + }); + + var handler = new JwtSecurityTokenHandler(); + var jwtSecurityToken = handler.ReadJwtToken(response.IdentityToken); + var jti = jwtSecurityToken.Claims.First(claim => claim.Type == "email").Value; + + ClaimsPrincipal identity = await _options.Value.ValidateUserAsync(httpcontext, handleId, response); + + if (identity == null) + { + httpcontext.Response.Redirect("error=access_denied&error_subcode=user_not_found"); + //return; + } + return await Task.FromResult((new ClaimsPrincipal(identity), redirectUri, handleId)); } public RequestDelegate OnSignedOut() diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs index 008387d..466371e 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthExtensions.cs @@ -1,16 +1,23 @@ -using EAVFramework.Configuration; +using EAVFramework.Configuration; +using IdentityModel.Client; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System; +using System.Security.Claims; +using System.Threading.Tasks; namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId { public static class MicrosoftEntraIdEasyAuthExtensions { - public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth(this AuthenticatedEAVFrameworkBuilder builder) + public static AuthenticatedEAVFrameworkBuilder AddMicrosoftEntraIdEasyAuth(this AuthenticatedEAVFrameworkBuilder builder, Func> validateUserAsync) { - builder.AddAuthenticationProvider((options, config) => - { + { + config.GetSection("EAVEasyAuth:MicrosoftEntraId").Bind(options); + options.ValidateUserAsync = validateUserAsync; }); return builder; diff --git a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs index 0f514f5..2b7100a 100644 --- a/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs +++ b/src/EAVFW.Extensions.EasyAuth.MicrosoftEntraId/MicrosoftEntraIdEasyAuthOptions.cs @@ -1,9 +1,23 @@ -namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId +using IdentityModel.Client; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace EAVFW.Extensions.EasyAuth.MicrosoftEntraId { public class MicrosoftEntraIdEasyAuthOptions { public string AuthorizationUrl { get; set; } public string ClientId { get; set; } - public string RedirectUri { get; set; } + public string ClientSecret { get; set; } + public string TenantId { get; set; } + public string GroupId { get; set; } + public string Scope { get; set; } + public string TokenEndpoint { get; set; } + public string RedirectUrl { get; set; } + + public Func> ValidateUserAsync { get; set; } } } \ No newline at end of file