Skip to content

Commit 5b9d995

Browse files
devlooped-botkzu
authored andcommitted
⬆️ Bump files with dotnet-file sync
# devlooped/oss - Fix improper first / in gh api repos devlooped/oss@f2b690c # devlooped/SponsorLink - Improve wording on editor usage requiring sponsorship devlooped/SponsorLink@21d8dac - Introduce standalone SponsorManifest for read/validate devlooped/SponsorLink@a755e4b
1 parent 5f3adcb commit 5b9d995

File tree

8 files changed

+214
-153
lines changed

8 files changed

+214
-153
lines changed

.github/actions/dotnet/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ runs:
88
id: dotnet
99
shell: bash
1010
run: |
11-
VERSIONS=$(gh api /repos/${{ github.repository }}/properties/values | jq -r '.[] | select(.property_name == "DOTNET") | .value')
11+
VERSIONS=$(gh api repos/${{ github.repository }}/properties/values | jq -r '.[] | select(.property_name == "DOTNET") | .value')
1212
# Remove extra whitespace from VERSIONS
1313
VERSIONS=$(echo "$VERSIONS" | tr -s ' ' | tr -d ' ')
1414
# Convert comma-separated to newline-separated

.netconfig

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,6 @@
223223

224224
etag = a5d79dbc0ed9fac4fb1879fb3790b9ebab18e47c14c454554ce9f53f21487bb5
225225
weak
226-
[file "src/SponsorLink/SponsorLink/ManifestStatus.cs"]
227-
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/ManifestStatus.cs
228-
sha = f47528874a6d9192b5546f84b455f5ccc474a707
229-
230-
etag = e46848f83c0436ba33a1c09a4060ad627a74db41bab66bb37ca40fce8a6532a7
231-
weak
232226
[file "src/SponsorLink/SponsorLink/Resources.es-AR.resx"]
233227
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es-AR.resx
234228
sha = 586398c3e650495f36601ecc8983a14ed745e058
@@ -237,21 +231,21 @@
237231
weak
238232
[file "src/SponsorLink/SponsorLink/Resources.es.resx"]
239233
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.es.resx
240-
sha = 29921560c73bb91c2a21a21800daf0b250773598
234+
sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834
241235

242-
etag = feb9dc86e4d9c0c4a294cd6e03c5b914943e8d206b88a125abd1b0f882ddb247
236+
etag = 89a7bb797aeacca43e043196a00eea91f282df4caf9bbe937749026a03f707ad
243237
weak
244238
[file "src/SponsorLink/SponsorLink/Resources.resx"]
245239
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/Resources.resx
246-
sha = 29921560c73bb91c2a21a21800daf0b250773598
240+
sha = 21d8dac3077c75cd07d7cc7f9e10f2620afce834
247241

248-
etag = 7665a3be17cd224b1c413ade6a9c1c5a822dace1e7f9daae33a2e52d8bca15bb
242+
etag = 8902652b8907de2fbccf73f3738d0fce503fc667a084171d6b88bf3373e559e7
249243
weak
250244
[file "src/SponsorLink/SponsorLink/SponsorLink.cs"]
251245
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.cs
252-
sha = 3f72a9fd35274a659dd380a7d5b747d71b9732a1
246+
sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
253247

254-
etag = 616598e0ecb6d2ce97660aa6ac049e2a31a1c953669743b7b612b61d40c37706
248+
etag = 402e2beb11cf64c07be3d0fc3e89115fd09fc24133c08a8951bf0e784909c510
255249
weak
256250
[file "src/SponsorLink/SponsorLink/SponsorLink.csproj"]
257251
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorLink.csproj
@@ -343,12 +337,6 @@
343337

344338
etag = 1875555adb7eab21acf1e730b6baeb8c095d9f6f9f07303a87ad9c16e0f6490d
345339
weak
346-
[file "src/SponsorLink/Tests/SponsorLinkTests.cs"]
347-
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorLinkTests.cs
348-
sha = f47528874a6d9192b5546f84b455f5ccc474a707
349-
350-
etag = 1fa41250bd984e8aa840a966d34ce0e94f2111d1422d7f50b864c38364fcf4a4
351-
weak
352340
[file "src/SponsorLink/Tests/SponsorableManifest.cs"]
353341
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorableManifest.cs
354342
sha = f47528874a6d9192b5546f84b455f5ccc474a707
@@ -424,6 +412,16 @@
424412
weak
425413
[file ".github/actions/dotnet/action.yml"]
426414
url = https://github.com/devlooped/oss/blob/main/.github/actions/dotnet/action.yml
427-
sha = 7e9acf45714821735ccc5bb3f0f844f505d5ae8a
428-
etag = 0dda1755bb185e92aecd1bf8665214f12e3562315257f642ca4ad85c24771ac1
415+
sha = f2b690ce307acb76c5b8d7faec1a5b971a93653e
416+
etag = 27ea11baa2397b3ec9e643a935832da97719c4e44215cfd135c49cad4c29373f
417+
weak
418+
[file "src/SponsorLink/SponsorLink/SponsorManifest.cs"]
419+
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/SponsorLink/SponsorManifest.cs
420+
sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
421+
etag = 55ef89e8441156541c1c74a50675b7f56633b56493031f0ffa877460839e3536
422+
weak
423+
[file "src/SponsorLink/Tests/SponsorManifestTests.cs"]
424+
url = https://github.com/devlooped/SponsorLink/blob/main/samples/dotnet/Tests/SponsorManifestTests.cs
425+
sha = a755e4be0f7cb73cfde208857e28f7cfeba2dcc3
426+
etag = 82ae1c417265f2e136544980b4f687a1cc2c1bfb24df93d354c259053550f4a3
429427
weak

src/SponsorLink/SponsorLink/ManifestStatus.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/SponsorLink/SponsorLink/Resources.es.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posterio
169169
<data name="Contributor_Title" xml:space="preserve">
170170
<value>Eres un contribuidor al proyecto, eres lo máximo 💟!</value>
171171
</data>
172+
<data name="Editor_Disabled" xml:space="preserve">
173+
<value>El uso de {0} sin warnings en el editor requiere un patrocinio activo. Ver mas en {1}.</value>
174+
</data>
172175
<data name="Grace_Description" xml:space="preserve">
173176
<value>Patrocinar los proyectos en que dependes asegura que se mantengan activos, y que recibas el apoyo que necesitas. También es muy económico y está disponible en todo el mundo!
174177
Por favor considera apoyar el proyecto patrocinando en {0} y ejecutando posteriormente 'sponsor sync {1}'.</value>

src/SponsorLink/SponsorLink/Resources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ Please consider supporting the project by sponsoring at {0} and running 'sponsor
171171
<value>You are a contributor to the project, you rock 💟!</value>
172172
</data>
173173
<data name="Editor_Disabled" xml:space="preserve">
174-
<value>Editor usage of {0} requires an active sponsorship. Learn more at {1}.</value>
174+
<value>Editor usage of {0} without warnings requires an active sponsorship. Learn more at {1}.</value>
175175
</data>
176176
<data name="Grace_Description" xml:space="preserve">
177177
<value>Sponsoring projects you depend on ensures they remain active, and that you get the support you need. It's also super affordable and available worldwide!

src/SponsorLink/SponsorLink/SponsorLink.cs

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -165,82 +165,4 @@ public static bool TryRead([NotNullWhen(true)] out ClaimsPrincipal? principal, I
165165

166166
return principal != null;
167167
}
168-
169-
/// <summary>
170-
/// Validates the manifest signature and optional expiration.
171-
/// </summary>
172-
/// <param name="jwt">The JWT to validate.</param>
173-
/// <param name="jwk">The key to validate the manifest signature with.</param>
174-
/// <param name="token">Except when returning <see cref="Status.Unknown"/>, returns the security token read from the JWT, even if signature check failed.</param>
175-
/// <param name="identity">The associated claims, only when return value is not <see cref="Status.Unknown"/>.</param>
176-
/// <param name="requireExpiration">Whether to check for expiration.</param>
177-
/// <returns>The status of the validation.</returns>
178-
public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsIdentity? identity, bool validateExpiration)
179-
{
180-
token = default;
181-
identity = default;
182-
183-
SecurityKey key;
184-
try
185-
{
186-
key = JsonWebKey.Create(jwk);
187-
}
188-
catch (ArgumentException)
189-
{
190-
return ManifestStatus.Unknown;
191-
}
192-
193-
var handler = new JsonWebTokenHandler { MapInboundClaims = false };
194-
195-
if (!handler.CanReadToken(jwt))
196-
return ManifestStatus.Unknown;
197-
198-
var validation = new TokenValidationParameters
199-
{
200-
RequireExpirationTime = false,
201-
ValidateLifetime = false,
202-
ValidateAudience = false,
203-
ValidateIssuer = false,
204-
ValidateIssuerSigningKey = true,
205-
IssuerSigningKey = key,
206-
RoleClaimType = "roles",
207-
NameClaimType = "sub",
208-
};
209-
210-
var result = handler.ValidateTokenAsync(jwt, validation).Result;
211-
if (result.Exception != null)
212-
{
213-
if (result.Exception is SecurityTokenInvalidSignatureException)
214-
{
215-
var jwtToken = handler.ReadJsonWebToken(jwt);
216-
token = jwtToken;
217-
identity = new ClaimsIdentity(jwtToken.Claims);
218-
return ManifestStatus.Invalid;
219-
}
220-
else
221-
{
222-
var jwtToken = handler.ReadJsonWebToken(jwt);
223-
token = jwtToken;
224-
identity = new ClaimsIdentity(jwtToken.Claims);
225-
return ManifestStatus.Invalid;
226-
}
227-
}
228-
229-
token = result.SecurityToken;
230-
identity = new ClaimsIdentity(result.ClaimsIdentity.Claims, "JWT");
231-
232-
if (validateExpiration && token.ValidTo == DateTime.MinValue)
233-
return ManifestStatus.Invalid;
234-
235-
// The sponsorable manifest does not have an expiration time.
236-
if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow)
237-
return ManifestStatus.Expired;
238-
239-
return ManifestStatus.Valid;
240-
}
241-
242-
class JwtRolesPrincipal(ClaimsIdentity identity) : ClaimsPrincipal([identity])
243-
{
244-
public override bool IsInRole(string role) => HasClaim("roles", role) || base.IsInRole(role);
245-
}
246168
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// <autogenerated />
2+
#nullable enable
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.IO;
7+
using System.Security.Claims;
8+
using Microsoft.IdentityModel.JsonWebTokens;
9+
using Microsoft.IdentityModel.Tokens;
10+
11+
namespace Devlooped.Sponsors;
12+
13+
/// <summary>
14+
/// The resulting status from validation.
15+
/// </summary>
16+
public enum ManifestStatus
17+
{
18+
/// <summary>
19+
/// The manifest couldn't be read at all.
20+
/// </summary>
21+
Unknown,
22+
/// <summary>
23+
/// The manifest was read and is valid (not expired and properly signed).
24+
/// </summary>
25+
Valid,
26+
/// <summary>
27+
/// The manifest was read but has expired.
28+
/// </summary>
29+
Expired,
30+
/// <summary>
31+
/// The manifest was read, but its signature is invalid.
32+
/// </summary>
33+
Invalid,
34+
}
35+
36+
/// <summary>
37+
/// Represents the sponsorship status of a user.
38+
/// </summary>
39+
/// <param name="Status">The status.</param>
40+
/// <param name="Principal">The principal potentially containing roles validated from the manifest.</param>
41+
/// <param name="SecurityToken">The security token from the validated manifest.</param>
42+
public record SponsorManifest(ManifestStatus Status, ClaimsPrincipal Principal, SecurityToken? SecurityToken)
43+
{
44+
/// <summary>
45+
/// Whether the manifest <see cref="Status"/> is <see cref="ManifestStatus.Valid"/>.
46+
/// </summary>
47+
public bool IsValid => Status == ManifestStatus.Valid;
48+
}
49+
50+
static partial class SponsorLink
51+
{
52+
/// <summary>
53+
/// Reads the local manifest (if present) for the specified sponsorable account and validates it
54+
/// against the given JWK key.
55+
/// </summary>
56+
/// <param name="sponsorable">The sponsorable account to read.</param>
57+
/// <param name="jwk">The public key to validate the signature on the manifest JWT if found.</param>
58+
/// <param name="validateExpiration">Whether to validate the manifest expiration. If <see langword="false"/>,
59+
/// an expired manifest will be reported as <see cref="ManifestStatus.Valid"/>. The expiration date
60+
/// can be checked in that case via the <see cref="SponsorManifest.SecurityToken"/>.</param>
61+
/// <returns>A manifest that represents the user status.</returns>
62+
public static SponsorManifest GetManifest(string sponsorable, string jwk, bool validateExpiration = true)
63+
{
64+
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
65+
".sponsorlink", "github", sponsorable + ".jwt");
66+
67+
if (!File.Exists(path))
68+
return new SponsorManifest(ManifestStatus.Unknown, new ClaimsPrincipal(), null);
69+
70+
return ParseManifest(File.ReadAllText(path), jwk, validateExpiration);
71+
}
72+
73+
internal static SponsorManifest ParseManifest(string jwt, string jwk, bool validateExpiration)
74+
{
75+
var status = Validate(jwt, jwk, out var token, out var identity, validateExpiration);
76+
77+
if (status == ManifestStatus.Unknown || identity == null)
78+
return new SponsorManifest(status, new ClaimsPrincipal(), token);
79+
80+
return new SponsorManifest(status, new JwtRolesPrincipal(identity), token);
81+
}
82+
83+
/// <summary>
84+
/// Validates the manifest signature and optional expiration.
85+
/// </summary>
86+
/// <param name="jwt">The JWT to validate.</param>
87+
/// <param name="jwk">The key to validate the manifest signature with.</param>
88+
/// <param name="token">Except when returning <see cref="Status.Unknown"/>, returns the security token read from the JWT, even if signature check failed.</param>
89+
/// <param name="identity">The associated claims, only when return value is not <see cref="Status.Unknown"/>.</param>
90+
/// <param name="requireExpiration">Whether to check for expiration.</param>
91+
/// <returns>The status of the validation.</returns>
92+
public static ManifestStatus Validate(string jwt, string jwk, out SecurityToken? token, out ClaimsIdentity? identity, bool validateExpiration)
93+
{
94+
token = default;
95+
identity = default;
96+
97+
SecurityKey key;
98+
try
99+
{
100+
key = JsonWebKey.Create(jwk);
101+
}
102+
catch (ArgumentException)
103+
{
104+
return ManifestStatus.Unknown;
105+
}
106+
107+
var handler = new JsonWebTokenHandler { MapInboundClaims = false };
108+
109+
if (!handler.CanReadToken(jwt))
110+
return ManifestStatus.Unknown;
111+
112+
var validation = new TokenValidationParameters
113+
{
114+
RequireExpirationTime = false,
115+
ValidateLifetime = false,
116+
ValidateAudience = false,
117+
ValidateIssuer = false,
118+
ValidateIssuerSigningKey = true,
119+
IssuerSigningKey = key,
120+
RoleClaimType = "roles",
121+
NameClaimType = "sub",
122+
};
123+
124+
var result = handler.ValidateTokenAsync(jwt, validation).Result;
125+
if (!result.IsValid || result.Exception != null)
126+
{
127+
if (result.Exception is SecurityTokenInvalidSignatureException)
128+
{
129+
var jwtToken = handler.ReadJsonWebToken(jwt);
130+
token = jwtToken;
131+
identity = new ClaimsIdentity(jwtToken.Claims);
132+
return ManifestStatus.Invalid;
133+
}
134+
else
135+
{
136+
var jwtToken = handler.ReadJsonWebToken(jwt);
137+
token = jwtToken;
138+
identity = new ClaimsIdentity(jwtToken.Claims);
139+
return ManifestStatus.Invalid;
140+
}
141+
}
142+
143+
token = result.SecurityToken;
144+
identity = new ClaimsIdentity(result.ClaimsIdentity.Claims, "JWT");
145+
146+
if (validateExpiration && token.ValidTo == DateTime.MinValue)
147+
return ManifestStatus.Invalid;
148+
149+
// The sponsorable manifest does not have an expiration time.
150+
if (validateExpiration && token.ValidTo < DateTimeOffset.UtcNow)
151+
return ManifestStatus.Expired;
152+
153+
return ManifestStatus.Valid;
154+
}
155+
156+
class JwtRolesPrincipal(ClaimsIdentity identity) : ClaimsPrincipal([identity])
157+
{
158+
public override bool IsInRole(string role) => HasClaim("roles", role) || base.IsInRole(role);
159+
}
160+
}

0 commit comments

Comments
 (0)