diff --git a/CommitHookTest/CommitHookTest.slnx b/CommitHookTest/CommitHookTest.slnx
new file mode 100644
index 0000000..14f1b0c
--- /dev/null
+++ b/CommitHookTest/CommitHookTest.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/CommitHookTest/OpenID-API/Controllers/WeatherForecastController.cs b/CommitHookTest/OpenID-API/Controllers/WeatherForecastController.cs
new file mode 100644
index 0000000..25b234b
--- /dev/null
+++ b/CommitHookTest/OpenID-API/Controllers/WeatherForecastController.cs
@@ -0,0 +1,37 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace OpenID_API.Controllers
+{
+ [ApiController]
+ [Route("[controller]")]
+ public class WeatherForecastController : ControllerBase
+ {
+ private static readonly string[] Summaries =
+ [
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+ ];
+
+ // Any authenticated user can access this endpoint, regardless of their role.
+ // This is the most basic level of authorization, which only checks if the user is authenticated.
+ [Authorize]
+ // The following lines are examples of how to restrict access to specific roles. Uncomment the desired line(s) to apply the restriction.
+ // This is the way you can specify multiple roles for an endpoint. You can use a comma-separated list to allow access to users with any of the specified roles.
+ // [Authorize(Roles = "user")] //Only users with the "user" role can access this endpoint
+ // [Authorize(Roles = "owner")] //Only users with the "owner" role can access this endpoint
+ // [Authorize(Roles = "admin")] //Only users with the "admin" role can access this endpoint
+ // [Authorize(Roles = "user,owner")] //Only users with either the "user" or "owner" role can access this endpoint
+ [HttpGet]
+ public IEnumerable Get()
+ {
+ var xxx = User;
+ return Enumerable.Range(1, 5).Select(index => new WeatherForecast
+ {
+ Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ TemperatureC = Random.Shared.Next(-20, 55),
+ Summary = Summaries[Random.Shared.Next(Summaries.Length)]
+ })
+ .ToArray();
+ }
+ }
+}
diff --git a/CommitHookTest/OpenID-API/Dockerfile b/CommitHookTest/OpenID-API/Dockerfile
new file mode 100644
index 0000000..94e22e7
--- /dev/null
+++ b/CommitHookTest/OpenID-API/Dockerfile
@@ -0,0 +1,30 @@
+# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+# This stage is used when running from VS in fast mode (Default for Debug configuration)
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
+USER $APP_UID
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+
+# This stage is used to build the service project
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["OpenID_API/OpenID_API.csproj", "OpenID_API/"]
+RUN dotnet restore "./OpenID_API/OpenID_API.csproj"
+COPY . .
+WORKDIR "/src/OpenID_API"
+RUN dotnet build "./OpenID_API.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+# This stage is used to publish the service project to be copied to the final stage
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./OpenID_API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "OpenID_API.dll"]
\ No newline at end of file
diff --git a/CommitHookTest/OpenID-API/OpenID-API.csproj b/CommitHookTest/OpenID-API/OpenID-API.csproj
new file mode 100644
index 0000000..8eebf44
--- /dev/null
+++ b/CommitHookTest/OpenID-API/OpenID-API.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net10.0
+ enable
+ enable
+ 2d959f13-fec1-48ad-a7f1-455a8d4830d3
+ Linux
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CommitHookTest/OpenID-API/OpenID-API.http b/CommitHookTest/OpenID-API/OpenID-API.http
new file mode 100644
index 0000000..98fea59
--- /dev/null
+++ b/CommitHookTest/OpenID-API/OpenID-API.http
@@ -0,0 +1,6 @@
+@OpenID_API_HostAddress = http://localhost:5007
+
+GET {{OpenID_API_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/CommitHookTest/OpenID-API/OpenIDConnect/OpenIDConnectUtils.cs b/CommitHookTest/OpenID-API/OpenIDConnect/OpenIDConnectUtils.cs
new file mode 100644
index 0000000..3fbc9f1
--- /dev/null
+++ b/CommitHookTest/OpenID-API/OpenIDConnect/OpenIDConnectUtils.cs
@@ -0,0 +1,225 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi;
+using System.IdentityModel.Tokens.Jwt;
+using System.Text;
+
+
+// Required NuGet Packages
+// Microsoft.AspNet.WebApi.Core
+// Microsoft.AspNetCore.Authentication.JwtBearer
+// Microsoft.AspNetCore.Authentication.OpenIdConnect
+// Microsoft.IdentityModel.Protocols.OpenIdConnect
+
+
+namespace OpenID_API.OpenIDConnect
+{
+ public class OpenIDConnectUtils
+ {
+ // Configuration priority from highest to lowest
+ // Highest: Command line arguments
+ // : Non-prefixed environment variables
+ // : User secrets from the .NET User Secrets Manager
+ // : Any appsettings.{ ENVIRONMENT_NAME }.json files
+ // : The appsettings.json file
+ // Lowest : Fallback to the host configuration
+
+ ///
+ /// Setting up OpenIDConnect authentication (Program.cs)
+ ///
+ /// WebApplicationBuilder
+
+ public void ConfigureBuilder(WebApplicationBuilder builder)
+ {
+ // Add services to the container.
+ MyConfiguration.Set(builder.Configuration);
+
+ // >>> This adds the authentication service
+ builder.Services.AddAuthentication(options =>
+ {
+ options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddCookie()
+ .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
+ {
+ options.Authority = builder.Configuration["OpenIDRealmURI"];
+ options.Audience = builder.Configuration["OpenIDClient"];
+ //options.TokenValidationParameters.RoleClaimType = "roles"; // adjust if your roles claim is named differently
+
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ RoleClaimType = "roles", // 👈 IMPORTANT
+ NameClaimType = "name",
+ ValidateIssuer = true,
+ ValidateAudience = true
+ };
+
+ options.MapInboundClaims = false; // prevent automatic claim type mapping
+ options.RequireHttpsMetadata = true; // set to false only for development
+ options.IncludeErrorDetails = true;
+
+ // Map claim types if needed
+ //options.TokenValidationParameters = new TokenValidationParameters
+ //{
+ // NameClaimType = "name",
+ // RoleClaimType = "roles"
+ //};
+
+ options.Events = new JwtBearerEvents
+ {
+ OnMessageReceived = context =>
+ {
+ // Optional: log the token or handle custom token retrieval
+ var token = context.Request.Headers["Authorization"].ToString();
+ string path = context.Request.Path;
+ if (!string.IsNullOrEmpty(token))
+ {
+ Console.WriteLine("Access token");
+ Console.WriteLine($"URL: {path}");
+ Console.WriteLine($"Token: {token}\r\n");
+ }
+ else
+ {
+ Console.WriteLine("Access token");
+ Console.WriteLine("URL: " + path);
+ Console.WriteLine("Token: No access token provided\r\n");
+ }
+ return Task.CompletedTask;
+ },
+ OnTokenValidated = context =>
+ {
+ var loggerFactory = context.HttpContext.RequestServices.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("Api.Authorization");
+ var claims = context?.Principal?.Claims;
+ if (claims is null || !claims.Any())
+ {
+ logger.LogWarning("Claims null or empty");
+ return Task.CompletedTask;
+ }
+ var claimsBuilder = new StringBuilder();
+ claimsBuilder.AppendLine("User Claims:");
+ foreach (var claim in claims)
+ {
+ claimsBuilder.AppendLine($"[{claim.Type}] - [{claim.Value}]");
+ }
+ logger.LogTrace("{Claims}", claimsBuilder.ToString());
+ return Task.CompletedTask;
+ },
+ OnAuthenticationFailed = context =>
+ {
+ // Optional: log authentication failures
+ Console.WriteLine("Authentication failed: " + context.Exception.Message);
+ return Task.CompletedTask;
+ }
+ };
+
+ });
+ // <<< End of authentication service setup
+
+ // >>> This adds the authorization policies
+ builder.Services
+ .AddAuthorizationBuilder()
+ .AddPolicy("read_access", builder =>
+ {
+ // claim, list of acceptable values
+ builder.RequireClaim("myClaim", "MyClaimValueRO1", "MyClaimValueRO2");
+ })
+ .AddPolicy("write_access", builder =>
+ {
+ builder.RequireClaim("myClaim", "MyClaimValueRW1", "MyClaimValueRW2");
+ })
+ .AddPolicy("Admin", policy => policy.RequireClaim("roles", "admin", "warlock"));
+ // <<< End of authorization policies setup
+
+ ConfigureBuilderOpenAPI(builder);
+
+ builder.Services.Configure(options =>
+ {
+ options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
+ options.KnownIPNetworks.Clear();
+ options.KnownProxies.Clear();
+ });
+
+ }
+
+ private void ConfigureBuilderOpenAPI(WebApplicationBuilder builder)
+ {
+ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+ builder.Services.AddEndpointsApiExplorer();
+
+ // Add Swagger with Authorization option
+ // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/HEAD/docs/configure-and-customize-swaggergen.md#add-security-definitions-and-requirements
+ builder.Services.AddSwaggerGen(options =>
+ {
+ options.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "My API",
+ Version = "v1"
+ });
+ options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.Http,
+ Scheme = "bearer",
+ BearerFormat = "JWT",
+ Description = "JWT Authorization header using the Bearer scheme."
+ });
+ options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
+ {
+ [new OpenApiSecuritySchemeReference("bearer", document)] = []
+ });
+ });
+ // Add Swagger with Authorization option
+ }
+
+ private void ConfigureAppOpenAPI(WebApplication app)
+ {
+ app.MapSwagger();
+ app.UseSwagger();
+ app.UseSwaggerUI(options =>
+ {
+ options.SwaggerEndpoint("v1/swagger.json", "Your Name Here");
+ });
+ }
+
+
+
+ ///
+ ///
+ /// WebApplication
+ public void ConfigureApp(WebApplication app)
+ {
+ if (app.Environment.IsDevelopment()) ConfigureAppOpenAPI(app);
+ }
+
+ public JwtSecurityToken GetJwtPayload(HttpContext myContext)
+ {
+ var handler = new JwtSecurityTokenHandler();
+ return handler.ReadJwtToken(myContext.GetTokenAsync("access_token").Result);
+ }
+
+ public string GetJwtClaim(HttpContext myContext, string theClaim)
+ {
+ JwtSecurityToken jwtPayload = GetJwtPayload(myContext);
+ return jwtPayload.Claims.FirstOrDefault(claim => claim.Type == theClaim).Value;
+ }
+
+ }
+
+ static public class MyConfiguration
+ {
+ static ConfigurationManager _config;
+ static public void Set(ConfigurationManager config)
+ {
+ _config = config;
+ }
+
+ static public ConfigurationManager Get()
+ {
+ return _config;
+ }
+
+ }
+}
diff --git a/CommitHookTest/OpenID-API/Program.cs b/CommitHookTest/OpenID-API/Program.cs
new file mode 100644
index 0000000..0670157
--- /dev/null
+++ b/CommitHookTest/OpenID-API/Program.cs
@@ -0,0 +1,25 @@
+using OpenID_API.OpenIDConnect;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Configure for OpenID
+OpenIDConnectUtils oidcConfig = new();
+oidcConfig.ConfigureBuilder(builder);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+// Configure for OpenID
+oidcConfig.ConfigureApp(app);
+
+app.UseHttpsRedirection();
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/CommitHookTest/OpenID-API/Properties/launchSettings.json b/CommitHookTest/OpenID-API/Properties/launchSettings.json
new file mode 100644
index 0000000..051717e
--- /dev/null
+++ b/CommitHookTest/OpenID-API/Properties/launchSettings.json
@@ -0,0 +1,35 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:8887"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:8888"
+ },
+ "Container (Dockerfile)": {
+ "commandName": "Docker",
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
+ "environmentVariables": {
+ "ASPNETCORE_HTTPS_PORTS": "8081",
+ "ASPNETCORE_HTTP_PORTS": "8080"
+ },
+ "publishAllPorts": true,
+ "useSSL": true
+ }
+ },
+ "$schema": "https://json.schemastore.org/launchsettings.json"
+}
\ No newline at end of file
diff --git a/CommitHookTest/OpenID-API/WeatherForecast.cs b/CommitHookTest/OpenID-API/WeatherForecast.cs
new file mode 100644
index 0000000..d9dbbf5
--- /dev/null
+++ b/CommitHookTest/OpenID-API/WeatherForecast.cs
@@ -0,0 +1,13 @@
+namespace OpenID_API
+{
+ public class WeatherForecast
+ {
+ public DateOnly Date { get; set; }
+
+ public int TemperatureC { get; set; }
+
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+ public string? Summary { get; set; }
+ }
+}
diff --git a/CommitHookTest/OpenID-API/appsettings.Development.json b/CommitHookTest/OpenID-API/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/CommitHookTest/OpenID-API/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/CommitHookTest/OpenID-API/appsettings.json b/CommitHookTest/OpenID-API/appsettings.json
new file mode 100644
index 0000000..2f29a4c
--- /dev/null
+++ b/CommitHookTest/OpenID-API/appsettings.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.AspNetCore.Authentication": "Information",
+ "Microsoft.AspNetCore.Authorization": "Information",
+ "Api.Authorization": "Trace"
+ }
+ },
+ "AllowedHosts": "*",
+ "Comment1": "Configuration for OpenID Connect authentication",
+ "OpenIDRealmURI": "https://auth.a.ucnit.eu/realms/xOIDCx",
+ "OpenIDClient": "Alice",
+ "OpenIDSecret": "JvDnso8O773pE9ENJdRhsrJd5pVD5Q86"
+}
diff --git a/CommitHookTest/TestProject/MSTestSettings.cs b/CommitHookTest/TestProject/MSTestSettings.cs
new file mode 100644
index 0000000..aaf278c
--- /dev/null
+++ b/CommitHookTest/TestProject/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/CommitHookTest/TestProject/Test1.cs b/CommitHookTest/TestProject/Test1.cs
new file mode 100644
index 0000000..20e81f1
--- /dev/null
+++ b/CommitHookTest/TestProject/Test1.cs
@@ -0,0 +1,13 @@
+namespace TestProject
+{
+ [TestClass]
+ public sealed class Test1
+ {
+ [TestMethod]
+ public void TestMethod1()
+ {
+ Assert.Fail("intentionally failing...");
+ }
+
+ }
+}
diff --git a/CommitHookTest/TestProject/TestProject.csproj b/CommitHookTest/TestProject/TestProject.csproj
new file mode 100644
index 0000000..60cb8d0
--- /dev/null
+++ b/CommitHookTest/TestProject/TestProject.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net10.0
+ latest
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+