Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Final Code, but not ready to merge] Credentials Table #1597

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/NuGetGallery.Core/Entities/Credential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

namespace NuGetGallery
{
public class Credential : IEntity
{
public Credential() { }

public Credential(string type, string value)
{
Type = type;
Value = value;
}

public int Key { get; set; }

[Required]
public int UserKey { get; set; }

[Required]
[StringLength(maximumLength: 64)]
public string Type { get; set; }

[StringLength(maximumLength: 256)]
public string Identifier { get; set; }

[Required]
[StringLength(maximumLength: 256)]
public string Value { get; set; }

public virtual User User { get; set; }
}
}
6 changes: 6 additions & 0 deletions src/NuGetGallery.Core/Entities/EntitiesContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public void DeleteOnCommit<T>(T entity) where T : class
#pragma warning disable 618 // TODO: remove Package.Authors completely once prodution services definitely no longer need it
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Credential>()
.HasKey(c => c.Key)
.HasRequired(c => c.User)
.WithMany(u => u.Credentials)
.HasForeignKey(c => c.UserKey);

modelBuilder.Entity<PackageLicenseReport>()
.HasKey(r => r.Key)
.HasMany(r => r.Licenses)
Expand Down
3 changes: 3 additions & 0 deletions src/NuGetGallery.Core/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public User(
{
HashedPassword = hashedPassword;
Messages = new HashSet<EmailMessage>();
Credentials = new List<Credential>();
Username = username;
}

Expand Down Expand Up @@ -59,6 +60,8 @@ public bool Confirmed

public DateTime? CreatedUtc { get; set; }

public virtual ICollection<Credential> Credentials { get; set; }

public void ConfirmEmailAddress()
{
if (String.IsNullOrEmpty(UnconfirmedEmailAddress))
Expand Down
1 change: 1 addition & 0 deletions src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Entities\Credential.cs" />
<Compile Include="Entities\CuratedFeed.cs" />
<Compile Include="Entities\CuratedPackage.cs" />
<Compile Include="Entities\EmailMessage.cs" />
Expand Down
4 changes: 4 additions & 0 deletions src/NuGetGallery/App_Start/ContainerBindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ public override void Load()
.To<EntityRepository<PackageStatistics>>()
.InRequestScope();

Bind<IEntityRepository<Credential>>()
.To<EntityRepository<Credential>>()
.InRequestScope();

Bind<ICuratedFeedService>()
.To<CuratedFeedService>()
.InRequestScope();
Expand Down
10 changes: 8 additions & 2 deletions src/NuGetGallery/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public static class Constants

public static readonly string ReturnUrlViewDataKey = "ReturnUrl";

public const string UrlValidationRegEx = @"(https?):\/\/[^ ""]+$";
public const string UrlValidationErrorMessage = "This doesn't appear to be a valid HTTP/HTTPS URL";

public static class ContentNames
{
public static readonly string FrontPageAnnouncement = "FrontPage-Announcement";
Expand All @@ -43,7 +46,10 @@ public static class ContentNames
public static readonly string PrivacyPolicy = "Privacy-Policy";
}

public const string UrlValidationRegEx = @"(https?):\/\/[^ ""]+$";
public const string UrlValidationErrorMessage = "This doesn't appear to be a valid HTTP/HTTPS URL";
public static class CredentialTypes
{
public static readonly string PasswordPbkdf2 = "password.pbkdf2";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credential type can be any string, but this sets a vague precedent for "[type].[subtype]". For example, if we decided to use a different password hashing algorithm (ROT13, for example), we'd use "password.rot13". Similarly, if we decide to rev API keys to be hashed, we'd use "apikey.v2"

public static readonly string ApiKeyV1 = "apikey.v1";
}
}
}
27 changes: 22 additions & 5 deletions src/NuGetGallery/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve
HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKey));
}

var user = UserService.FindByApiKey(parsedApiKey);
User user = GetUserByApiKey(apiKey);
if (user == null)
{
return new HttpStatusCodeWithBodyResult(
Expand Down Expand Up @@ -210,7 +210,7 @@ private async Task<ActionResult> CreatePackageInternal(string apiKey)
HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKey));
}

var user = UserService.FindByApiKey(parsedApiKey);
User user = GetUserByApiKey(apiKey);
if (user == null)
{
return new HttpStatusCodeWithBodyResult(
Expand Down Expand Up @@ -260,7 +260,7 @@ private async Task<ActionResult> CreatePackageInternal(string apiKey)
}
}

return new HttpStatusCodeResult(201);
return new HttpStatusCodeResult(HttpStatusCode.Created);
}

[HttpDelete]
Expand All @@ -275,7 +275,7 @@ public virtual ActionResult DeletePackage(string apiKey, string id, string versi
HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKey));
}

var user = UserService.FindByApiKey(parsedApiKey);
User user = GetUserByApiKey(apiKey);
if (user == null)
{
return new HttpStatusCodeWithBodyResult(
Expand Down Expand Up @@ -312,7 +312,7 @@ public virtual ActionResult PublishPackage(string apiKey, string id, string vers
HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKey));
}

var user = UserService.FindByApiKey(parsedApiKey);
User user = GetUserByApiKey(apiKey);
if (user == null)
{
return new HttpStatusCodeWithBodyResult(
Expand Down Expand Up @@ -447,6 +447,23 @@ public virtual async Task<ActionResult> GetStatsDownloads(int? count)
return new HttpStatusCodeResult(HttpStatusCode.NotFound);
}

private User GetUserByApiKey(string apiKey)
{
var cred = UserService.AuthenticateCredential(Constants.CredentialTypes.ApiKeyV1, apiKey.ToLowerInvariant());
User user;
if (cred == null)
{
#pragma warning disable 0618
user = UserService.FindByApiKey(Guid.Parse(apiKey));
#pragma warning restore 0618
}
else
{
user = cred.User;
}
return user;
}

private static void QuietlyLogException(Exception e)
{
try
Expand Down
2 changes: 1 addition & 1 deletion src/NuGetGallery/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public virtual ActionResult LogOn(SignInRequest request, string returnUrl)
{
ModelState.AddModelError(
String.Empty,
Strings.UserNotFound);
Strings.UsernameAndPasswordNotFound);

return View();
}
Expand Down
19 changes: 17 additions & 2 deletions src/NuGetGallery/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@ public virtual ActionResult Account()
{
var user = UserService.FindByUsername(CurrentUser.Identity.Name);
var curatedFeeds = CuratedFeedService.GetFeedsForManager(user.Key);
var apiCredential = user
.Credentials
.FirstOrDefault(c => c.Type == Constants.CredentialTypes.ApiKeyV1);
return View(
new AccountViewModel
{
ApiKey = user.ApiKey.ToString(),
ApiKey = apiCredential == null ?
user.ApiKey.ToString() :
apiCredential.Value,
CuratedFeeds = curatedFeeds.Select(cf => cf.Name)
});
}
Expand Down Expand Up @@ -193,7 +198,17 @@ public virtual ActionResult Packages()
[HttpPost]
public virtual ActionResult GenerateApiKey()
{
UserService.GenerateApiKey(CurrentUser.Identity.Name);
// Get the user
var user = UserService.FindByUsername(CurrentUser.Identity.Name);

// Generate an API Key
var apiKey = Guid.NewGuid();

// Set the existing API Key field
user.ApiKey = apiKey;

// Add/Replace the API Key credential, and save to the database
UserService.ReplaceCredential(user, CredentialBuilder.CreateV1ApiKey(apiKey));
return RedirectToAction(MVC.Users.Account());
}

Expand Down
33 changes: 33 additions & 0 deletions src/NuGetGallery/Infrastructure/CredentialBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace NuGetGallery
{
/// <summary>
/// Provides helper methods to generate credentials.
/// </summary>
public static class CredentialBuilder
{
public static Credential CreateV1ApiKey()
{
return CreateV1ApiKey(Guid.NewGuid());
}

public static Credential CreateV1ApiKey(Guid apiKey)
{
var value = apiKey
.ToString()
.ToLowerInvariant();
return new Credential(Constants.CredentialTypes.ApiKeyV1, value);
}

public static Credential CreatePbkdf2Password(string plaintextPassword)
{
return new Credential(
Constants.CredentialTypes.PasswordPbkdf2,
CryptographyService.GenerateSaltedHash(plaintextPassword, Constants.PBKDF2HashAlgorithmId));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions src/NuGetGallery/Migrations/201309172217450_CredentialsTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace NuGetGallery.Migrations
{
using System;
using System.Data.Entity.Migrations;

public partial class CredentialsTable : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Credentials",
c => new
{
Key = c.Int(nullable: false, identity: true),
UserKey = c.Int(nullable: false),
Type = c.String(nullable: false, maxLength: 64),
Identifier = c.String(maxLength: 256),
Value = c.String(nullable: false, maxLength: 256),
})
.PrimaryKey(t => t.Key)
.ForeignKey("dbo.Users", t => t.UserKey, cascadeDelete: true)
.Index(t => t.UserKey);

CreateIndex(
"dbo.Credentials",
new[] { "Type", "Value" },
unique: true,
name: "IX_Credentials_Type_Value");
}

public override void Down()
{
DropIndex("dbo.Credentials", new[] { "UserKey" });
DropIndex("dbo.Credentials", "IX_Credentials_Type_Value");
DropForeignKey("dbo.Credentials", "UserKey", "dbo.Users");
DropTable("dbo.Credentials");
}
}
}
Loading