Compare commits

...

3 Commits

35 changed files with 467 additions and 171 deletions

7
.gitignore vendored

@ -6,14 +6,8 @@
/src/isn/.vscode/
/test/isnd.tests/obj/
/test/isnd.tests/bin/
/packages/
bower_components/
/test/isn.tests/bin
/test/isn.tests/obj/
appsettings.Testing.json
appsettings.Development.json
/wwwroot/.sass-cache/
/src/isnd/wwwroot/.sass-cache/
/src/isn.abst/bin
/src/isn.abst/obj
/src/isnd/packages/
@ -23,4 +17,5 @@ appsettings.Development.json
/artifacts/
/.vs/
/.vscode/
appsettings.Development.json

11
.vscode/tasks.json vendored

@ -90,20 +90,13 @@
],
"problemMatcher": "$msCompile"
},
{
"label": "copyTestConfig",
"command": "dotnet",
"type": "process",
"args": [ "build", "/t:CopyTestConfig" ],
"group": "test"
},
{
"label": "test",
"command": "dotnet",
"type": "process",
"options": {
"env": {
"ASPNETCORE_ENVIRONMENT": "Testing"
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"args": [
@ -112,7 +105,7 @@
"--logger:xunit"
],
"problemMatcher": "$msCompile",
"dependsOn": [ "build", "copyTestConfig"],
"dependsOn": [ "build"],
"group": "test"
},
{

@ -1,9 +1,9 @@
{
"dotnet": {
"enabled": false
"enabled": true
},
"msbuild": {
"enabled": true
"enabled": false
},
"Dnx": {
"enabled": false

@ -22,7 +22,7 @@ namespace isnd.Data.Catalog
[JsonProperty("@id")]
public string Id { get => GetId(); }
public string Id { get => id; }
private readonly string id;
public string GetId() { return id; }

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace isn
{
public interface IDataProtector
{
string Protect(string data);
string UnProtect(string data);
}
public class DefaultDataProtector : IDataProtector
{
private byte delta = 145;
public DefaultDataProtector()
{
}
public string Protect(string data)
{
List<Byte> protd = new List<byte>();
StringBuilder sb = new StringBuilder();
foreach (byte c in Encoding.UTF8.GetBytes(data))
{
protd.Add((byte) (c ^ delta));
}
return System.Convert.ToBase64String(protd.ToArray());
}
public string UnProtect(string data)
{
if (data==null) return null;
StringBuilder sb = new StringBuilder();
List<byte> unps = new List<byte>();
foreach (byte c in System.Convert.FromBase64CharArray(data.ToCharArray(),0,data.Length))
{
unps.Add((byte) (c ^ delta));
}
return Encoding.UTF8.GetString(unps.ToArray());
}
}
}

@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
namespace isn
{
public class SourceSettings
{
private RSA rsa;
/// <summary>
/// Protected API Key
/// </summary>
@ -22,19 +25,25 @@ namespace isn
/// <value></value>
public string Alias { get; set; }
public SourceSettings()
{
rsa = RSA.Create();
}
public string GetClearApiKey()
{
if (!string.IsNullOrEmpty(ApiKey)) return ApiKey;
return ProtectedApiKey = Protector.UnProtect(ApiKey);
return
Encoding.UTF8.GetString(
rsa.Decrypt(Encoding.UTF8.GetBytes(ProtectedApiKey),
RSAEncryptionPadding.Pkcs1));
}
public void SetApiKey(string key)
{
ApiKey = key;
ProtectedApiKey = Protector.Protect(key);
ApiKey = Encoding.UTF8.GetString(
rsa.Encrypt(Encoding.UTF8.GetBytes(key),
RSAEncryptionPadding.Pkcs1));
}
public static IDataProtector Protector { get; private set ; } = new DefaultDataProtector();
}
public class Settings

@ -13,6 +13,7 @@ using Microsoft.Extensions.Options;
using isnd.Data;
using isnd.Entities;
using isnd.Data.ApiKeys;
using isnd.Interfaces;
namespace isnd.Controllers
@ -23,17 +24,20 @@ namespace isnd.Controllers
private readonly ApplicationDbContext dbContext;
private readonly IsndSettings isndSettings;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IApiKeyProvider apiKeyProvider;
private readonly IDataProtector protector;
public ApiKeysController(ApplicationDbContext dbContext,
IOptions<IsndSettings> isndSettingsOptions,
IDataProtectionProvider provider,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
IApiKeyProvider apiKeyProvider
)
{
this.dbContext = dbContext;
this.isndSettings = isndSettingsOptions.Value;
protector = provider.CreateProtector(isndSettings.ProtectionTitle);
_userManager = userManager;
this.apiKeyProvider = apiKeyProvider;
}
[HttpGet]
@ -57,17 +61,17 @@ namespace isnd.Controllers
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
IQueryable<ApiKey> userKeys = GetUserKeys();
string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
IQueryable<ApiKey> userKeys = apiKeyProvider.GetUserKeys(User.Identity.Name);
if (userKeys.Count() >= isndSettings.MaxUserKeyCount)
{
ModelState.AddModelError(null, "Maximum key count reached");
return View();
}
ApiKey newKey = new ApiKey { UserId = userid, Name = model.Name,
CreationDate = DateTimeOffset.Now.ToUniversalTime() };
_ = dbContext.ApiKeys.Add(newKey);
_ = await dbContext.SaveChangesAsync();
model.UserId = userId;
ApiKey newKey = await apiKeyProvider.CreateApiKeyAsync(model);
return View("Details", new DetailModel { Name = newKey.Name,
ProtectedValue = protector.Protect(newKey.Id),
ApiKey = newKey });
@ -79,7 +83,6 @@ namespace isnd.Controllers
string userid = User.FindFirstValue(ClaimTypes.NameIdentifier);
ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
return View(new DeleteModel { ApiKey = key });
}
[HttpPost]
@ -103,7 +106,7 @@ namespace isnd.Controllers
ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
if (key == null)
{
ModelState.AddModelError(null, "Key not found");
ModelState.AddModelError("id", "Key not found");
return View();
}
return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)});

@ -7,6 +7,7 @@ using isn.abst;
namespace isnd.Controllers
{
// TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
public partial class PackagesController
{

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc;
using isnd.Entities;
using isn.abst;
using System.Threading.Tasks;
using isnd.Data.Catalog;
namespace isnd.Controllers
{
// TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
public partial class PackagesController
{
// Web get the paquet
[HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Search)]
[HttpHead("~" + Constants.ApiVersionPrefix + ApiConfig.Search)]
public async Task<IActionResult> Search(
string q=null,
int skip=0,
int take=25,
bool prerelease=false,
string semVerLevel = "2.0.0",
string packageType = null)
{
PackageRegistrationQuery query = new PackageRegistrationQuery
{
Prerelease= prerelease,
Query = q,
Skip = skip,
Take = take
};
var result = await packageManager.SearchPackageAsync(query);
return Ok(result);
}
}
}

@ -23,7 +23,7 @@ namespace isnd.Controllers
return View(new RegistrationPageIndexQueryAndResult
{
Query = model,
Result = pkgs.ToArray()
Result = pkgs.GetResults().Select(p => new PackageRegistration(apiBase, p)).ToArray()
});
}

@ -8,6 +8,7 @@ using isnd.Data;
using isnd.Entities;
using isnd.Interfaces;
using isn.Abstract;
using isn.abst;
namespace isnd.Controllers
{
@ -16,6 +17,7 @@ namespace isnd.Controllers
{
const int maxTake = 100;
private readonly Resource[] resources;
private readonly string apiBase;
private readonly ILogger<PackagesController> logger;
private readonly IDataProtector protector;
@ -36,6 +38,9 @@ namespace isnd.Controllers
this.dbContext = dbContext;
packageManager = pm;
resources = packageManager.GetResources().ToArray();
this.apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix;
}
}
}

@ -9,6 +9,8 @@ namespace isnd.Data.ApiKeys
[Display(Name = "Key Name")]
public string Name { get; set; }
public string UserId { get; set; }
public int ValidityPeriodInDays { get; set; }
}
}

@ -23,7 +23,7 @@ namespace isnd.Data.Catalog
this.apiBase = apiBase;
}
public CatalogPage(string bid, string pkgid, string apiBase, List<PackageVersion> versions) : this(pkgid, apiBase)
public CatalogPage(string pkgid, string apiBase, List<PackageVersion> versions) : this(pkgid, apiBase)
{
AddVersionRange(versions);
}
@ -54,6 +54,7 @@ namespace isnd.Data.Catalog
long commitMax = 0;
foreach (var p in vitems)
{
if (p.LatestCommit==null) continue;
var pkg = p.ToPackage(apiBase);
if (items.Contains(pkg)) continue;
@ -69,8 +70,8 @@ namespace isnd.Data.Catalog
}
items.Add(pkg);
}
Upper = upper.ToFullString();
Lower = lower.ToFullString();
Upper = upper?.ToFullString() ?? lower?.ToFullString();
Lower = lower?.ToFullString() ?? upper?.ToFullString();
}
/// <summary>

@ -1,4 +1,5 @@
using isnd.Data.Packages;
using isnd.Entities;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -14,11 +15,11 @@ namespace isnd.Data.Catalog
Items = new List<CatalogPage>();
}
public PackageRegistration(string bid, string apiBase, Packages.Package pkg) : base(bid + $"/{pkg.Id}/index.json")
public PackageRegistration(string apiBase, Packages.Package pkg) : base($"{apiBase}{ApiConfig.Registration}/{pkg.Id}/index.json")
{
Items = new List<CatalogPage>
{
new CatalogPage(bid, pkg.Id, apiBase, pkg.Versions)
new CatalogPage(pkg.Id, apiBase, pkg.Versions)
};
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace isnd.Data.Packages
{
public interface IPackage
{
string Id { get; set; }
string OwnerId { get; set; }
string Description { get; set; }
bool Public { get; set; }
ApplicationUser Owner { get; set; }
List<PackageVersion> Versions { get; set; }
long CommitNId { get; set; }
string CommitId { get; }
Commit LatestCommit { get; set; }
DateTimeOffset CommitTimeStamp { get; set; }
}
}

@ -2,24 +2,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using isnd.Interfaces;
using Newtonsoft.Json;
namespace isnd.Data.Packages
{
public interface IPackage
{
string Id { get; set; }
string OwnerId { get; set; }
string Description { get; set; }
bool Public { get; set; }
ApplicationUser Owner { get; set; }
List<PackageVersion> Versions { get; set; }
long CommitNId { get; set; }
string CommitId { get; }
Commit LatestCommit { get; set; }
DateTimeOffset CommitTimeStamp { get; set; }
}
public class Package
{
@ -60,6 +48,9 @@ namespace isnd.Data.Packages
public virtual Commit LatestCommit { get; set; }
internal string GetLatestVersion()
{
return Versions.Max(v => v.NugetVersion).ToFullString();
}
}
}

@ -10,6 +10,7 @@ using isnd.Entities;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NuGet.Versioning;
using System;
namespace isnd.Data
{
@ -72,6 +73,6 @@ namespace isnd.Data
return new Catalog.Package(apiBase, this.PackageId , FullString,
new Catalog.PackageDetails(this, apiBase, apiBase + ApiConfig.Registration + "/" + this.PackageId + "/" + FullString + ".json"));
}
public bool IsDeleted => LatestCommit.Action == PackageAction.DeletePackage;
public bool IsDeleted => LatestCommit?.Action == PackageAction.DeletePackage;
}
}

@ -0,0 +1,13 @@
using System.Linq;
using System.Threading.Tasks;
using isnd.Data.ApiKeys;
namespace isnd.Interfaces
{
public interface IApiKeyProvider
{
Task<ApiKey> CreateApiKeyAsync(CreateModel model);
IQueryable<ApiKey> GetUserKeys(string identityName);
}
}

@ -27,7 +27,7 @@ namespace isnd.Interfaces
Task<PackageRegistration> GetCatalogIndexAsync();
Task<PackageRegistration> GetPackageRegistrationIndexAsync(PackageRegistrationQuery query);
Task<IEnumerable<PackageRegistration>> SearchPackageAsync(PackageRegistrationQuery query);
Task<PackageSearchResult> SearchPackageAsync(PackageRegistrationQuery query);
}
}

@ -0,0 +1,48 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using isnd.Data;
using isnd.Data.ApiKeys;
using isnd.Entities;
using isnd.Interfaces;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace isnd;
public class ApiKeyProvider : IApiKeyProvider
{
private readonly IsndSettings isndSettings;
private readonly IDataProtector protector;
private readonly ApplicationDbContext dbContext;
public ApiKeyProvider(
ApplicationDbContext dbContext,
IDataProtectionProvider dataProtectionProvider, IOptions<IsndSettings> isndSettingsOptions)
{
this.dbContext = dbContext;
isndSettings = isndSettingsOptions.Value;
protector = dataProtectionProvider.CreateProtector(isndSettings.ProtectionTitle);
}
public async Task<ApiKey> CreateApiKeyAsync(CreateModel model)
{
var newKey = new ApiKey{
UserId = model.UserId,
CreationDate = DateTime.Now,
Name = model.Name,
ValidityPeriodInDays = model.ValidityPeriodInDays
};
_ = dbContext.ApiKeys.Add(newKey);
_ = await dbContext.SaveChangesAsync();
return newKey;
}
public IQueryable<ApiKey> GetUserKeys(string identityName)
{
return dbContext.ApiKeys.Include(k => k.User).Where(k => k.User.UserName == identityName);
}
}

@ -62,12 +62,11 @@ namespace isnd.Services
},
new Resource(apiBase + ApiConfig.Search,
"SearchQueryService/" + BASE_API_LEVEL)
"SearchQueryService/3.0.0-rc")
{
Comment = "Search Query service"
Comment = "Query endpoint of NuGet Search service (primary) used by RC clients"
},
new Resource(apiBase + ApiConfig.Registration,
"RegistrationsBaseUrl/Versioned")
{
@ -75,9 +74,6 @@ namespace isnd.Services
"This base URL includes SemVer 2.0.0 packages."
}
};
// TODO ? RegistrationsBaseUrl RegistrationsBaseUrl/v* Catalog
return res;
}
@ -215,9 +211,14 @@ namespace isnd.Services
(string pkgId, string semver = null, string pkgType = null)
{
return (await dbContext.PackageVersions
.Include(v => v.Package).Include(v => v.Package.Owner)
.Include(v => v.Package)
.Include(v => v.Package.LatestCommit)
.Include(v => v.Package.Owner)
.Include(v => v.LatestCommit)
.Where(v => v.PackageId == pkgId
&& v.FullString == semver).SingleOrDefaultAsync()).ToPackage(
&& v.FullString == semver
&& v.LatestCommit !=null
).SingleOrDefaultAsync()).ToPackage(
apiBase);
}
@ -241,6 +242,7 @@ namespace isnd.Services
return dbContext.PackageVersions
.Include(v => v.Package)
.Include(v => v.Package.Owner)
.Include(v => v.Package.LatestCommit)
.Include(v => v.LatestCommit)
.Where(v => v.PackageId == pkgId && semver == v.FullString
&& (pkgType == null || pkgType == v.Type)
@ -268,6 +270,7 @@ namespace isnd.Services
.Include(p => p.Versions)
.Include(p => p.Owner)
.Include(p => p.LatestCommit)
.Include(p => p.Versions[0].LatestCommit)
.SingleOrDefaultAsync(p => p.Id.ToLower() == query.Query);
if (scope==null) return null;
if (scope.Versions.Count==0) return null;
@ -275,31 +278,27 @@ namespace isnd.Services
foreach (var version in scope.Versions)
version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId);
return
new PackageRegistration(bid, apiBase, scope);
new PackageRegistration(apiBase, scope);
}
public async Task<IEnumerable<PackageRegistration>> SearchPackageAsync(PackageRegistrationQuery query)
public async Task<PackageSearchResult> SearchPackageAsync(PackageRegistrationQuery query)
{
string bid = $"{apiBase}{ApiConfig.Registration}";
if (query.Query == null) query.Query = "";
var scope = (await dbContext.Packages
if (string.IsNullOrWhiteSpace(query.Query))
query.Query="";
var scope = dbContext.Packages
.Include(p => p.Owner)
.Include(p => p.Versions)
.Include(p => p.LatestCommit)
.Include(p => p.LatestCommit.Versions)
.Where(p => p.Id.StartsWith(query.Query)
&& (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease)))
.OrderBy(p => p.CommitNId)
.Skip(query.Skip).Take(query.Take)
.ToListAsync()
);
scope.ForEach(pkg =>
pkg.Versions.ForEach (version =>
version.LatestCommit = dbContext.Commits.Single(c => c.Id == version.CommitNId))
);
&& p.LatestCommit != null
&& (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease))
&& p.Versions.Count()>0)
.OrderBy(p => p.CommitNId);
return scope.Select(p => new PackageRegistration(bid, apiBase, p));
return new PackageSearchResult(await scope.Skip(query.Skip).Take(query.Take)
.ToListAsync(), apiBase, scope.Count());
}
}
}

@ -16,9 +16,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using Microsoft.OpenApi.Models;
using System.IO;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.DataProtection;
namespace isnd
{
@ -76,9 +76,9 @@ namespace isnd
.AddTransient<IMailer, EmailSender>()
.AddTransient<IEmailSender, EmailSender>()
.AddTransient<IPackageManager, PackageManager>()
.AddTransient<IApiKeyProvider, ApiKeyProvider>()
.AddSingleton<IAuthorizationHandler, ValidApiKeyRequirementHandler>();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
@ -95,6 +95,7 @@ namespace isnd
s.SerializerSettings.ReferenceResolverProvider = () => new NSJWebApiReferenceResolver();
})
.AddXmlSerializerFormatters();
#if SWAGGER
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
@ -117,6 +118,8 @@ namespace isnd
var xmlFilename = $"{typeof(Startup).Assembly.GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
#endif
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -130,8 +133,10 @@ namespace isnd
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
#if SWAGGER
app.UseSwagger();
app.UseSwaggerUI();
#endif
}
else
{

@ -0,0 +1,60 @@
using System.ComponentModel.DataAnnotations;
using isnd.Data.Catalog;
using Newtonsoft.Json;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
namespace isnd.ViewModels
{
public class PackageHit : Permalink
{
public PackageHit(string id) : base(id, "Package")
{
}
/// <summary>
/// The full SemVer 2.0.0 version string of the package (could contain build metadata)
/// </summary>
/// <value></value>
[Required]
public string version { get; set; }
/// <summary>
/// All of the versions of the package matching the prerelease parameter
/// </summary>
/// <value></value>
public SearchVersionInfo[] versions { get; set; }
public string description { get; set; }
public string[] authors { get; set; }
public string iconUrl { get; set; }
public string licenseUrl { get; set; }
public string projectUrl { get; set; }
/// <summary>
/// The absolute URL to the associated registration index
/// </summary>
/// <value></value>
public string registration { get; set; }
public string summary { get; set; }
public string[] tags { get; set; }
public string title { get; set; }
/// <summary>
/// This value can be inferred by the sum of downloads in the versions array
/// </summary>
/// <value></value>
public int totalDownloads { get; set; }
/// <summary>
/// boolean indicating whether the package is verified
/// </summary>
/// <value></value>
public bool verified { get; set; }
/// <summary>
/// The package types defined by the package author (added in SearchQueryService/3.5.0)
/// </summary>
/// <value></value>
[Required]
public PackageType[] packageTypes { get; set; }
}
}

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text.Json.Serialization;
using isnd.Data.Packages;
using isnd.Entities;
namespace isnd.ViewModels
{
public class PackageSearchResult
{
public IEnumerable<Package> GetResults()
{
return result;
}
private IEnumerable<Package> result;
[JsonIgnore]
public string ApiBase{get; private set;}
public PackageSearchResult(IEnumerable<Package> result, string apiBase, int totalHit)
{
this.result = result;
this.ApiBase = apiBase;
data=result.Select(r => NewPackageHit(apiBase, r)).ToArray();
this.totalHits = totalHit;
}
private static PackageHit NewPackageHit(string apiBase, Package package)
{
string regId = $"{apiBase}{ApiConfig.Registration}/{package.Id}/index.json";
return new PackageHit(regId)
{
version = package.GetLatestVersion(),
description = package.Description,
title = package.Id,
versions = package.Versions.Select(v => new SearchVersionInfo(regId, v)).ToArray()
};
}
public PackageHit[] data { get; protected set; }
public int totalHits { get; set; }
}
}

@ -0,0 +1,21 @@
using isnd.Data.Catalog;
using Newtonsoft.Json;
namespace isnd.ViewModels
{
public class SearchVersionInfo: Permalink
{
public SearchVersionInfo(string id, Data.PackageVersion v) : base(id, "VersionInfo")
{
Version = v.FullString;
}
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("downloads")]
public long Downloads { get; set; }
}
}

@ -32,6 +32,5 @@
</dl>
</div>
<div>
@Html.ActionLink("Edit", "Edit", new { pkgid = Model.PackageId, version = Model.FullString }) |
<a asp-action="Index">Back to List</a>
</div>

@ -37,9 +37,8 @@
<button class="far fa-copy" style="float:right" onclick="navigator.clipboard.writeText($('#code').text());this.innerText='copied'" >copy</button>
<pre><code id="code" >&lt;PackageReference Include="@Model.pkgid" Version="@Model.latest.FullString" /&gt;</code></pre>
</div>
<div>
@Html.ActionLink("Edit", "Edit", new { pkgid = Model.pkgid, version = Model.latest.FullString }) |
@Html.ActionLink("Delete", "Delete", new { pkgid = Model.pkgid, version= Model.latest.FullString }) |
<div class="controls">
@Html.ActionLink("Delete", "Delete", new { pkgid = Model.pkgid, version= Model.latest.FullString }) |
<a asp-action="Index">Back to List</a>
</div>
</div>

@ -46,7 +46,7 @@
</div>
<footer class="border-top footer text-muted" style="position:bottom">
isn @SiteHelpers.SemVer &copy; 2021-2022 Paul Schneider - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
isn @SiteHelpers.SemVer &copy; 2021-2024 Paul Schneider - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</footer>
<!-- <script src="/lib/jquery/dist/jquery.slim.min.js" ></script>
<script src="/lib/popper/popper.min.js"></script>

@ -37,7 +37,6 @@
</PackageReference>
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.15" />
</ItemGroup>
<ItemGroup>

@ -12,20 +12,6 @@ namespace isn.tests
{
public class Tests
{
[Fact]
public void HaveADefaultDataProtector()
{
string pass = "a lame and big pass";
isn.IDataProtector _protector = new isn.DefaultDataProtector();
string protectedPassword = _protector.Protect(pass);
string unprotectedPassword = _protector.UnProtect(protectedPassword);
Console.WriteLine(protectedPassword);
Assert.Equal(pass, unprotectedPassword);
Assert.True(protectedPassword != null);
Assert.True(protectedPassword.Length > 0);
}
[Fact]
public async Task TestHttpClient()
{
@ -44,14 +30,13 @@ namespace isn.tests
public void TestPush()
{
Program.LoadConfig();
var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg"
+ Constants.PacketFileExtension });
var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg" });
}
[Fact]
public void GetServerResourcesUsingHttpClientAsyncTest()
{
var model = SourceHelpers.GetServerResources("Https://isn.pschneider.fr/v3/index");
var model = SourceHelpers.GetServerResources("Https://isn.pschneider.fr/v3/index.json");
Console.WriteLine(JsonConvert.SerializeObject(model));
Assert.NotNull(model.Resources);
var pub = model.Resources.FirstOrDefault((r) => r.Type.StartsWith("PackagePublish/"));

@ -47,11 +47,11 @@ namespace isnd.host.tests
{
var services = serviceScope.ServiceProvider;
var dbContext = services.GetRequiredService<ApplicationDbContext>();
var paul = dbContext.Users.FirstOrDefaultAsync
(u => u.Email == "paul@pschneider.fr").Result;
if (paul != null)
var tester = dbContext.Users.FirstOrDefaultAsync
(u => u.Id == server.TestingUser.Id).Result;
if (tester != null)
{
dbContext.Users.Remove(paul);
dbContext.Users.Remove(tester);
dbContext.SaveChanges();
}
}
@ -64,10 +64,10 @@ namespace isnd.host.tests
using (var serviceScope = server.Host.Services.CreateScope())
{
var isnSettings = serviceScope.ServiceProvider.GetService<IOptions<isnd.Entities.IsndSettings>>().Value;
string pkgSourceUrl = isnSettings.ExternalUrl + apiindex;
string pkgSourceUrl = isnSettings.ExternalUrl + apiindex + ".json";
ProcessStartInfo psi = new ProcessStartInfo("nuget");
psi.ArgumentList.Add("install");
psi.ArgumentList.Add("gitversion");
psi.ArgumentList.Add("isnd");
psi.ArgumentList.Add("-PreRelease");
psi.ArgumentList.Add("-Source");
psi.ArgumentList.Add(pkgSourceUrl);
@ -114,7 +114,7 @@ namespace isnd.host.tests
}
public string SPIIndexURI
{
get => server.Addresses.First() + "/v3/index";
get => server.Addresses.First(a => a.StartsWith("http:")) + "/v3/index.json";
}
[Fact]
@ -128,7 +128,7 @@ namespace isnd.host.tests
PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>();
IEnumerable<IPackageSearchMetadata> packages = await resource.GetMetadataAsync(
"isn.abst",
"isnd",
includePrerelease: true,
includeUnlisted: true,
cache,
@ -149,10 +149,12 @@ namespace isnd.host.tests
[Fact]
public async Task TestFindPackageAsync()
{
ILogger logger = NullLogger.Instance;
ILogger logger = new TestLogger();
CancellationToken cancellationToken = CancellationToken.None;
SourceRepository repository = Repository.Factory.GetCoreV3(SPIIndexURI);
repository.PackageSource.AllowInsecureConnections=true;
PackageSearchResource resource = await repository.GetResourceAsync<PackageSearchResource>();
SearchFilter searchFilter = new SearchFilter(includePrerelease: true);
@ -170,5 +172,51 @@ namespace isnd.host.tests
}
}
[Fact]
public async Task TestPackagePush()
{
var logger = new TestLogger();
SourceRepository repository = Repository.Factory.GetCoreV3(SPIIndexURI);
PackageUpdateResource pushRes = await repository.GetResourceAsync<PackageUpdateResource>();
SymbolPackageUpdateResourceV3 symbolPackageResource = await repository.GetResourceAsync<SymbolPackageUpdateResourceV3>();
await pushRes.Push(new List<string>{ "../../../../../src/isnd/bin/Release/isnd.1.1.4.nupkg" }, null,
5000, false, GetApiKey, GetSymbolsApiKey, false, false, symbolPackageResource, logger);
}
private string GetSymbolsApiKey(string apiUrl)
{
return GetApiKey(apiUrl);
}
private string GetApiKey(string apiUrl)
{
return server.ProtectedTestingApiKey;
}
}
internal class TestLogger : NuGet.Common.LoggerBase
{
public override void Log(ILogMessage message)
{
string msg = $"{message.Level}: {message.Message}";
switch (message.Level)
{
case LogLevel.Debug:
case LogLevel.Information:
case LogLevel.Verbose:
case LogLevel.Minimal:
case LogLevel.Warning:
case LogLevel.Error:
Debug.WriteLine(msg);
break;
}
}
public override Task LogAsync(ILogMessage message)
{
return Task.Run(()=>Log(message));
}
}
}

@ -1,10 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using isn;
using isnd.Data;
using isnd.Entities;
using isnd.Interfaces;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Identity;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Xunit;
namespace isnd.tests
@ -15,6 +26,13 @@ namespace isnd.tests
{
public IWebHost Host { get; private set;}
public List<string> Addresses { get; private set; } = new List<string>();
public Microsoft.Extensions.Logging.ILogger Logger { get; internal set; }
private IsndSettings siteSettings;
public IDataProtector DataProtector { get; private set; }
public string ProtectedTestingApiKey { get; internal set; }
public ApplicationUser TestingUser { get; private set; }
public WebServerFixture()
{
@ -29,25 +47,66 @@ namespace isnd.tests
public void SetupHost()
{
var webhostBuilder = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
var builder = WebHost.CreateDefaultBuilder(new string[0]);
// .UseContentRoot("../../../../../src/isnd")
.UseStartup(typeof(Startup))
builder.UseStartup(typeof(Startup))
.ConfigureAppConfiguration((builderContext, config) =>
{
config.AddJsonFile("appsettings.json", false);
config.AddJsonFile("appsettings.json", true);
config.AddJsonFile("appsettings.Development.json", false);
});
Host = webhostBuilder.Build();
Host = builder.Build();
var logFactory = Host.Services.GetRequiredService<ILoggerFactory>();
Logger = logFactory.CreateLogger<WebServerFixture>();
Host.Start(); //Starts listening on the configured addresses.
var server = Host.Services.GetRequiredService<IServer>();
var addressFeature = server.Features.Get<IServerAddressesFeature>();
foreach (var address in addressFeature.Addresses)
var addressFeatures = server.Features.Get<IServerAddressesFeature>();
foreach (var address in addressFeatures.Addresses)
{
Addresses.Add(address);
}
siteSettings = Host.Services.GetRequiredService<IOptions<IsndSettings>>().Value;
DataProtector = Host.Services.GetRequiredService<IDataProtectionProvider>()
.CreateProtector(siteSettings.ProtectionTitle);
var dbContext = Host.Services.GetRequiredService<ApplicationDbContext>();
string testingUserName = "Tester";
TestingUser = dbContext.Users.FirstOrDefault(u=>u.UserName==testingUserName);
if (TestingUser==null)
{
var userManager = Host.Services.GetRequiredService<UserManager<ApplicationUser>>();
TestingUser = new ApplicationUser
{
UserName=testingUserName
};
var result = userManager.CreateAsync(TestingUser).Result;
Assert.True(result.Succeeded);
TestingUser = dbContext.Users.FirstOrDefault(u=>u.UserName==testingUserName);
}
var testKey = dbContext.ApiKeys.FirstOrDefault(k=>k.UserId==TestingUser.Id);
if (testKey == null)
{
var keyProvider = Host.Services.GetService<IApiKeyProvider>();
var apiKeyQuery = new Data.ApiKeys.CreateModel
{
Name = "Testing Key",
UserId = TestingUser.Id,
ValidityPeriodInDays = 1
};
testKey = keyProvider.CreateApiKeyAsync(apiKeyQuery).Result;
}
ProtectedTestingApiKey = DataProtector.Protect(testKey.Id);
}
}
}

@ -14,7 +14,10 @@
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5005"
"Url": "http://127.0.0.1:5002"
},
"Https": {
"Url": "https://127.0.0.1:5003"
}
}
}

Loading…