diff --git a/PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs b/PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs index fd59b55..b12e835 100644 --- a/PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs +++ b/PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs @@ -39,7 +39,6 @@ public class PublicDirectoryUITests(ITestOutputHelper output) : PageTest // Listed should be visible await conn.SetPluginSettingsAndVisibility(slug, "{}", "listed"); await tester.GoToUrl("/public/plugins"); - await tester.Page!.WaitForSelectorAsync("a[href='/public/plugins/rockstar-stylist']"); Assert.True(await tester.Page.Locator("a[href='/public/plugins/rockstar-stylist']").IsVisibleAsync()); @@ -68,7 +67,7 @@ public class PublicDirectoryUITests(ITestOutputHelper output) : PageTest await tester.Page.WaitForSelectorAsync("a[href='/public/plugins/rockstar-stylist']", new PageWaitForSelectorOptions { State = WaitForSelectorState.Hidden }); Assert.False(await tester.Page.Locator("a[href='/public/plugins/rockstar-stylist']").IsVisibleAsync()); - // Try to access hidden plugin page without being owner or admin + // Public page should not be accessible if hidden and owner is not logged in var response = await tester.GoToUrl("/public/plugins/rockstar-stylist"); Assert.Equal(404, response?.Status); diff --git a/PluginBuilder.Tests/UnitTest1.cs b/PluginBuilder.Tests/UnitTest1.cs index ac0fcb7..4835d49 100644 --- a/PluginBuilder.Tests/UnitTest1.cs +++ b/PluginBuilder.Tests/UnitTest1.cs @@ -102,14 +102,13 @@ public class UnitTest1 : UnitTestBase Assert.Equal("rockstar-stylist", version.ProjectSlug); // Let's see what happen if there is two versions of the same plugin - await conn.ExecuteAsync("INSERT INTO versions VALUES('rockstar-stylist', ARRAY[1,0,2,1], 0, ARRAY[1,4,6,0], 'f', CURRENT_TIMESTAMP, NULL)"); + await conn.ExecuteAsync("INSERT INTO versions VALUES('rockstar-stylist', ARRAY[1,0,2,1], 0, ARRAY[1,4,6,0], 'f', CURRENT_TIMESTAMP)"); versions = await client.GetPublishedVersions("1.4.6.0", true); - version = Assert.Single(versions); Assert.Equal("1.0.2.1", version.Version); versions = await client.GetPublishedVersions("1.4.6.0", true, true); - Assert.Equal("1.0.2.1", versions[1].Version); - Assert.Equal("1.0.2.0", versions[0].Version); + Assert.Equal("1.0.2.1", versions[0].Version); + Assert.Equal("1.0.2.0", versions[1].Version); // listed - always render await conn.ExecuteAsync("UPDATE plugins SET visibility = 'listed' WHERE slug = 'rockstar-stylist'"); diff --git a/PluginBuilder/Controllers/AccountController.cs b/PluginBuilder/Controllers/AccountController.cs index 457f928..ccd0da5 100644 --- a/PluginBuilder/Controllers/AccountController.cs +++ b/PluginBuilder/Controllers/AccountController.cs @@ -12,7 +12,6 @@ namespace PluginBuilder.Controllers; [Authorize] [Route("/account/")] public class AccountController( - GPGKeyService _gpgService, DBConnectionFactory connectionFactory, UserManager userManager, ExternalAccountVerificationService externalAccountVerificationService, @@ -71,7 +70,7 @@ public class AccountController( { if (!ModelState.IsValid) return View(model); - var user = await userManager.GetUserAsync(User); + var user = await userManager.GetUserAsync(User)!; await using var conn = await connectionFactory.Open(); var accountSettings = await conn.GetAccountDetailSettings(user!.Id) ?? new AccountSettings(); @@ -79,21 +78,7 @@ public class AccountController( accountSettings.Nostr = model.Settings.Nostr; accountSettings.Twitter = model.Settings.Twitter; accountSettings.Email = model.Settings.Email; - if (!string.IsNullOrEmpty(model.Settings.GPGKey?.PublicKey)) - { - var isPublicKeyValid = _gpgService.ValidateArmouredPublicKey(model.Settings.GPGKey.PublicKey.Trim(), out var message, out var keyViewModel); - if (!isPublicKeyValid) - { - TempData[TempDataConstant.WarningMessage] = $"GPG Key is not valid: {message}"; - return View(model); - } - accountSettings.GPGKey = keyViewModel; - } - else - { - accountSettings.GPGKey = new(); - } - await conn.SetAccountDetailSettings(accountSettings, user.Id); + await conn.SetAccountDetailSettings(accountSettings, user!.Id); TempData[TempDataConstant.SuccessMessage] = "Account details updated successfully"; return RedirectToAction(nameof(AccountDetails)); diff --git a/PluginBuilder/Controllers/AdminController.cs b/PluginBuilder/Controllers/AdminController.cs index 47b1616..946e072 100644 --- a/PluginBuilder/Controllers/AdminController.cs +++ b/PluginBuilder/Controllers/AdminController.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Npgsql; using PluginBuilder.Configuration; using PluginBuilder.Controllers.Logic; @@ -92,7 +91,6 @@ public class AdminController( model.Plugins = plugins; model.VerifiedEmailForPluginPublish = await conn.GetVerifiedEmailForPluginPublishSetting(); - model.VerifiedGPGSignatureForPluginRelease = await conn.RequiresGPGSignatureForPluginRelease(); return View(model); } @@ -100,7 +98,7 @@ public class AdminController( public async Task UpdateVerifiedEmailForPublishRequirement(bool verifiedEmailForPluginPublish) { await using var conn = await connectionFactory.Open(); - await conn.UpdatePluginAdminSettings(SettingsKeys.VerifiedEmailForPluginPublish, verifiedEmailForPluginPublish); + await conn.UpdateVerifiedEmailForPluginPublishSetting(verifiedEmailForPluginPublish); await userVerifiedCache.RefreshIsVerifiedEmailRequiredForPublish(conn); TempData[TempDataConstant.SuccessMessage] = "Email requirement setting for publishing plugin updated successfully"; return RedirectToAction("ListPlugins"); diff --git a/PluginBuilder/Controllers/Logic/UserVerifiedLogic.cs b/PluginBuilder/Controllers/Logic/UserVerifiedLogic.cs index 14b012c..4ef1b16 100644 --- a/PluginBuilder/Controllers/Logic/UserVerifiedLogic.cs +++ b/PluginBuilder/Controllers/Logic/UserVerifiedLogic.cs @@ -46,7 +46,6 @@ public class UserVerifiedLogic( public class UserVerifiedCache { public bool IsEmailVerificationRequiredForPublish { get; private set; } - public bool IsGPGSignatureRequiredForRelease { get; private set; } public bool IsEmailVerificationRequiredForLogin { get; private set; } public bool IsGithubVerificationRequired { get; private set; } @@ -56,11 +55,6 @@ public class UserVerifiedCache IsEmailVerificationRequiredForPublish = await conn.GetVerifiedEmailForPluginPublishSetting(); } - public async Task RefreshIsGPGSignatureRequirementForRelease(NpgsqlConnection conn) - { - IsGPGSignatureRequiredForRelease = await conn.RequiresGPGSignatureForPluginRelease(); - } - public async Task RefreshIsVerifiedEmailRequiredForLogin(NpgsqlConnection conn) { IsEmailVerificationRequiredForLogin = await conn.GetVerifiedEmailForLoginSetting(); @@ -75,7 +69,6 @@ public class UserVerifiedCache public async Task RefreshAllUserVerifiedSettings(NpgsqlConnection conn) { await RefreshIsVerifiedEmailRequiredForPublish(conn); - await RefreshIsGPGSignatureRequirementForRelease(conn); await RefreshIsVerifiedEmailRequiredForLogin(conn); await RefreshIsVerifiedGithubRequired(conn); } diff --git a/PluginBuilder/Controllers/PluginController.cs b/PluginBuilder/Controllers/PluginController.cs index 94730ef..57e581f 100644 --- a/PluginBuilder/Controllers/PluginController.cs +++ b/PluginBuilder/Controllers/PluginController.cs @@ -1,4 +1,3 @@ -using System.Text; using Dapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -22,7 +21,6 @@ public class PluginController( DBConnectionFactory connectionFactory, UserManager userManager, BuildService buildService, - GPGKeyService gpgKeyService, AzureStorageClient azureStorageClient, UserVerifiedLogic userVerifiedLogic, FirstBuildEvent firstBuildEvent, @@ -195,58 +193,32 @@ public class PluginController( [ModelBinder(typeof(PluginSlugModelBinder))] PluginSlug pluginSlug, [ModelBinder(typeof(PluginVersionModelBinder))] - PluginVersion version, string command, IFormFile? signatureFile) + PluginVersion version, string command) { await using var conn = await connectionFactory.Open(); - var pluginBuild = await conn.QueryFirstOrDefaultAsync<(long buildId, string identifier)>( + if (command == "remove") + { + var pluginBuild = await conn.QueryFirstOrDefaultAsync<(long buildId, string identifier)>( "SELECT v.build_id, p.identifier FROM versions v JOIN plugins p ON v.plugin_slug = p.slug WHERE plugin_slug=@pluginSlug AND ver=@version", new { pluginSlug = pluginSlug.ToString(), version = version.VersionParts }); - - var requireGPGSignature = await conn.RequiresGPGSignatureForPluginRelease(); - switch (command) - { - case "remove": - FullBuildId fullBuildId = new(pluginSlug, pluginBuild.buildId); - await conn.ExecuteAsync("DELETE FROM versions WHERE plugin_slug=@pluginSlug AND ver=@version", - new { pluginSlug = pluginSlug.ToString(), version = version.VersionParts }); - await buildService.UpdateBuild(fullBuildId, BuildStates.Removed, null); - return RedirectToAction(nameof(Build), new { pluginSlug = pluginSlug.ToString(), pluginBuild.buildId }); - - case "sign_release": - var build_info = await conn.QueryFirstOrDefaultAsync("SELECT build_info FROM builds b WHERE b.plugin_slug=@pluginSlug AND b.id=@buildId LIMIT 1", - new { pluginSlug = pluginSlug.ToString(), pluginBuild.buildId }); - - if (build_info is null || BuildInfo.Parse(build_info) is not { } buildInfo || string.IsNullOrEmpty(buildInfo.GitCommit)) - { - TempData[TempDataConstant.WarningMessage] = "Build information for plugin not available"; - return RedirectToAction(nameof(Version), new { pluginSlug = pluginSlug.ToString(), version = version.ToString() }); - } - - var rawSignedBytes = Encoding.UTF8.GetBytes(buildInfo.GitCommit); - var signatureVerification = await gpgKeyService.VerifyDetachedSignature(pluginSlug.ToString(), userManager.GetUserId(User)!, - rawSignedBytes, signatureFile); - - if (!signatureVerification.valid) - { - TempData[TempDataConstant.WarningMessage] = signatureVerification.message; - return RedirectToAction(nameof(Version), new { pluginSlug = pluginSlug.ToString(), version = version.ToString() }); - } - await conn.UpdateVersionReleaseStatus(pluginSlug, command, version, signatureVerification.proof); - break; - - default: - if (requireGPGSignature && command == "release") - { - TempData[TempDataConstant.WarningMessage] = "A verified GPG signature is required to release this version"; - return RedirectToAction(nameof(Version), new { pluginSlug = pluginSlug.ToString(), version = version.ToString() }); - } - await conn.UpdateVersionReleaseStatus(pluginSlug, command, version); - break; + FullBuildId fullBuildId = new(pluginSlug, pluginBuild.buildId); + await conn.ExecuteAsync("DELETE FROM versions WHERE plugin_slug=@pluginSlug AND ver=@version", + new { pluginSlug = pluginSlug.ToString(), version = version.VersionParts }); + await buildService.UpdateBuild(fullBuildId, BuildStates.Removed, null); + return RedirectToAction(nameof(Build), new { pluginSlug = pluginSlug.ToString(), pluginBuild.buildId }); } + // Email notifications are now handled on first build creation, not on release. + await conn.ExecuteAsync("UPDATE versions SET pre_release=@preRelease WHERE plugin_slug=@pluginSlug AND ver=@version", + new + { + pluginSlug = pluginSlug.ToString(), + version = version.VersionParts, + preRelease = command == "unrelease" + }); TempData[TempDataConstant.SuccessMessage] = - $"Version {version} {(command is "release" or "sign_release" ? "released" : "unreleased")}"; + $"Version {version} {(command == "release" ? "released" : "unreleased")}"; return RedirectToAction(nameof(Version), new { pluginSlug = pluginSlug.ToString(), version = version.ToString() }); } @@ -272,8 +244,8 @@ public class PluginController( await using var conn = await connectionFactory.Open(); var row = await conn - .QueryFirstOrDefaultAsync<(string manifest_info, string build_info, string state, DateTimeOffset created_at, bool published, bool pre_release, string signatureproof)>( - "SELECT manifest_info, build_info, state, created_at, v.ver IS NOT NULL, v.pre_release, v.signatureproof FROM builds b " + + .QueryFirstOrDefaultAsync<(string manifest_info, string build_info, string state, DateTimeOffset created_at, bool published, bool pre_release)>( + "SELECT manifest_info, build_info, state, created_at, v.ver IS NOT NULL, v.pre_release FROM builds b " + "LEFT JOIN versions v ON b.plugin_slug=v.plugin_slug AND b.id=v.build_id " + "WHERE b.plugin_slug=@pluginSlug AND id=@buildId " + "LIMIT 1", @@ -284,25 +256,21 @@ public class PluginController( "ORDER BY created_at;", new { pluginSlug = pluginSlug.ToString(), buildId }); var logs = string.Join("\r\n", logLines); - var signatureProof = string.IsNullOrWhiteSpace(row.signatureproof) - ? new SignatureProof() : JsonConvert.DeserializeObject(row.signatureproof, CamelCaseSerializerSettings.Instance) ?? new SignatureProof(); - BuildViewModel vm = new(); var buildInfo = row.build_info is null ? null : BuildInfo.Parse(row.build_info); var manifest = row.manifest_info is null ? null : PluginManifest.Parse(row.manifest_info); vm.FullBuildId = new FullBuildId(pluginSlug, buildId); - vm.ManifestInfo = NiceJson(row.manifest_info, signatureProof?.Fingerprint); + vm.ManifestInfo = NiceJson(row.manifest_info); vm.BuildInfo = buildInfo?.ToString(Formatting.Indented); vm.DownloadLink = buildInfo?.Url; vm.State = row.state; vm.CreatedDate = (DateTimeOffset.UtcNow - row.created_at).ToTimeAgo(); - vm.Commit = buildInfo?.GitCommit; + vm.Commit = buildInfo?.GitCommit?.Substring(0, 8); vm.Repository = buildInfo?.GitRepository; vm.GitRef = buildInfo?.GitRef; vm.Version = PluginVersionViewModel.CreateOrNull(manifest?.Version?.ToString(), row.published, row.pre_release, row.state, pluginSlug.ToString()); vm.RepositoryLink = GetUrl(buildInfo); vm.DownloadLink = buildInfo?.Url; - vm.RequireGPGSignatureForRelease = await conn.RequiresGPGSignatureForPluginRelease(); //vm.Error = buildInfo?.Error; vm.Published = row.published; //var buildId = await conn.NewBuild(pluginSlug); @@ -312,14 +280,12 @@ public class PluginController( return View(vm); } - private string? NiceJson(string? json, string? fingerprint = null) + private string? NiceJson(string? json) { if (json is null) return null; var data = JObject.Parse(json); data = new JObject(data.Properties().OrderBy(p => p.Name)); - if (!string.IsNullOrWhiteSpace(fingerprint)) - data["SignatureFingerprint"] = fingerprint; return data.ToString(Formatting.Indented); } diff --git a/PluginBuilder/Data/Scripts/14.SignatureProof.sql b/PluginBuilder/Data/Scripts/14.SignatureProof.sql deleted file mode 100644 index f427bd3..0000000 --- a/PluginBuilder/Data/Scripts/14.SignatureProof.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE versions -ADD COLUMN signatureproof JSONB NULL; diff --git a/PluginBuilder/DataModels/AccountSettings.cs b/PluginBuilder/DataModels/AccountSettings.cs index cefee99..5b693ac 100644 --- a/PluginBuilder/DataModels/AccountSettings.cs +++ b/PluginBuilder/DataModels/AccountSettings.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using PluginBuilder.ViewModels; namespace PluginBuilder.DataModels; @@ -17,5 +16,4 @@ public class AccountSettings [Display(Name = "Public Email address")] public string? Email { get; set; } public string? PendingNewEmail { get; set; } - public PgpKeyViewModel? GPGKey { get; set; } } diff --git a/PluginBuilder/DataModels/SettingsKeys.cs b/PluginBuilder/DataModels/SettingsKeys.cs index 3ffc719..8672a3e 100644 --- a/PluginBuilder/DataModels/SettingsKeys.cs +++ b/PluginBuilder/DataModels/SettingsKeys.cs @@ -5,7 +5,6 @@ public static class SettingsKeys { public const string EmailSettings = nameof(EmailSettings); public const string VerifiedEmailForPluginPublish = nameof(VerifiedEmailForPluginPublish); - public const string VerifiedGPGSignatureForPluginRelease = nameof(VerifiedGPGSignatureForPluginRelease); public const string VerifiedEmailForLogin = nameof(VerifiedEmailForLogin); public const string FirstPluginBuildReviewers = nameof(FirstPluginBuildReviewers); public const string VerifiedGithub = nameof(VerifiedGithub); diff --git a/PluginBuilder/PluginBuilder.csproj b/PluginBuilder/PluginBuilder.csproj index 20ddfd7..8dc7847 100644 --- a/PluginBuilder/PluginBuilder.csproj +++ b/PluginBuilder/PluginBuilder.csproj @@ -14,102 +14,102 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + true - + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -122,7 +122,7 @@ - - + + diff --git a/PluginBuilder/Program.cs b/PluginBuilder/Program.cs index ec53862..24956e6 100644 --- a/PluginBuilder/Program.cs +++ b/PluginBuilder/Program.cs @@ -174,7 +174,6 @@ public class Program services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddHttpClient(); services.AddSingleton(); services.AddSingleton(); diff --git a/PluginBuilder/Services/GPGKeyService.cs b/PluginBuilder/Services/GPGKeyService.cs deleted file mode 100644 index d8d4aef..0000000 --- a/PluginBuilder/Services/GPGKeyService.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Text; -using Newtonsoft.Json; -using Org.BouncyCastle.Bcpg; -using Org.BouncyCastle.Bcpg.OpenPgp; -using PluginBuilder.DataModels; -using PluginBuilder.Util; -using PluginBuilder.Util.Extensions; -using PluginBuilder.ViewModels; - -namespace PluginBuilder.Services; - -public class GPGKeyService(DBConnectionFactory connectionFactory) -{ - public bool ValidateArmouredPublicKey(string publicKey, out string message, out PgpKeyViewModel? vm) - { - publicKey = publicKey.Trim(); - vm = null; - if (publicKey.Contains("-----BEGIN PGP PRIVATE KEY BLOCK-----", StringComparison.OrdinalIgnoreCase)) - { - message = "Private key block detected; upload only the public key"; - return false; - } - try - { - using var publicKeyStream = new MemoryStream(Encoding.ASCII.GetBytes(publicKey)); - using var decoderStream = PgpUtilities.GetDecoderStream(publicKeyStream); - var keyRingBundle = new PgpPublicKeyRingBundle(decoderStream); - PgpPublicKey? key = null; - - foreach (PgpPublicKeyRing keyRing in keyRingBundle.GetKeyRings()) - { - foreach (PgpPublicKey k in keyRing.GetPublicKeys()) - { - if (k.GetSignatures().All(sig => ((sig.GetHashedSubPackets()?.GetKeyFlags()) & PgpKeyFlags.CanSign) == 0)) continue; - key = k; - break; - } - - if (key != null) - break; - } - - if (key == null) - { - message = "Signing key is required"; - return false; - } - - bool canSign = key.Algorithm switch - { - PublicKeyAlgorithmTag.RsaGeneral or - PublicKeyAlgorithmTag.RsaSign or - PublicKeyAlgorithmTag.Dsa or - PublicKeyAlgorithmTag.ECDsa or - PublicKeyAlgorithmTag.EdDsa => true, - _ => false - }; - if (!canSign) - { - message = "Public key provided does not support signing"; - return false; - } - - if (key.GetValidSeconds() != 0 && key.CreationTime.AddSeconds(key.GetValidSeconds()) <= DateTimeOffset.UtcNow) - { - message = "Key has expired"; - return false; - } - - if (key.IsRevoked() || key.GetSignatures().Any(sig => sig.SignatureType == PgpSignature.KeyRevocation)) - { - message = "Key is revoked"; - return false; - } - vm = new PgpKeyViewModel - { - KeyId = key.KeyId.ToString("X"), - Fingerprint = BitConverter.ToString(key.GetFingerprint()).Replace("-", ""), - PublicKey = publicKey, - CreatedDate = key.CreationTime, - AddedDate = DateTimeOffset.UtcNow, - ValidDays = key.GetValidSeconds() == 0 ? -1 : (long)(key.CreationTime.AddSeconds(key.GetValidSeconds()) - key.CreationTime).TotalDays, - Version = key.Version - }; - message = "Public Key validated successfully"; - return true; - } - catch - { - message = "An error occurred while validating public key"; - return false; - } - } - - public async Task VerifyDetachedSignature(string pluginslug, string userId, byte[] rawSignedBytes, IFormFile? signatureFile) - { - try - { - if (signatureFile is not { Length: > 0 }) - return new SignatureProofResponse(false, "Please upload a valid GPG signature file (.asc)"); - - string signatureText; - using (var reader = new StreamReader(signatureFile.OpenReadStream())) - signatureText = await reader.ReadToEndAsync(); - - var publicKey = await GetPluginOwnerPublicKeys(pluginslug, userId); - if (string.IsNullOrEmpty(publicKey)) - { - return new SignatureProofResponse(false, "No public keys found for this user. Kindly update your account profile with your GPG public key"); - } - await using Stream pubIn = new MemoryStream(Encoding.ASCII.GetBytes(publicKey)); - PgpPublicKeyRingBundle pubBundle = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(pubIn)); - - await using Stream sigIn = new MemoryStream(Encoding.ASCII.GetBytes(signatureText)); - PgpObjectFactory sigFact = new PgpObjectFactory(PgpUtilities.GetDecoderStream(sigIn)); - PgpSignatureList sigList = (PgpSignatureList)sigFact.NextPgpObject(); - - if (sigList.Count <= 0) - { - return new SignatureProofResponse(false, "No signature found in armoured file uploaded"); - } - - PgpSignature signature = sigList[0]; - PgpPublicKey signingKey = pubBundle.GetPublicKey(signature.KeyId); - if (signingKey == null) - { - return new SignatureProofResponse(false, "File was signed with a key not associated with the user's public key"); - } - signature.InitVerify(signingKey); - signature.Update(rawSignedBytes); - bool ok = signature.Verify(); - if (!ok) - { - return new SignatureProofResponse(false, "Unable to verify signature. Commit mismatch"); - } - var signatureProof = new SignatureProof - { - Armour = signatureText, - KeyId = signature.KeyId.ToString("X"), - Fingerprint = BitConverter.ToString(signingKey.GetFingerprint()).Replace("-", ""), - SignedAt = signature.CreationTime, - VerifiedAt = DateTimeOffset.UtcNow - }; - return new SignatureProofResponse(true, "Signature verified successfully", signatureProof); - } - catch (Exception ex) - { - return new SignatureProofResponse(false, $"Verification failed: {ex.Message}"); - } - } - - private async Task GetPluginOwnerPublicKeys(string pluginSlug, string userId) - { - await using var conn = await connectionFactory.Open(); - var pluginOwners = await conn.GetPluginOwners(pluginSlug); - if (pluginOwners == null || pluginOwners.Count != 0 == false) return string.Empty; - - var owner = pluginOwners.FirstOrDefault(o => o.UserId == userId); - if (owner == null || string.IsNullOrEmpty(owner.AccountDetail)) return string.Empty; - - var accountSettings = JsonConvert.DeserializeObject(owner.AccountDetail, CamelCaseSerializerSettings.Instance); - if (accountSettings?.GPGKey == null || string.IsNullOrEmpty(accountSettings.GPGKey.PublicKey)) return string.Empty; - - return accountSettings.GPGKey.PublicKey; - } -} diff --git a/PluginBuilder/Util/Extensions/NpgsqlConnectionExtensions.cs b/PluginBuilder/Util/Extensions/NpgsqlConnectionExtensions.cs index b04d092..b4b4efe 100644 --- a/PluginBuilder/Util/Extensions/NpgsqlConnectionExtensions.cs +++ b/PluginBuilder/Util/Extensions/NpgsqlConnectionExtensions.cs @@ -4,7 +4,6 @@ using Newtonsoft.Json.Linq; using Npgsql; using PluginBuilder.DataModels; using PluginBuilder.Services; -using PluginBuilder.ViewModels; using PluginBuilder.ViewModels.Plugin; namespace PluginBuilder.Util.Extensions; @@ -151,9 +150,8 @@ public static class NpgsqlConnectionExtensions const string sql = """ SELECT u."Id" AS "UserId", - up.is_primary_owner AS "IsPrimary", - u."Email", - u."AccountDetail" + u."Email" AS "Email", + up.is_primary_owner AS "IsPrimary" FROM users_plugins up JOIN "AspNetUsers" u ON u."Id" = up.user_id WHERE up.plugin_slug = @slug @@ -199,22 +197,6 @@ public static class NpgsqlConnectionExtensions }); } - public static async Task UpdateVersionReleaseStatus(this NpgsqlConnection connection, PluginSlug pluginSlug, string command, PluginVersion version, SignatureProof? signatureProof = null) - { - var updated = await connection.ExecuteAsync( - "UPDATE versions SET pre_release = @preRelease, signatureproof = CASE WHEN @hasSignature THEN @signatureproof::JSONB WHEN @preRelease THEN NULL ELSE signatureproof END " + - "WHERE plugin_slug = @pluginSlug AND ver = @version", - new - { - signatureproof = signatureProof != null ? JsonConvert.SerializeObject(signatureProof, CamelCaseSerializerSettings.Instance) : null, - hasSignature = signatureProof != null, - pluginSlug = pluginSlug.ToString(), - version = version.VersionParts, - preRelease = command == "unrelease" - }); - return updated == 1; - } - public static async Task GetPluginsByUserId(this NpgsqlConnection connection, string userId) { return (await connection.QueryAsync( @@ -381,12 +363,6 @@ public static class NpgsqlConnectionExtensions "INSERT INTO settings (key, value) VALUES (@key, @value)", new { key = SettingsKeys.VerifiedEmailForPluginPublish, value = "true" }); } - if (result.All(r => r.key != SettingsKeys.VerifiedGPGSignatureForPluginRelease)) - { - await connection.ExecuteAsync( - "INSERT INTO settings (key, value) VALUES (@key, @value)", - new { key = SettingsKeys.VerifiedGPGSignatureForPluginRelease, value = "false" }); - } if (result.All(r => r.key != SettingsKeys.VerifiedEmailForLogin)) { await connection.ExecuteAsync( @@ -408,17 +384,11 @@ public static class NpgsqlConnectionExtensions return bool.TryParse(settingValue, out var result) && result; } - public static async Task RequiresGPGSignatureForPluginRelease(this NpgsqlConnection connection) - { - var settingValue = await SettingsGetAsync(connection, SettingsKeys.VerifiedGPGSignatureForPluginRelease); - return bool.TryParse(settingValue, out var result) && result; - } - - public static async Task UpdatePluginAdminSettings(this NpgsqlConnection connection, string settingKey, bool newValue) + public static async Task UpdateVerifiedEmailForPluginPublishSetting(this NpgsqlConnection connection, bool newValue) { var stringValue = newValue.ToString().ToLowerInvariant(); await connection.ExecuteAsync("UPDATE settings SET value = @Value WHERE key = @Key", - new { Value = stringValue, Key = settingKey }); + new { Value = stringValue, Key = SettingsKeys.VerifiedEmailForPluginPublish }); } public static async Task GetVerifiedEmailForLoginSetting(this NpgsqlConnection connection) diff --git a/PluginBuilder/ViewModels/Admin/AdminPluginViewModel.cs b/PluginBuilder/ViewModels/Admin/AdminPluginViewModel.cs index 3303ab5..7ed5428 100644 --- a/PluginBuilder/ViewModels/Admin/AdminPluginViewModel.cs +++ b/PluginBuilder/ViewModels/Admin/AdminPluginViewModel.cs @@ -18,7 +18,6 @@ public class AdminPluginSettingViewModel : BasePagingViewModel { public IEnumerable Plugins { get; set; } = new List(); public bool VerifiedEmailForPluginPublish { get; set; } - public bool VerifiedGPGSignatureForPluginRelease { get; set; } public string SearchText { get; set; } = null!; public string? Status { get; set; } public override int CurrentPageCount => Plugins.Count(); diff --git a/PluginBuilder/ViewModels/BuildViewModel.cs b/PluginBuilder/ViewModels/BuildViewModel.cs index 0ca5ca4..5e6193e 100644 --- a/PluginBuilder/ViewModels/BuildViewModel.cs +++ b/PluginBuilder/ViewModels/BuildViewModel.cs @@ -18,5 +18,4 @@ public class BuildViewModel public string GitRef { get; internal set; } public string RepositoryLink { get; internal set; } public string Logs { get; set; } - public bool RequireGPGSignatureForRelease { get; set; } } diff --git a/PluginBuilder/ViewModels/PgpKeyViewModel.cs b/PluginBuilder/ViewModels/PgpKeyViewModel.cs deleted file mode 100644 index 49a4535..0000000 --- a/PluginBuilder/ViewModels/PgpKeyViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace PluginBuilder.ViewModels; - -public class PgpKeyViewModel -{ - public string? KeyId { get; set; } - public string? Fingerprint { get; set; } - public string? PublicKey { get; set; } - public DateTimeOffset CreatedDate { get; set; } - public DateTimeOffset AddedDate { get; set; } - public long ValidDays { get; set; } - public int Version { get; set; } -} - -public record SignatureProofResponse(bool valid, string message, SignatureProof? proof = null); - -public class SignatureProof -{ - public string Armour { get; init; } - public string KeyId { get; init; } - public string Fingerprint { get; init; } - public DateTime SignedAt { get; init; } - public DateTimeOffset VerifiedAt { get; init; } -} diff --git a/PluginBuilder/ViewModels/Plugin/PluginOwnersPageViewModel.cs b/PluginBuilder/ViewModels/Plugin/PluginOwnersPageViewModel.cs index bb1b677..7f36193 100644 --- a/PluginBuilder/ViewModels/Plugin/PluginOwnersPageViewModel.cs +++ b/PluginBuilder/ViewModels/Plugin/PluginOwnersPageViewModel.cs @@ -1,5 +1,3 @@ -using System.Text.Json; - namespace PluginBuilder.ViewModels.Plugin; public class PluginOwnersPageViewModel @@ -13,8 +11,6 @@ public class PluginOwnersPageViewModel public record OwnerVm( string UserId, - bool IsPrimary, string? Email, - string? AccountDetail + bool IsPrimary ); - diff --git a/PluginBuilder/Views/Account/AccountDetails.cshtml b/PluginBuilder/Views/Account/AccountDetails.cshtml index d0fc7b0..7a2142c 100644 --- a/PluginBuilder/Views/Account/AccountDetails.cshtml +++ b/PluginBuilder/Views/Account/AccountDetails.cshtml @@ -15,15 +15,13 @@ { Confirmed } -
- +
+ @if (Model.NeedToVerifyEmail) { - - Verify - + }
@@ -33,15 +31,13 @@ { Confirmed } -
+
@if (!Model.GithubAccountVerified) { - - Verify - + }
@@ -60,77 +56,9 @@ - -
- - @{ - DateTimeOffset? expiryDate = null; - if (Model.Settings.GPGKey?.ValidDays > 0) - { - expiryDate = Model.Settings.GPGKey.CreatedDate.AddDays(Model.Settings.GPGKey.ValidDays); - } - } - @if (!string.IsNullOrEmpty(Model.Settings.GPGKey?.Fingerprint) && (!expiryDate.HasValue || expiryDate > DateTimeOffset.UtcNow)) - { - Confirmed - } - - @if (expiryDate.HasValue) - { - if (expiryDate.Value < DateTimeOffset.UtcNow) - { -
- Your GPG key expired on @expiryDate.Value.ToString("yyyy-MM-dd"). -
- } - else if (expiryDate.Value <= DateTimeOffset.UtcNow.AddMonths(1)) - { -
- Your GPG key will expire on @expiryDate.Value.ToString("yyyy-MM-dd"). -
- } - } -
- @if (!string.IsNullOrEmpty(Model.Settings.GPGKey?.Fingerprint)) - { -
- - -
- - - } - else - { - - - } -
-
-
- - -@section FooterScripts { - -} diff --git a/PluginBuilder/Views/Admin/ListPlugins.cshtml b/PluginBuilder/Views/Admin/ListPlugins.cshtml index 4bc9e5d..47b9b69 100644 --- a/PluginBuilder/Views/Admin/ListPlugins.cshtml +++ b/PluginBuilder/Views/Admin/ListPlugins.cshtml @@ -9,17 +9,17 @@ - - - @if (Model.Version != null) { @@ -116,7 +74,7 @@ else { @("@") - @Model.Commit?.Substring(0, 8) + @Model.Commit } @@ -183,20 +141,4 @@ - - }