Skip to content

Commit 99da93f

Browse files
committed
2.7.1
1 parent f702109 commit 99da93f

18 files changed

Lines changed: 2289 additions & 1408 deletions

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,9 @@ FodyWeavers.xsd
395395
*.msp
396396

397397
*.sln.iml
398-
/source/NpgsqlRestTestWebApi/appsettings.Development.json
399398

400399
.idea/
401-
build/
400+
build/
401+
_wwwroot/
402+
src/
403+
appsettings.Development.json

NpgsqlRest.sln

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ VisualStudioVersion = 17.8.34330.188
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRest", "NpgsqlRest\NpgsqlRest.csproj", "{65089EC7-0F4D-479F-8327-31121E2348CB}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRestTestWebApi", "NpgsqlRestTestWebApi\NpgsqlRestTestWebApi.csproj", "{85E01073-FB42-4149-A171-55B0ACD1F06D}"
9-
EndProject
108
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRestTests", "NpgsqlRestTests\NpgsqlRestTests.csproj", "{2C5E44A6-D7B5-43E7-A2B7-DBD5E49292DA}"
119
EndProject
1210
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35679C8A-E260-4E62-8A5C-702999F671B6}"
@@ -31,6 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRest.CrudSource", "pl
3129
EndProject
3230
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRest.TsClient", "plugins\NpgsqlRest.TsClient\NpgsqlRest.TsClient.csproj", "{88607FEC-9FED-45D7-B629-92BBDBC682C7}"
3331
EndProject
32+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NpgsqlRestClient", "NpgsqlRestClient\NpgsqlRestClient.csproj", "{C47CABBE-0CF1-4A22-AF8D-720DCDF991D0}"
33+
EndProject
34+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D48B45A5-E086-4A0F-9E3B-F977EC551B9B}"
35+
EndProject
3436
Global
3537
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3638
Debug|Any CPU = Debug|Any CPU
@@ -41,10 +43,6 @@ Global
4143
{65089EC7-0F4D-479F-8327-31121E2348CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
4244
{65089EC7-0F4D-479F-8327-31121E2348CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
4345
{65089EC7-0F4D-479F-8327-31121E2348CB}.Release|Any CPU.Build.0 = Release|Any CPU
44-
{85E01073-FB42-4149-A171-55B0ACD1F06D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45-
{85E01073-FB42-4149-A171-55B0ACD1F06D}.Debug|Any CPU.Build.0 = Debug|Any CPU
46-
{85E01073-FB42-4149-A171-55B0ACD1F06D}.Release|Any CPU.ActiveCfg = Release|Any CPU
47-
{85E01073-FB42-4149-A171-55B0ACD1F06D}.Release|Any CPU.Build.0 = Release|Any CPU
4846
{2C5E44A6-D7B5-43E7-A2B7-DBD5E49292DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4947
{2C5E44A6-D7B5-43E7-A2B7-DBD5E49292DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
5048
{2C5E44A6-D7B5-43E7-A2B7-DBD5E49292DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -65,11 +63,17 @@ Global
6563
{88607FEC-9FED-45D7-B629-92BBDBC682C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
6664
{88607FEC-9FED-45D7-B629-92BBDBC682C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
6765
{88607FEC-9FED-45D7-B629-92BBDBC682C7}.Release|Any CPU.Build.0 = Release|Any CPU
66+
{C47CABBE-0CF1-4A22-AF8D-720DCDF991D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67+
{C47CABBE-0CF1-4A22-AF8D-720DCDF991D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
68+
{C47CABBE-0CF1-4A22-AF8D-720DCDF991D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
69+
{C47CABBE-0CF1-4A22-AF8D-720DCDF991D0}.Release|Any CPU.Build.0 = Release|Any CPU
6870
EndGlobalSection
6971
GlobalSection(SolutionProperties) = preSolution
7072
HideSolutionNode = FALSE
7173
EndGlobalSection
7274
GlobalSection(NestedProjects) = preSolution
75+
{2C5E44A6-D7B5-43E7-A2B7-DBD5E49292DA} = {D48B45A5-E086-4A0F-9E3B-F977EC551B9B}
76+
{2D8EAE94-E992-4CA0-BB86-91738ABB429B} = {D48B45A5-E086-4A0F-9E3B-F977EC551B9B}
7377
{9D0D7807-9633-49A7-81D6-F2DD722DCC04} = {168D4570-4DD0-4E85-83E4-889B7495370A}
7478
{94DF762B-9C25-4E27-95C6-DED177A2FDB0} = {168D4570-4DD0-4E85-83E4-889B7495370A}
7579
{88607FEC-9FED-45D7-B629-92BBDBC682C7} = {168D4570-4DD0-4E85-83E4-889B7495370A}

NpgsqlRest/Auth/AuthHandler.cs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
using System.Net;
2+
using System.Security.Claims;
3+
using Microsoft.AspNetCore.Http.HttpResults;
4+
using Npgsql;
5+
6+
using static NpgsqlRest.Auth.ClaimsDictionary;
7+
8+
namespace NpgsqlRest.Auth;
9+
10+
internal static class AuthHandler
11+
{
12+
public static async Task HandleLoginAsync(
13+
NpgsqlCommand command,
14+
HttpContext context,
15+
RoutineEndpoint endpoint,
16+
Routine routine,
17+
NpgsqlRestOptions options,
18+
ILogger? logger)
19+
{
20+
await using var reader = await command.ExecuteReaderAsync();
21+
22+
if (await reader.ReadAsync() is false)
23+
{
24+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
25+
return;
26+
}
27+
28+
if (reader.FieldCount == 0)
29+
{
30+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
31+
return;
32+
}
33+
34+
var authenticationType = options.AuthenticationOptions.DefaultAuthenticationType;
35+
string? scheme = null;
36+
string? message = null;
37+
var claims = new List<Claim>(10);
38+
39+
for (int i = 0; i < routine.ColumnCount; i++)
40+
{
41+
var name1 = routine.ColumnNames[i];
42+
var name2 = endpoint.ColumnNames[i];
43+
var descriptor = routine.ColumnsTypeDescriptor[i];
44+
45+
if (options.AuthenticationOptions.StatusColumnName is not null)
46+
{
47+
if (string.Equals(name1, options.AuthenticationOptions.StatusColumnName, StringComparison.OrdinalIgnoreCase) ||
48+
string.Equals(name2, options.AuthenticationOptions.StatusColumnName, StringComparison.OrdinalIgnoreCase))
49+
{
50+
if (descriptor.IsBoolean)
51+
{
52+
var ok = reader?.GetBoolean(i);
53+
if (ok is false)
54+
{
55+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
56+
}
57+
}
58+
else if (descriptor.IsNumeric)
59+
{
60+
var status = reader?.GetInt32(i) ?? 200;
61+
if (status != (int)HttpStatusCode.OK)
62+
{
63+
context.Response.StatusCode = status;
64+
}
65+
}
66+
else
67+
{
68+
logger?.WrongStatusType(routine.Type, routine.Schema, routine.Name);
69+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
70+
await context.Response.CompleteAsync();
71+
return;
72+
}
73+
continue;
74+
}
75+
}
76+
77+
if (options.AuthenticationOptions.SchemeColumnName is not null)
78+
{
79+
if (string.Equals(name1, options.AuthenticationOptions.SchemeColumnName, StringComparison.OrdinalIgnoreCase) ||
80+
string.Equals(name2, options.AuthenticationOptions.SchemeColumnName, StringComparison.OrdinalIgnoreCase))
81+
{
82+
scheme = reader?.GetValue(i).ToString();
83+
continue;
84+
}
85+
}
86+
87+
if (options.AuthenticationOptions.MessageColumnName is not null)
88+
{
89+
if (string.Equals(name1, options.AuthenticationOptions.MessageColumnName, StringComparison.OrdinalIgnoreCase) ||
90+
string.Equals(name2, options.AuthenticationOptions.MessageColumnName, StringComparison.OrdinalIgnoreCase))
91+
{
92+
message = reader?.GetValue(i).ToString();
93+
continue;
94+
}
95+
}
96+
97+
string? claimType;
98+
if (options.AuthenticationOptions.UseActiveDirectoryFederationServicesClaimTypes)
99+
{
100+
if (ClaimTypesDictionary.TryGetValue(name1.ToLowerInvariant(), out claimType) is false)
101+
{
102+
if (ClaimTypesDictionary.TryGetValue(name2.ToLowerInvariant(), out claimType) is false)
103+
{
104+
claimType = name2;
105+
}
106+
}
107+
}
108+
else
109+
{
110+
claimType = name2;
111+
}
112+
113+
if (reader?.IsDBNull(i) is true)
114+
{
115+
claims.Add(new Claim(claimType, ""));
116+
}
117+
else if (descriptor.IsArray)
118+
{
119+
object[]? values = reader?.GetValue(i) as object[];
120+
for (int j = 0; j < values?.Length; j++)
121+
{
122+
claims.Add(new Claim(claimType, values[j]?.ToString() ?? ""));
123+
}
124+
}
125+
else
126+
{
127+
string? value = reader?.GetValue(i)?.ToString();
128+
claims.Add(new Claim(claimType, value ?? ""));
129+
}
130+
}
131+
132+
if (context.Response.StatusCode == (int)HttpStatusCode.OK)
133+
{
134+
var principal = new ClaimsPrincipal(new ClaimsIdentity(
135+
claims, scheme ?? authenticationType,
136+
nameType: options.AuthenticationOptions.DefaultNameClaimType,
137+
roleType: options.AuthenticationOptions.DefaultRoleClaimType));
138+
139+
if (Results.SignIn(principal: principal, authenticationScheme: scheme) is not SignInHttpResult result)
140+
{
141+
logger?.LogError("Failed in constructing user identity for authentication.");
142+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
143+
return;
144+
}
145+
await result.ExecuteAsync(context);
146+
}
147+
if (message is not null)
148+
{
149+
await context.Response.WriteAsync(message);
150+
}
151+
}
152+
153+
public static async Task HandleLogoutAsync(NpgsqlCommand command, Routine routine, HttpContext context)
154+
{
155+
if (routine.IsVoid)
156+
{
157+
await command.ExecuteNonQueryAsync();
158+
await Results.SignOut().ExecuteAsync(context);
159+
await context.Response.CompleteAsync();
160+
return;
161+
}
162+
163+
List<string> schemes = new(5);
164+
await using NpgsqlDataReader reader = await command.ExecuteReaderAsync();
165+
#pragma warning disable CS8602 // Dereference of a possibly null reference.
166+
while (await reader.ReadAsync())
167+
{
168+
for (int i = 0; i < reader?.FieldCount; i++)
169+
{
170+
if (await reader.IsDBNullAsync(i) is true)
171+
{
172+
continue;
173+
}
174+
175+
var descriptor = routine.ColumnsTypeDescriptor[i];
176+
if (descriptor.IsArray)
177+
{
178+
object[]? values = reader?.GetValue(i) as object[];
179+
for (int j = 0; j < values?.Length; j++)
180+
{
181+
var value = values[j]?.ToString();
182+
if (value is not null)
183+
{
184+
schemes.Add(value);
185+
}
186+
}
187+
}
188+
else
189+
{
190+
string? value = reader?.GetValue(i)?.ToString();
191+
if (value is not null)
192+
{
193+
schemes.Add(value);
194+
}
195+
}
196+
}
197+
}
198+
#pragma warning restore CS8602 // Dereference of a possibly null reference.
199+
200+
await Results.SignOut(authenticationSchemes: schemes.Count == 0 ? null : schemes).ExecuteAsync(context);
201+
await context.Response.CompleteAsync();
202+
}
203+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Collections.Frozen;
2+
3+
namespace NpgsqlRest.Auth;
4+
5+
public static class ClaimsDictionary
6+
{
7+
//var names = string.Join($",{Environment.NewLine}", typeof(ClaimTypes)
8+
// .GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
9+
// .Select(f => f.Name)
10+
// .Order()
11+
// .Select(n => $"{{ nameof(System.Security.Claims.ClaimTypes.{n}).ToLowerInvariant(), System.Security.Claims.ClaimTypes.{n} }}"));
12+
public static readonly FrozenDictionary<string, string> ClaimTypesDictionary = new Dictionary<string, string>()
13+
{
14+
{ nameof(System.Security.Claims.ClaimTypes.Actor).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Actor },
15+
{ nameof(System.Security.Claims.ClaimTypes.Anonymous).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Anonymous },
16+
{ nameof(System.Security.Claims.ClaimTypes.Authentication).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Authentication },
17+
{ nameof(System.Security.Claims.ClaimTypes.AuthenticationInstant).ToLowerInvariant(), System.Security.Claims.ClaimTypes.AuthenticationInstant },
18+
{ nameof(System.Security.Claims.ClaimTypes.AuthenticationMethod).ToLowerInvariant(), System.Security.Claims.ClaimTypes.AuthenticationMethod },
19+
{ nameof(System.Security.Claims.ClaimTypes.AuthorizationDecision).ToLowerInvariant(), System.Security.Claims.ClaimTypes.AuthorizationDecision },
20+
{ nameof(System.Security.Claims.ClaimTypes.CookiePath).ToLowerInvariant(), System.Security.Claims.ClaimTypes.CookiePath },
21+
{ nameof(System.Security.Claims.ClaimTypes.Country).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Country },
22+
{ nameof(System.Security.Claims.ClaimTypes.DateOfBirth).ToLowerInvariant(), System.Security.Claims.ClaimTypes.DateOfBirth },
23+
{ nameof(System.Security.Claims.ClaimTypes.DenyOnlyPrimaryGroupSid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.DenyOnlyPrimaryGroupSid },
24+
{ nameof(System.Security.Claims.ClaimTypes.DenyOnlyPrimarySid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.DenyOnlyPrimarySid },
25+
{ nameof(System.Security.Claims.ClaimTypes.DenyOnlySid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.DenyOnlySid },
26+
{ nameof(System.Security.Claims.ClaimTypes.DenyOnlyWindowsDeviceGroup).ToLowerInvariant(), System.Security.Claims.ClaimTypes.DenyOnlyWindowsDeviceGroup },
27+
{ nameof(System.Security.Claims.ClaimTypes.Dns).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Dns },
28+
{ nameof(System.Security.Claims.ClaimTypes.Dsa).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Dsa },
29+
{ nameof(System.Security.Claims.ClaimTypes.Email).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Email },
30+
{ nameof(System.Security.Claims.ClaimTypes.Expiration).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Expiration },
31+
{ nameof(System.Security.Claims.ClaimTypes.Expired).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Expired },
32+
{ nameof(System.Security.Claims.ClaimTypes.Gender).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Gender },
33+
{ nameof(System.Security.Claims.ClaimTypes.GivenName).ToLowerInvariant(), System.Security.Claims.ClaimTypes.GivenName },
34+
{ nameof(System.Security.Claims.ClaimTypes.GroupSid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.GroupSid },
35+
{ nameof(System.Security.Claims.ClaimTypes.Hash).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Hash },
36+
{ nameof(System.Security.Claims.ClaimTypes.HomePhone).ToLowerInvariant(), System.Security.Claims.ClaimTypes.HomePhone },
37+
{ nameof(System.Security.Claims.ClaimTypes.IsPersistent).ToLowerInvariant(), System.Security.Claims.ClaimTypes.IsPersistent },
38+
{ nameof(System.Security.Claims.ClaimTypes.Locality).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Locality },
39+
{ nameof(System.Security.Claims.ClaimTypes.MobilePhone).ToLowerInvariant(), System.Security.Claims.ClaimTypes.MobilePhone },
40+
{ nameof(System.Security.Claims.ClaimTypes.Name).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Name },
41+
{ nameof(System.Security.Claims.ClaimTypes.NameIdentifier).ToLowerInvariant(), System.Security.Claims.ClaimTypes.NameIdentifier },
42+
{ nameof(System.Security.Claims.ClaimTypes.OtherPhone).ToLowerInvariant(), System.Security.Claims.ClaimTypes.OtherPhone },
43+
{ nameof(System.Security.Claims.ClaimTypes.PostalCode).ToLowerInvariant(), System.Security.Claims.ClaimTypes.PostalCode },
44+
{ nameof(System.Security.Claims.ClaimTypes.PrimaryGroupSid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.PrimaryGroupSid },
45+
{ nameof(System.Security.Claims.ClaimTypes.PrimarySid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.PrimarySid },
46+
{ nameof(System.Security.Claims.ClaimTypes.Role).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Role },
47+
{ nameof(System.Security.Claims.ClaimTypes.Rsa).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Rsa },
48+
{ nameof(System.Security.Claims.ClaimTypes.SerialNumber).ToLowerInvariant(), System.Security.Claims.ClaimTypes.SerialNumber },
49+
{ nameof(System.Security.Claims.ClaimTypes.Sid).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Sid },
50+
{ nameof(System.Security.Claims.ClaimTypes.Spn).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Spn },
51+
{ nameof(System.Security.Claims.ClaimTypes.StateOrProvince).ToLowerInvariant(), System.Security.Claims.ClaimTypes.StateOrProvince },
52+
{ nameof(System.Security.Claims.ClaimTypes.StreetAddress).ToLowerInvariant(), System.Security.Claims.ClaimTypes.StreetAddress },
53+
{ nameof(System.Security.Claims.ClaimTypes.Surname).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Surname },
54+
{ nameof(System.Security.Claims.ClaimTypes.System).ToLowerInvariant(), System.Security.Claims.ClaimTypes.System },
55+
{ nameof(System.Security.Claims.ClaimTypes.Thumbprint).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Thumbprint },
56+
{ nameof(System.Security.Claims.ClaimTypes.Upn).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Upn },
57+
{ nameof(System.Security.Claims.ClaimTypes.Uri).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Uri },
58+
{ nameof(System.Security.Claims.ClaimTypes.UserData).ToLowerInvariant(), System.Security.Claims.ClaimTypes.UserData },
59+
{ nameof(System.Security.Claims.ClaimTypes.Version).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Version },
60+
{ nameof(System.Security.Claims.ClaimTypes.Webpage).ToLowerInvariant(), System.Security.Claims.ClaimTypes.Webpage },
61+
{ nameof(System.Security.Claims.ClaimTypes.WindowsAccountName).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsAccountName },
62+
{ nameof(System.Security.Claims.ClaimTypes.WindowsDeviceClaim).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsDeviceClaim },
63+
{ nameof(System.Security.Claims.ClaimTypes.WindowsDeviceGroup).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsDeviceGroup },
64+
{ nameof(System.Security.Claims.ClaimTypes.WindowsFqbnVersion).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsFqbnVersion },
65+
{ nameof(System.Security.Claims.ClaimTypes.WindowsSubAuthority).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsSubAuthority },
66+
{ nameof(System.Security.Claims.ClaimTypes.WindowsUserClaim).ToLowerInvariant(), System.Security.Claims.ClaimTypes.WindowsUserClaim },
67+
{ nameof(System.Security.Claims.ClaimTypes.X500DistinguishedName).ToLowerInvariant(), System.Security.Claims.ClaimTypes.X500DistinguishedName }
68+
}.ToFrozenDictionary();
69+
}

0 commit comments

Comments
 (0)