From a6bc1356677280fa9660c60ada3ea2d03b576f1c Mon Sep 17 00:00:00 2001 From: anurse Date: Mon, 7 Oct 2013 15:02:37 -0700 Subject: [PATCH 01/18] Stashing Katana Auth work --- .../App_Start/AuthenticationModule.cs | 2 +- src/NuGetGallery/App_Start/OwinStartup.cs | 30 +++++++++++++++++++ .../Authentication/AuthenticateUserResult.cs | 13 ++++++++ .../Authentication/AuthenticationService.cs | 22 ++++++++++++++ src/NuGetGallery/NuGetGallery.csproj | 24 +++++++++++++++ src/NuGetGallery/RouteNames.cs | 1 + src/NuGetGallery/Web.config | 3 -- src/NuGetGallery/packages.config | 7 +++++ .../AuthenticationServiceFacts.cs | 17 +++++++++++ .../NuGetGallery.Facts.csproj | 1 + 10 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/NuGetGallery/App_Start/OwinStartup.cs create mode 100644 src/NuGetGallery/Authentication/AuthenticateUserResult.cs create mode 100644 src/NuGetGallery/Authentication/AuthenticationService.cs create mode 100644 tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs diff --git a/src/NuGetGallery/App_Start/AuthenticationModule.cs b/src/NuGetGallery/App_Start/AuthenticationModule.cs index 9def43507d..e79854112d 100644 --- a/src/NuGetGallery/App_Start/AuthenticationModule.cs +++ b/src/NuGetGallery/App_Start/AuthenticationModule.cs @@ -23,7 +23,7 @@ public void Dispose() public static void Start() { - DynamicModuleUtility.RegisterModule(typeof(AuthenticationModule)); + //DynamicModuleUtility.RegisterModule(typeof(AuthenticationModule)); } private void OnAuthenticateRequest(object sender, EventArgs e) diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs new file mode 100644 index 0000000000..6d9412f117 --- /dev/null +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Owin; +using Microsoft.Owin; +using Microsoft.Owin.Extensions; +using Microsoft.Owin.Diagnostics; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; + +[assembly: OwinStartup(typeof(NuGetGallery.OwinStartup))] + +namespace NuGetGallery +{ + public class OwinStartup + { + // This method is auto-detected by the OWIN pipeline. DO NOT RENAME IT! + public static void Configuration(IAppBuilder app) + { + app.UseCookieAuthentication(new CookieAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.Active, + CookieHttpOnly = true, + CookieName = System.Web.Security.FormsAuthentication.FormsCookieName, + LoginPath = "/users/account/logon" + }); + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs new file mode 100644 index 0000000000..d33134675f --- /dev/null +++ b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NuGetGallery.Authentication +{ + public class AuthenticateUserResult + { + public User User { get; private set; } + public IEnumerable Roles { get; private set; } + } +} diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs new file mode 100644 index 0000000000..dd2329be61 --- /dev/null +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NuGetGallery.Authentication +{ + public class AuthenticationService + { + public IEntitiesContext Entities { get; private set; } + + public AuthenticationService(IEntitiesContext entities) + { + Entities = entities; + } + + public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, string password) + { + + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index eb767dd37c..40c2e3dcc6 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -117,6 +117,9 @@ ..\..\packages\WindowsAzure.Caching.1.7.0.0\lib\net35-full\Microsoft.ApplicationServer.Caching.Core.dll + + ..\..\packages\Microsoft.AspNet.Identity.Core.1.0.0-rc1\lib\net45\Microsoft.AspNet.Identity.Core.dll + @@ -135,6 +138,21 @@ False ..\..\packages\Microsoft.Data.Services.Client.5.5.0\lib\net40\Microsoft.Data.Services.Client.dll + + ..\..\packages\Microsoft.Owin.2.0.0-rc1\lib\net45\Microsoft.Owin.dll + + + ..\..\packages\Microsoft.Owin.Diagnostics.2.0.0-rc1\lib\net40\Microsoft.Owin.Diagnostics.dll + + + ..\..\packages\Microsoft.Owin.Host.SystemWeb.2.0.0-rc1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\..\packages\Microsoft.Owin.Security.2.0.0-rc1\lib\net45\Microsoft.Owin.Security.dll + + + ..\..\packages\Microsoft.Owin.Security.Cookies.2.0.0-rc1\lib\net45\Microsoft.Owin.Security.Cookies.dll + ..\..\packages\WindowsAzure.Caching.1.7.0.0\lib\net35-full\Microsoft.Web.DistributedCache.dll @@ -186,6 +204,9 @@ ..\..\packages\ODataNullPropagationVisitor.0.5.4237.2641\lib\net40\ODataNullPropagationVisitor.dll + + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + False ..\..\packages\PoliteCaptcha.0.4.0.0\lib\net40\PoliteCaptcha.dll @@ -290,11 +311,14 @@ T4MVC.tt + + + diff --git a/src/NuGetGallery/RouteNames.cs b/src/NuGetGallery/RouteNames.cs index 299503d661..f167a9d523 100644 --- a/src/NuGetGallery/RouteNames.cs +++ b/src/NuGetGallery/RouteNames.cs @@ -48,5 +48,6 @@ public static class RouteName public const string LegacyRegister = "LegacyRegister"; public const string PackageEnableLicenseReport = "EnableLicenseReport"; public const string PackageDisableLicenseReport = "DisableLicenseReport"; + public const string OwinRoute = "OwinRoute"; } } diff --git a/src/NuGetGallery/Web.config b/src/NuGetGallery/Web.config index f8c2f11076..8e6b957e6e 100644 --- a/src/NuGetGallery/Web.config +++ b/src/NuGetGallery/Web.config @@ -162,9 +162,6 @@ - - - diff --git a/src/NuGetGallery/packages.config b/src/NuGetGallery/packages.config index 89ff48d77c..71cdf3b0fc 100644 --- a/src/NuGetGallery/packages.config +++ b/src/NuGetGallery/packages.config @@ -21,6 +21,7 @@ + @@ -31,6 +32,11 @@ + + + + + @@ -43,6 +49,7 @@ + diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs new file mode 100644 index 0000000000..763c613da2 --- /dev/null +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NuGetGallery.Authentication +{ + public class AuthenticationServiceFacts + { + public void GivenNoUserWithName_ItReturnsNoSuchUserResult() + { + // Arrange + var entities = SetupFakeEntities(); + } + } +} diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index d497bd9e13..4563cc78ab 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -195,6 +195,7 @@ Properties\CommonAssemblyInfo.cs + From 23c96a29271f8cc567dee347ded0b4136ebbc3cf Mon Sep 17 00:00:00 2001 From: anurse Date: Mon, 7 Oct 2013 15:58:41 -0700 Subject: [PATCH 02/18] Started a new authentication service to pull auth-related stuff out of UserService. Also, as a demonstration of a cleaner service layer. --- .../Authentication/AuthenticateUserResult.cs | 18 ++++++++++++++++-- .../Authentication/AuthenticationService.cs | 3 ++- .../AuthenticationServiceFacts.cs | 18 +++++++++++++++--- tests/NuGetGallery.Facts/Framework/Fakes.cs | 16 ++++++++++++++++ .../Framework/TestContainer.cs | 3 ++- .../Framework/UnitTestBindings.cs | 16 ++++++++++++++++ 6 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs index d33134675f..177fb22a91 100644 --- a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs +++ b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs @@ -7,7 +7,21 @@ namespace NuGetGallery.Authentication { public class AuthenticateUserResult { - public User User { get; private set; } - public IEnumerable Roles { get; private set; } + public AuthenticateUserResultStatus Status { get; private set; } + + private AuthenticateUserResult(AuthenticateUserResultStatus status) + { + Status = status; + } + + public static AuthenticateUserResult NoSuchUser() + { + return new AuthenticateUserResult(AuthenticateUserResultStatus.NoSuchUser); + } + } + + public enum AuthenticateUserResultStatus + { + NoSuchUser } } diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index dd2329be61..ecb5949bcf 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -16,7 +16,8 @@ public AuthenticationService(IEntitiesContext entities) public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, string password) { - + return AuthenticateUserResult.NoSuchUser(); + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 763c613da2..0bd15d480a 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -3,15 +3,27 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using NuGetGallery.Framework; +using Xunit; namespace NuGetGallery.Authentication { public class AuthenticationServiceFacts { - public void GivenNoUserWithName_ItReturnsNoSuchUserResult() + public class TheAuthenticateUserMethod : TestContainer { - // Arrange - var entities = SetupFakeEntities(); + [Fact] + public void GivenNoUserWithName_ItReturnsNoSuchUserResult() + { + // Arrange + var service = Get(); + + // Act + var result = service.AuthenticateUser("notARealUser", "password"); + + // Assert + Assert.Equal(AuthenticateUserResultStatus.NoSuchUser, result.Status); + } } } } diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 9612d12662..31d22d5081 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -1,11 +1,14 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Security.Principal; namespace NuGetGallery.Framework { public static class Fakes { + private static readonly MethodInfo SetMethod = typeof(IEntitiesContext).GetMethod("Set"); + public static readonly User User = new User("testUser"); public static readonly User Admin = new User("testAdmin"); public static readonly User Owner = new User("testPackageOwner") { EmailAddress = "confirmed@example.com" }; //package owners need confirmed email addresses, obviously. @@ -32,5 +35,18 @@ public static IIdentity ToIdentity(this User user) { return new GenericIdentity(user.Username); } + + internal static void ConfigureEntitiesContext(FakeEntitiesContext ctxt) + { + var fields = typeof(Fakes) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => typeof(IEntity).IsAssignableFrom(f.FieldType)); + foreach (var field in fields) + { + object set = SetMethod.MakeGenericMethod(field.FieldType).Invoke(ctxt, new object[0]); + var method = set.GetType().GetMethod("Add"); + method.Invoke(set, new object[] { field.GetValue(null) }); + } + } } } diff --git a/tests/NuGetGallery.Facts/Framework/TestContainer.cs b/tests/NuGetGallery.Facts/Framework/TestContainer.cs index 9ddfc64c16..36733c02ff 100644 --- a/tests/NuGetGallery.Facts/Framework/TestContainer.cs +++ b/tests/NuGetGallery.Facts/Framework/TestContainer.cs @@ -17,7 +17,8 @@ public class TestContainer : TestClass, IDisposable { public IKernel Kernel { get; private set; } - protected TestContainer() + public TestContainer() : this(UnitTestBindings.CreateContainer()) { } + protected TestContainer(IKernel kernel) { // Initialize the container Kernel = UnitTestBindings.CreateContainer(); diff --git a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs index 40ea7b23f9..a71469d549 100644 --- a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs +++ b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs @@ -25,6 +25,13 @@ internal static IKernel CreateContainer() return kernel; } + internal static IKernel CreateContainer() + { + var kernel = new TestKernel(new UnitTestBindings()); + kernel.Bind().ToSelf(); + return kernel; + } + public override void Load() { Bind() @@ -59,6 +66,15 @@ public override void Load() return mockService.Object; }) .InSingletonScope(); + + Bind() + .ToMethod(_ => + { + var ctxt = new FakeEntitiesContext(); + Fakes.ConfigureEntitiesContext(ctxt); + return ctxt; + }) + .InSingletonScope(); } private class TestKernel : MoqMockingKernel From f8b9e7824df5d29231b3e6b43484b4d28d86823b Mon Sep 17 00:00:00 2001 From: anurse Date: Mon, 7 Oct 2013 16:56:47 -0700 Subject: [PATCH 03/18] Implemented AuthenticationService.AuthenticateUser --- .../Authentication/AuthenticateUserResult.cs | 25 ++++---- .../Authentication/AuthenticationService.cs | 59 ++++++++++++++++++- .../Diagnostics/IDiagnosticsService.cs | 17 +++++- .../Diagnostics/NullDiagnosticsSource.cs | 4 ++ src/NuGetGallery/Services/ContentService.cs | 2 +- .../AuthenticationServiceFacts.cs | 29 ++++++++- tests/NuGetGallery.Facts/Framework/Fakes.cs | 8 ++- 7 files changed, 124 insertions(+), 20 deletions(-) diff --git a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs index 177fb22a91..f5f4ba100c 100644 --- a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs +++ b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs @@ -7,21 +7,26 @@ namespace NuGetGallery.Authentication { public class AuthenticateUserResult { - public AuthenticateUserResultStatus Status { get; private set; } + public static readonly AuthenticateUserResult Failed = new AuthenticateUserResult(); - private AuthenticateUserResult(AuthenticateUserResultStatus status) + public bool Success { get; private set; } + public User User { get; private set; } + + private AuthenticateUserResult() { - Status = status; + Success = false; + User = null; } - public static AuthenticateUserResult NoSuchUser() + public AuthenticateUserResult(User user) { - return new AuthenticateUserResult(AuthenticateUserResultStatus.NoSuchUser); + if (user == null) + { + throw new ArgumentNullException("user"); + } + + Success = true; + User = user; } } - - public enum AuthenticateUserResultStatus - { - NoSuchUser - } } diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index ecb5949bcf..5044baa605 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using NuGetGallery.Diagnostics; namespace NuGetGallery.Authentication { @@ -9,15 +10,67 @@ public class AuthenticationService { public IEntitiesContext Entities { get; private set; } - public AuthenticationService(IEntitiesContext entities) + private IDiagnosticsSource Trace { get; set; } + + public AuthenticationService(IEntitiesContext entities, IDiagnosticsService diagnostics) { Entities = entities; + Trace = diagnostics.SafeGetSource("AuthenticationService"); } public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, string password) { - return AuthenticateUserResult.NoSuchUser(); - throw new NotImplementedException(); + using (Trace.Activity("Authenticate:" + userNameOrEmail)) + { + var user = FindByUserName(userNameOrEmail); + + // Check if the user exists + if (user == null) + { + Trace.Information("No such user: " + userNameOrEmail); + return AuthenticateUserResult.Failed; + } + + // Validate the password + Credential matched; + if (!ValidatePasswordCredential(user.Credentials, password, out matched)) + { + Trace.Information("Password validation failed: " + userNameOrEmail); + return AuthenticateUserResult.Failed; + } + + // Return the result + Trace.Verbose("Successfully authenticated '" + user.Username + "' with '" + matched.Type + "' credential"); + return new AuthenticateUserResult(user); + } + } + + private User FindByUserName(string userNameOrEmail) + { + return Entities + .Set() + .SingleOrDefault(u => u.Username == userNameOrEmail); + } + + private static bool ValidatePasswordCredential(IEnumerable creds, string password, out Credential matched) + { + matched = creds.FirstOrDefault(c => ValidatePasswordCredential(c, password)); + return matched != null; + } + + private static readonly Dictionary> _validators = new Dictionary>(StringComparer.OrdinalIgnoreCase) { + { CredentialTypes.Password.Pbkdf2, (password, cred) => CryptographyService.ValidateSaltedHash(cred.Value, password, Constants.PBKDF2HashAlgorithmId) }, + { CredentialTypes.Password.Sha1, (password, cred) => CryptographyService.ValidateSaltedHash(cred.Value, password, Constants.Sha1HashAlgorithmId) } + }; + + private static bool ValidatePasswordCredential(Credential cred, string password) + { + Func validator; + if (!_validators.TryGetValue(cred.Type, out validator)) + { + return false; + } + return validator(password, cred); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Diagnostics/IDiagnosticsService.cs b/src/NuGetGallery/Diagnostics/IDiagnosticsService.cs index 554c428daa..2cb2a6a10a 100644 --- a/src/NuGetGallery/Diagnostics/IDiagnosticsService.cs +++ b/src/NuGetGallery/Diagnostics/IDiagnosticsService.cs @@ -1,4 +1,5 @@ -namespace NuGetGallery.Diagnostics +using System; +namespace NuGetGallery.Diagnostics { public interface IDiagnosticsService { @@ -14,7 +15,19 @@ public static class DiagnosticsServiceExtensions { public static IDiagnosticsSource SafeGetSource(this IDiagnosticsService self, string name) { - return self == null ? new NullDiagnosticsSource() : self.GetSource(name); + // Hyper-defensive code to get a diagnostics source when self could be null AND self.GetSource(name) could return null. + // Designed to support all kinds of mocking scenarios and basically just never fail :) + try + { + return self == null ? + NullDiagnosticsSource.Instance : + (self.GetSource(name) ?? NullDiagnosticsSource.Instance); + } + catch(Exception ex) + { + System.Diagnostics.Trace.WriteLine("Error getting trace source: " + ex.ToString()); + return NullDiagnosticsSource.Instance; + } } } } diff --git a/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs b/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs index f2f7c1d787..08af78befd 100644 --- a/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs +++ b/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs @@ -7,6 +7,10 @@ namespace NuGetGallery.Diagnostics { public class NullDiagnosticsSource : IDiagnosticsSource { + public static readonly NullDiagnosticsSource Instance = new NullDiagnosticsSource(); + + private NullDiagnosticsSource() { } + public void TraceEvent(System.Diagnostics.TraceEventType type, int id, string message, string member = null, string file = null, int line = 0) { // No-op! diff --git a/src/NuGetGallery/Services/ContentService.cs b/src/NuGetGallery/Services/ContentService.cs index 302cafee0d..23de4d1032 100644 --- a/src/NuGetGallery/Services/ContentService.cs +++ b/src/NuGetGallery/Services/ContentService.cs @@ -28,7 +28,7 @@ public class ContentService : IContentService protected ConcurrentDictionary ContentCache { get { return _contentCache; } } protected ContentService() { - Trace = new NullDiagnosticsSource(); + Trace = NullDiagnosticsSource.Instance; } public ContentService(IFileStorageService fileStorage, IDiagnosticsService diagnosticsService) diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 0bd15d480a..061c711037 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -22,7 +22,34 @@ public void GivenNoUserWithName_ItReturnsNoSuchUserResult() var result = service.AuthenticateUser("notARealUser", "password"); // Assert - Assert.Equal(AuthenticateUserResultStatus.NoSuchUser, result.Status); + Assert.False(result.Success); + } + + [Fact] + public void GivenUserNameDoesNotMatchPassword_ItReturnsNoSuchUserResult() + { + // Arrange + var service = Get(); + + // Act + var result = service.AuthenticateUser(Fakes.User.Username, "bogus password!!"); + + // Assert + Assert.False(result.Success); + } + + [Fact] + public void GivenUserNameWithMatchingPasswordCredential_ItReturnsAuthenticatedResult() + { + // Arrange + var service = Get(); + + // Act + var result = service.AuthenticateUser(Fakes.User.Username, Fakes.Password); + + // Assert + Assert.True(result.Success); + Assert.Same(Fakes.User, result.User); } } } diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 31d22d5081..d3657c31b3 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -9,9 +9,11 @@ public static class Fakes { private static readonly MethodInfo SetMethod = typeof(IEntitiesContext).GetMethod("Set"); - public static readonly User User = new User("testUser"); - public static readonly User Admin = new User("testAdmin"); - public static readonly User Owner = new User("testPackageOwner") { EmailAddress = "confirmed@example.com" }; //package owners need confirmed email addresses, obviously. + public static readonly string Password = "p@ssw0rd!"; + + public static readonly User User = new User("testUser") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) } }; + public static readonly User Admin = new User("testAdmin") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) } }; + public static readonly User Owner = new User("testPackageOwner") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) }, EmailAddress = "confirmed@example.com" }; //package owners need confirmed email addresses, obviously. public static readonly PackageRegistration Package = new PackageRegistration() { From b5db15038d705303291b5bb5280485ca6aed2724 Mon Sep 17 00:00:00 2001 From: anurse Date: Tue, 8 Oct 2013 16:33:10 -0700 Subject: [PATCH 04/18] Stashing auth rearch --- NuGetGallery.sln | 44 +--- src/NuGetGallery.Core/Entities/User.cs | 1 + .../Authentication/AuthenticateUserResult.cs | 32 --- .../Authentication/AuthenticatedUser.cs | 29 +++ .../Authentication/AuthenticationService.cs | 110 +++++++++- .../Authentication/AuthenticationTypes.cs | 12 ++ .../Authentication/UserSession.cs | 32 +++ src/NuGetGallery/ExtensionMethods.cs | 26 ++- src/NuGetGallery/NuGetGallery.csproj | 4 +- src/NuGetGallery/Strings.Designer.cs | 22 +- src/NuGetGallery/Strings.resx | 8 +- .../AuthenticationServiceFacts.cs | 196 +++++++++++++++++- tests/NuGetGallery.Facts/Framework/Fakes.cs | 60 ++++-- .../NuGetGallery.Facts.csproj | 19 ++ tests/NuGetGallery.Facts/packages.config | 5 + 15 files changed, 477 insertions(+), 123 deletions(-) delete mode 100644 src/NuGetGallery/Authentication/AuthenticateUserResult.cs create mode 100644 src/NuGetGallery/Authentication/AuthenticatedUser.cs create mode 100644 src/NuGetGallery/Authentication/AuthenticationTypes.cs create mode 100644 src/NuGetGallery/Authentication/UserSession.cs diff --git a/NuGetGallery.sln b/NuGetGallery.sln index cbf88d09bd..d9cb388b81 100644 --- a/NuGetGallery.sln +++ b/NuGetGallery.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.20827.3 +VisualStudioVersion = 12.0.21005.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{96E4AFF8-D3A1-4102-ADCF-05F186F916A9}" ProjectSection(SolutionItems) = preProject @@ -36,10 +36,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetGallery.FunctionalTest EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetGallery.FunctionalTests.Helpers", "tests\NuGetGallery.FunctionalTests.Helpers\NuGetGallery.FunctionalTests.Helpers.csproj", "{8FB56455-C688-44AE-95F1-48FFCB199BFE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetGallery.Monitoring", "src\NuGetGallery.Monitoring\NuGetGallery.Monitoring.csproj", "{DFF0089E-4918-4A12-992B-B9DD2C070B0F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetGallery.Monitoring.Azure", "src\NuGetGallery.Monitoring.Azure\NuGetGallery.Monitoring.Azure.csproj", "{6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -136,42 +132,6 @@ Global {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|x64.Build.0 = Release|Any CPU {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|x86.ActiveCfg = Release|Any CPU {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|x86.Build.0 = Release|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Debug|x64.ActiveCfg = Debug|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Debug|x64.Build.0 = Debug|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Debug|x86.ActiveCfg = Debug|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Debug|x86.Build.0 = Debug|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Release|x64.ActiveCfg = Release|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Release|x64.Build.0 = Release|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Release|x86.ActiveCfg = Release|Any CPU - {DFF0089E-4918-4A12-992B-B9DD2C070B0F}.Release|x86.Build.0 = Release|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Debug|x64.ActiveCfg = Debug|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Debug|x64.Build.0 = Debug|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Debug|x86.ActiveCfg = Debug|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Debug|x86.Build.0 = Debug|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Release|x64.ActiveCfg = Release|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Release|x64.Build.0 = Release|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Release|x86.ActiveCfg = Release|Any CPU - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB}.Release|x86.Build.0 = Release|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Debug|x86.ActiveCfg = Debug|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Release|Any CPU.Build.0 = Release|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0A6B1A52-4D26-4946-9DDD-416D01A1ADBF}.Release|x86.ActiveCfg = Release|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Debug|x86.ActiveCfg = Debug|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|Any CPU.Build.0 = Release|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {8FB56455-C688-44AE-95F1-48FFCB199BFE}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -184,7 +144,5 @@ Global {4405C24C-7F57-4826-831F-D5D7E139F02E} = {B9B19787-DCC0-489E-9173-36A32C6B6848} {DBECF66B-8F2F-4B32-9143-E243BAFF12DF} = {2ECA1159-9B9D-4D65-95AF-F14337FD3DA6} {F240D1BC-BBFB-4F22-9DF8-3FDE36BFD665} = {2ECA1159-9B9D-4D65-95AF-F14337FD3DA6} - {DFF0089E-4918-4A12-992B-B9DD2C070B0F} = {2ECA1159-9B9D-4D65-95AF-F14337FD3DA6} - {6569F5ED-1DB6-433C-8C7F-E96C7B8E54BB} = {2ECA1159-9B9D-4D65-95AF-F14337FD3DA6} EndGlobalSection EndGlobal diff --git a/src/NuGetGallery.Core/Entities/User.cs b/src/NuGetGallery.Core/Entities/User.cs index d57613fbb9..0dce5df133 100644 --- a/src/NuGetGallery.Core/Entities/User.cs +++ b/src/NuGetGallery.Core/Entities/User.cs @@ -13,6 +13,7 @@ public User() : this(null) public User(string username) { Credentials = new List(); + Roles = new List(); Username = username; } diff --git a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs b/src/NuGetGallery/Authentication/AuthenticateUserResult.cs deleted file mode 100644 index f5f4ba100c..0000000000 --- a/src/NuGetGallery/Authentication/AuthenticateUserResult.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NuGetGallery.Authentication -{ - public class AuthenticateUserResult - { - public static readonly AuthenticateUserResult Failed = new AuthenticateUserResult(); - - public bool Success { get; private set; } - public User User { get; private set; } - - private AuthenticateUserResult() - { - Success = false; - User = null; - } - - public AuthenticateUserResult(User user) - { - if (user == null) - { - throw new ArgumentNullException("user"); - } - - Success = true; - User = user; - } - } -} diff --git a/src/NuGetGallery/Authentication/AuthenticatedUser.cs b/src/NuGetGallery/Authentication/AuthenticatedUser.cs new file mode 100644 index 0000000000..4930d3e144 --- /dev/null +++ b/src/NuGetGallery/Authentication/AuthenticatedUser.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NuGetGallery.Authentication +{ + public class AuthenticatedUser + { + public User User { get; private set; } + public Credential CredentialUsed { get; private set; } + + public AuthenticatedUser(User user, Credential cred) + { + if (user == null) + { + throw new ArgumentNullException("user"); + } + + if (cred == null) + { + throw new ArgumentNullException("cred"); + } + + User = user; + CredentialUsed = cred; + } + } +} diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index 5044baa605..59e4658e10 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -1,24 +1,30 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Web; using NuGetGallery.Diagnostics; +using System.Data.Entity; +using System.Globalization; +using Microsoft.Owin; +using System.Security.Claims; namespace NuGetGallery.Authentication { public class AuthenticationService { public IEntitiesContext Entities { get; private set; } - + public IOwinContext Context { get; private set; } private IDiagnosticsSource Trace { get; set; } - public AuthenticationService(IEntitiesContext entities, IDiagnosticsService diagnostics) + public AuthenticationService(IEntitiesContext entities, IOwinContext context, IDiagnosticsService diagnostics) { Entities = entities; + Context = context; Trace = diagnostics.SafeGetSource("AuthenticationService"); } - public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, string password) + public virtual AuthenticatedUser Authenticate(string userNameOrEmail, string password) { using (Trace.Activity("Authenticate:" + userNameOrEmail)) { @@ -28,7 +34,7 @@ public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, s if (user == null) { Trace.Information("No such user: " + userNameOrEmail); - return AuthenticateUserResult.Failed; + return null; } // Validate the password @@ -36,12 +42,102 @@ public virtual AuthenticateUserResult AuthenticateUser(string userNameOrEmail, s if (!ValidatePasswordCredential(user.Credentials, password, out matched)) { Trace.Information("Password validation failed: " + userNameOrEmail); - return AuthenticateUserResult.Failed; + return null; } // Return the result Trace.Verbose("Successfully authenticated '" + user.Username + "' with '" + matched.Type + "' credential"); - return new AuthenticateUserResult(user); + return new AuthenticatedUser(user, matched); + } + } + + public virtual AuthenticatedUser Authenticate(Credential credential) + { + if (credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) + { + // Password credentials cannot be used this way. + throw new ArgumentException(Strings.PasswordCredentialsCannotBeUsedHere, "credential"); + } + + using (Trace.Activity("Authenticate Credential: " + credential.Type)) + { + var matched = FindMatchingCredential(credential); + + if (matched == null) + { + Trace.Information("No user matches credential of type: " + credential.Type); + return null; + } + + Trace.Verbose("Successfully authenticated '" + matched.User.Username + "' with '" + matched.Type + "' credential"); + return new AuthenticatedUser(matched.User, matched); + } + } + + public virtual UserSession GetActiveSession() + { + var principal = Context.Authentication.User; + if (principal == null) + { + return null; + } + + return new UserSession(principal); + } + + public virtual UserSession CreateSession(AuthenticatedUser user) + { + // Create a claims identity for the session + ClaimsIdentity identity = CreateIdentity(user); + + Context.Authentication.SignIn(identity); + return new UserSession(new ClaimsPrincipal(identity)); + } + + public static ClaimsIdentity CreateIdentity(AuthenticatedUser user) + { + return CreateIdentity(user.User, user.CredentialUsed.Type); + } + + public static ClaimsIdentity CreateIdentity(User user, string authenticationType) + { + ClaimsIdentity identity = new ClaimsIdentity( + claims: Enumerable.Concat(new[] { + new Claim(ClaimsIdentity.DefaultNameClaimType, user.Username) + }, user.Roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r.Name))), + authenticationType: authenticationType, + nameType: ClaimsIdentity.DefaultNameClaimType, + roleType: ClaimsIdentity.DefaultRoleClaimType); + return identity; + } + + private Credential FindMatchingCredential(Credential credential) + { + var results = Entities + .Set() + .Include(u => u.User) + .Include(u => u.User.Roles) + .Where(c => c.Type == credential.Type && c.Value == credential.Value) + .ToList(); + + if (results.Count == 0) + { + return null; + } + else if (results.Count == 1) + { + return results[0]; + } + else + { + // Don't put the credential itself in trace, but do put the Key for lookup later. + string message = String.Format( + CultureInfo.CurrentCulture, + Strings.MultipleMatchingCredentials, + credential.Type, + results.First().Key); + Trace.Error(message); + throw new InvalidOperationException(message); } } @@ -49,6 +145,8 @@ private User FindByUserName(string userNameOrEmail) { return Entities .Set() + .Include(u => u.Credentials) + .Include(u => u.Roles) .SingleOrDefault(u => u.Username == userNameOrEmail); } diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs new file mode 100644 index 0000000000..47e451be45 --- /dev/null +++ b/src/NuGetGallery/Authentication/AuthenticationTypes.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NuGetGallery.Authentication +{ + public class AuthenticationTypes + { + public static readonly string Session = "Session"; + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/UserSession.cs b/src/NuGetGallery/Authentication/UserSession.cs new file mode 100644 index 0000000000..96eb852965 --- /dev/null +++ b/src/NuGetGallery/Authentication/UserSession.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Text; + +namespace NuGetGallery.Authentication +{ + public class UserSession : IPrincipal + { + public ClaimsPrincipal Principal { get; private set; } + + public string Username { get { return Principal.Identity.Name; } } + public string AuthenticationType { get { return Principal.Identity.AuthenticationType; } } + + public IIdentity Identity + { + get { return Principal.Identity; } + } + + public UserSession(ClaimsPrincipal principal) + { + Principal = principal; + } + + public bool IsInRole(string role) + { + return Principal.IsInRole(role); + } + } +} diff --git a/src/NuGetGallery/ExtensionMethods.cs b/src/NuGetGallery/ExtensionMethods.cs index b34268c4a0..697d01a870 100644 --- a/src/NuGetGallery/ExtensionMethods.cs +++ b/src/NuGetGallery/ExtensionMethods.cs @@ -8,6 +8,7 @@ using System.Net.Mail; using System.Runtime.Versioning; using System.Security; +using System.Security.Claims; using System.Security.Principal; using System.ServiceModel.Activation; using System.Text; @@ -15,6 +16,7 @@ using System.Web.Routing; using System.Web.WebPages; using NuGet; +using NuGetGallery.Authentication; namespace NuGetGallery { @@ -40,6 +42,16 @@ public static void AddOrSet(this ConcurrentDictionary val); } + public static UserSession AsUserSession(this IPrincipal self) + { + if (self == null) + { + return null; + } + // Direct cast because a non-ClaimsPrincipal is an error here. + return new UserSession((ClaimsPrincipal)self); + } + public static SecureString ToSecureString(this string str) { SecureString output = new SecureString(); @@ -175,25 +187,17 @@ public static bool IsOwner(this Package package, IPrincipal user) return package.PackageRegistration.IsOwner(user); } - public static bool IsOwner(this Package package, User user) + public static bool IsOwner(this Package package, UserSession user) { return package.PackageRegistration.IsOwner(user); } public static bool IsOwner(this PackageRegistration package, IPrincipal user) { - if (package == null) - { - throw new ArgumentNullException("package"); - } - if (user == null || user.Identity == null) - { - return false; - } - return user.IsAdministrator() || package.Owners.Any(u => u.Username == user.Identity.Name); + return IsOwner(package, user.AsUserSession()); } - public static bool IsOwner(this PackageRegistration package, User user) + public static bool IsOwner(this PackageRegistration package, UserSession user) { if (package == null) { diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 40c2e3dcc6..1cb78f6bc7 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -317,8 +317,10 @@ - + + + diff --git a/src/NuGetGallery/Strings.Designer.cs b/src/NuGetGallery/Strings.Designer.cs index c3627b7e59..4d45c422cc 100644 --- a/src/NuGetGallery/Strings.Designer.cs +++ b/src/NuGetGallery/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18052 +// Runtime Version:4.0.30319.34003 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -123,6 +123,15 @@ public static string InvalidApiKey { } } + /// + /// Looks up a localized string similar to Multiple Credentials match '{0}' credential with Key {1}. + /// + public static string MultipleMatchingCredentials { + get { + return ResourceManager.GetString("MultipleMatchingCredentials", resourceCulture); + } + } + /// /// Looks up a localized string similar to A nuget package's {0} property may not be more than {1} characters long.. /// @@ -186,6 +195,15 @@ public static string ParameterCannotBeNullOrEmpty { } } + /// + /// Looks up a localized string similar to Password credentials cannot be used with Authenticate(Credential). Use Authenticate(string, string) instead.. + /// + public static string PasswordCredentialsCannotBeUsedHere { + get { + return ResourceManager.GetString("PasswordCredentialsCannotBeUsedHere", resourceCulture); + } + } + /// /// Looks up a localized string similar to The requested resource can only be accessed via SSL.. /// @@ -232,7 +250,7 @@ public static string UserIsNotYetConfirmed { } /// - /// Looks up a localized string similar to A user with the provided user name and password does not exist. Try logging on with your username if you were using an email address to log on.. + /// Looks up a localized string similar to A unique user with that username or email address and password does not exist. Try logging on with your username if you were using an email address to log on.. /// public static string UsernameAndPasswordNotFound { get { diff --git a/src/NuGetGallery/Strings.resx b/src/NuGetGallery/Strings.resx index 1a78062a04..eb3b6851fc 100644 --- a/src/NuGetGallery/Strings.resx +++ b/src/NuGetGallery/Strings.resx @@ -183,4 +183,10 @@ You must confirm the email address for the account in order to use the API key. - + + Multiple Credentials match '{0}' credential with Key {1} + + + Password credentials cannot be used with Authenticate(Credential). Use Authenticate(string, string) instead. + + \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 061c711037..a108696db1 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Moq; using NuGetGallery.Framework; using Xunit; @@ -10,47 +15,216 @@ namespace NuGetGallery.Authentication { public class AuthenticationServiceFacts { - public class TheAuthenticateUserMethod : TestContainer + public class TheAuthenticateMethod : TestContainer { [Fact] - public void GivenNoUserWithName_ItReturnsNoSuchUserResult() + public void GivenNoUserWithName_ItReturnsNull() { // Arrange var service = Get(); // Act - var result = service.AuthenticateUser("notARealUser", "password"); + var result = service.Authenticate("notARealUser", "password"); // Assert - Assert.False(result.Success); + Assert.Null(result); } [Fact] - public void GivenUserNameDoesNotMatchPassword_ItReturnsNoSuchUserResult() + public void GivenUserNameDoesNotMatchPassword_ItReturnsNull() { // Arrange var service = Get(); // Act - var result = service.AuthenticateUser(Fakes.User.Username, "bogus password!!"); + var result = service.Authenticate(Fakes.User.Username, "bogus password!!"); // Assert - Assert.False(result.Success); + Assert.Null(result); } [Fact] - public void GivenUserNameWithMatchingPasswordCredential_ItReturnsAuthenticatedResult() + public void GivenUserNameWithMatchingPasswordCredential_ItReturnsAuthenticatedUser() { // Arrange var service = Get(); // Act - var result = service.AuthenticateUser(Fakes.User.Username, Fakes.Password); + var result = service.Authenticate(Fakes.User.Username, Fakes.Password); // Assert - Assert.True(result.Success); + var expectedCred = Fakes.User.Credentials.SingleOrDefault( + c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(result); Assert.Same(Fakes.User, result.User); + Assert.Same(expectedCred, result.CredentialUsed); + } + + // We don't normally test exception conditions, but it's really important that + // this overload is NOT used for Passwords since every call to generate a Password Credential + // uses a new Salt and thus produces a value that cannot be looked up in the DB. Instead, + // we must look up the user and then verify the salted password hash. + [Fact] + public void GivenPasswordCredential_ItThrowsArgumentException() + { + // Arrange + var service = Get(); + var cred = CredentialBuilder.CreatePbkdf2Password("bogus"); + + // Act + var ex = Assert.Throws(() => + service.Authenticate(cred)); + + // Assert + Assert.Equal(Strings.PasswordCredentialsCannotBeUsedHere + Environment.NewLine + "Parameter name: credential", ex.Message); + Assert.Equal("credential", ex.ParamName); + } + + [Fact] + public void GivenInvalidApiKeyCredential_ItReturnsNull() + { + // Arrange + var service = Get(); + + // Act + var result = service.Authenticate(CredentialBuilder.CreateV1ApiKey()); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GivenMatchingApiKeyCredential_ItReturnsTheUserAndMatchingCredential() + { + // Arrange + var service = Get(); + var cred = Fakes.User.Credentials.Single( + c => String.Equals(c.Type, CredentialTypes.ApiKeyV1, StringComparison.OrdinalIgnoreCase)); + + // Act + // Create a new credential to verify that it's a value-based lookup! + var result = service.Authenticate(CredentialBuilder.CreateV1ApiKey(Guid.Parse(cred.Value))); + + // Assert + Assert.NotNull(result); + Assert.Same(Fakes.User, result.User); + Assert.Same(cred, result.CredentialUsed); + } + + [Fact] + public void GivenMultipleMatchingCredentials_ItThrows() + { + // Arrange + var service = Get(); + var entities = Get(); + var cred = CredentialBuilder.CreateV1ApiKey(); + cred.Key = 42; + var creds = entities.Set(); + creds.Add(cred); + creds.Add(CredentialBuilder.CreateV1ApiKey(Guid.Parse(cred.Value))); + + // Act + var ex = Assert.Throws(() => service.Authenticate(CredentialBuilder.CreateV1ApiKey(Guid.Parse(cred.Value)))); + + // Assert + Assert.Equal(String.Format( + CultureInfo.CurrentCulture, + Strings.MultipleMatchingCredentials, + cred.Type, + cred.Key), ex.Message); + } + } + + public class TheGetActiveSessionMethod : TestContainer + { + [Fact] + public void GivenNoActiveSession_ItReturnsNull() + { + // Arrange + var service = Get(); + GetMock() + .Setup(c => c.Authentication.User) + .ReturnsNull(); + + // Act + var result = service.GetActiveSession(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GivenAValidSession_ItReturnsMatchingUserSessionObject() + { + // Arrange + var service = Get(); + var principal = Fakes.Admin.ToPrincipal(); + GetMock() + .Setup(c => c.Authentication.User) + .Returns(principal); + + // Act + var result = service.GetActiveSession(); + + // Assert + Assert.NotNull(result); + Assert.Equal(Fakes.Admin.Username, result.Username); + Assert.Equal(AuthenticationTypes.Session, result.AuthenticationType); + Assert.Same(principal, result.Principal); + Assert.True(result.IsInRole(Constants.AdminRoleName)); + } + } + + public class TheCreateSessionMethod : TestContainer + { + [Fact] + public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() + { + // Arrange + var service = Get(); + ClaimsIdentity id = null; + GetMock() + .Setup(c => c.Authentication.SignIn(It.IsAny())) + .Callback(ids => id = ids.SingleOrDefault()); + + var passwordCred = Fakes.Admin.Credentials.SingleOrDefault( + c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); + + var user = new AuthenticatedUser(Fakes.Admin, passwordCred); + + // Act + service.CreateSession(user); + + // Assert + Assert.NotNull(id); + var principal = new ClaimsPrincipal(id); + Assert.Equal(Fakes.Admin.Username, id.Name); + Assert.Equal(passwordCred.Type, id.AuthenticationType); + Assert.True(principal.IsInRole(Constants.AdminRoleName)); + } + + [Fact] + public void GivenAUser_ItReturnsTheCreatedUserSession() + { + // Arrange + var service = Get(); + GetMock() + .Setup(c => c.Authentication.SignIn(It.IsAny())); + + var passwordCred = Fakes.Admin.Credentials.SingleOrDefault( + c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); + + var user = new AuthenticatedUser(Fakes.Admin, passwordCred); + + // Act + var session = service.CreateSession(user); + + // Assert + Assert.NotNull(session); + Assert.Equal(Fakes.Admin.Username, session.Username); + Assert.Equal(passwordCred.Type, session.AuthenticationType); + Assert.True(session.IsInRole(Constants.AdminRoleName)); } } } -} +} \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index d3657c31b3..322c429bb7 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Security.Claims; using System.Security.Principal; +using NuGetGallery.Authentication; namespace NuGetGallery.Framework { @@ -11,9 +14,23 @@ public static class Fakes public static readonly string Password = "p@ssw0rd!"; - public static readonly User User = new User("testUser") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) } }; - public static readonly User Admin = new User("testAdmin") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) } }; - public static readonly User Owner = new User("testPackageOwner") { Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) }, EmailAddress = "confirmed@example.com" }; //package owners need confirmed email addresses, obviously. + public static readonly User User = new User("testUser") { + Key = 42, + Credentials = new List() { + CredentialBuilder.CreatePbkdf2Password(Password), + CredentialBuilder.CreateV1ApiKey(Guid.Parse("519e180e-335c-491a-ac26-e83c4bd31d65")) + } + }; + public static readonly User Admin = new User("testAdmin") { + Key = 43, + Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) }, + Roles = new List() { new Role() { Name = Constants.AdminRoleName } } + }; + public static readonly User Owner = new User("testPackageOwner") { + Key = 44, + Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) }, + EmailAddress = "confirmed@example.com" //package owners need confirmed email addresses, obviously. + }; public static readonly PackageRegistration Package = new PackageRegistration() { @@ -25,12 +42,16 @@ public static class Fakes } }; - public static IPrincipal ToPrincipal(this User user) + public static ClaimsPrincipal ToPrincipal(this User user) { - return new GenericPrincipal( - new GenericIdentity(user.Username), - user.Roles == null ? new string[0] : - user.Roles.Select(r => r.Name).ToArray()); + ClaimsIdentity identity = new ClaimsIdentity( + claims: Enumerable.Concat(new[] { + new Claim(ClaimsIdentity.DefaultNameClaimType, user.Username), + }, user.Roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r.Name))), + authenticationType: AuthenticationTypes.Session, + nameType: ClaimsIdentity.DefaultNameClaimType, + roleType: ClaimsIdentity.DefaultRoleClaimType); + return new ClaimsPrincipal(identity); } public static IIdentity ToIdentity(this User user) @@ -40,14 +61,21 @@ public static IIdentity ToIdentity(this User user) internal static void ConfigureEntitiesContext(FakeEntitiesContext ctxt) { - var fields = typeof(Fakes) - .GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(f => typeof(IEntity).IsAssignableFrom(f.FieldType)); - foreach (var field in fields) + // Add Users + var users = ctxt.Set(); + users.Add(User); + users.Add(Admin); + users.Add(Owner); + + // Add Credentials and link to users + var creds = ctxt.Set(); + foreach (var user in users) { - object set = SetMethod.MakeGenericMethod(field.FieldType).Invoke(ctxt, new object[0]); - var method = set.GetType().GetMethod("Add"); - method.Invoke(set, new object[] { field.GetValue(null) }); + foreach (var cred in user.Credentials) + { + cred.User = user; + creds.Add(cred); + } } } } diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index 4563cc78ab..938fd3718c 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -81,6 +81,21 @@ False ..\..\packages\Microsoft.Data.Services.Client.5.5.0\lib\net40\Microsoft.Data.Services.Client.dll + + False + ..\..\packages\Microsoft.Owin.2.0.0-rc1\lib\net45\Microsoft.Owin.dll + + + ..\..\packages\Microsoft.Owin.Host.SystemWeb.2.0.0-rc1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + False + ..\..\packages\Microsoft.Owin.Security.2.0.0-rc1\lib\net45\Microsoft.Owin.Security.dll + + + False + ..\..\packages\Microsoft.Owin.Security.Cookies.2.0.0-rc1\lib\net45\Microsoft.Owin.Security.Cookies.dll + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll True @@ -121,6 +136,10 @@ False ..\..\packages\NuGet.Core.2.7.0\lib\net40-Client\NuGet.Core.dll + + False + ..\..\packages\Owin.1.0\lib\net40\Owin.dll + False ..\..\packages\PoliteCaptcha.0.4.0.0\lib\net40\PoliteCaptcha.dll diff --git a/tests/NuGetGallery.Facts/packages.config b/tests/NuGetGallery.Facts/packages.config index 435c38def6..c8fc35eff3 100644 --- a/tests/NuGetGallery.Facts/packages.config +++ b/tests/NuGetGallery.Facts/packages.config @@ -13,6 +13,10 @@ + + + + @@ -25,6 +29,7 @@ + From f350b17c4ba7a5729c1812ed717aeac78c4fc640 Mon Sep 17 00:00:00 2001 From: anurse Date: Wed, 9 Oct 2013 12:40:32 -0700 Subject: [PATCH 05/18] Moved things over to use UserSession instead of IPrincipal --- .../Authentication/AuthenticationService.cs | 14 +- .../Authentication/AuthenticationTypes.cs | 12 - .../Authentication/UserSession.cs | 32 ++- src/NuGetGallery/Controllers/AppController.cs | 44 +++- .../Controllers/CuratedFeedsController.cs | 2 +- .../Controllers/CuratedPackagesController.cs | 8 +- .../Controllers/PackagesController.cs | 35 ++- .../Controllers/UsersController.cs | 20 +- src/NuGetGallery/ExtensionMethods.cs | 26 +-- src/NuGetGallery/ExtensionMethods.cs.rej | 63 ++++++ .../RequiresAccountConfirmationAttribute.cs | 7 +- src/NuGetGallery/NuGetGallery.csproj | 1 - .../AuthenticationServiceFacts.cs | 63 ------ .../CuratedFeedsControllerFacts.cs | 25 +-- .../CuratedPackagesControllerFacts.cs | 28 +-- .../Controllers/JsonApiControllerFacts.cs | 8 +- .../Controllers/PackagesControllerFacts.cs | 208 ++++++++---------- tests/NuGetGallery.Facts/Framework/Fakes.cs | 2 +- .../Framework/TestExtensionMethods.cs | 13 +- .../TestUtils/TestUtility.cs | 5 +- 20 files changed, 309 insertions(+), 307 deletions(-) delete mode 100644 src/NuGetGallery/Authentication/AuthenticationTypes.cs create mode 100644 src/NuGetGallery/ExtensionMethods.cs.rej diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index 59e4658e10..86b7bf6522 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -74,24 +74,12 @@ public virtual AuthenticatedUser Authenticate(Credential credential) } } - public virtual UserSession GetActiveSession() - { - var principal = Context.Authentication.User; - if (principal == null) - { - return null; - } - - return new UserSession(principal); - } - - public virtual UserSession CreateSession(AuthenticatedUser user) + public virtual void CreateSession(AuthenticatedUser user) { // Create a claims identity for the session ClaimsIdentity identity = CreateIdentity(user); Context.Authentication.SignIn(identity); - return new UserSession(new ClaimsPrincipal(identity)); } public static ClaimsIdentity CreateIdentity(AuthenticatedUser user) diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs deleted file mode 100644 index 47e451be45..0000000000 --- a/src/NuGetGallery/Authentication/AuthenticationTypes.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace NuGetGallery.Authentication -{ - public class AuthenticationTypes - { - public static readonly string Session = "Session"; - } -} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/UserSession.cs b/src/NuGetGallery/Authentication/UserSession.cs index 96eb852965..e3b3381e8e 100644 --- a/src/NuGetGallery/Authentication/UserSession.cs +++ b/src/NuGetGallery/Authentication/UserSession.cs @@ -3,20 +3,32 @@ using System.Linq; using System.Security.Claims; using System.Security.Principal; -using System.Text; +using System.Web; namespace NuGetGallery.Authentication { - public class UserSession : IPrincipal + public class UserSession : IPrincipal, IIdentity { - public ClaimsPrincipal Principal { get; private set; } + public virtual ClaimsPrincipal Principal { get; private set; } - public string Username { get { return Principal.Identity.Name; } } - public string AuthenticationType { get { return Principal.Identity.AuthenticationType; } } - - public IIdentity Identity + public virtual string AuthenticationType { - get { return Principal.Identity; } + get { return Principal.Identity.AuthenticationType; } + } + + public virtual bool IsAuthenticated + { + get { return Principal.Identity.IsAuthenticated; } + } + + public virtual string Name + { + get { return Principal.Identity.Name; } + } + + public virtual IIdentity Identity + { + get { return Principal.Identity; } } public UserSession(ClaimsPrincipal principal) @@ -24,9 +36,9 @@ public UserSession(ClaimsPrincipal principal) Principal = principal; } - public bool IsInRole(string role) + public virtual bool IsInRole(string role) { return Principal.IsInRole(role); } } -} +} \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/AppController.cs b/src/NuGetGallery/Controllers/AppController.cs index b470aa8f43..cdbb41a72f 100644 --- a/src/NuGetGallery/Controllers/AppController.cs +++ b/src/NuGetGallery/Controllers/AppController.cs @@ -1,18 +1,58 @@ -using System.Security.Principal; +using System; +using System.Security.Claims; +using System.Security.Principal; using System.Web.Mvc; +using NuGetGallery.Authentication; namespace NuGetGallery { public abstract partial class AppController : Controller { + private Lazy _session; + + [Obsolete("Use UserSession instead!")] public virtual IIdentity Identity { - get { return User.Identity; } + get { return base.User.Identity; } + } + + [Obsolete("Use UserSession instead!")] + public new IPrincipal User + { + get { return base.User; } + } + + public UserSession UserSession + { + get { return _session.Value; } + } + + public AppController() + { + _session = new Lazy(LoadSession); } protected internal virtual T GetService() { return DependencyResolver.Current.GetService(); } + + private UserSession LoadSession() + { + if (!HttpContext.Request.IsAuthenticated) + { + return null; + } + + ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal; + if (principal == null) + { + return null; + } + else + { + return new UserSession(principal); + } + } } } \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/CuratedFeedsController.cs b/src/NuGetGallery/Controllers/CuratedFeedsController.cs index 30745f89bb..9c80ca4e49 100644 --- a/src/NuGetGallery/Controllers/CuratedFeedsController.cs +++ b/src/NuGetGallery/Controllers/CuratedFeedsController.cs @@ -33,7 +33,7 @@ public virtual ActionResult CuratedFeed(string name) return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != Identity.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) { return new HttpStatusCodeResult(403); } diff --git a/src/NuGetGallery/Controllers/CuratedPackagesController.cs b/src/NuGetGallery/Controllers/CuratedPackagesController.cs index 4b58e6df64..0a5376aec0 100644 --- a/src/NuGetGallery/Controllers/CuratedPackagesController.cs +++ b/src/NuGetGallery/Controllers/CuratedPackagesController.cs @@ -31,7 +31,7 @@ public virtual ActionResult GetCreateCuratedPackageForm(string curatedFeedName) return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != Identity.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) { return new HttpStatusCodeResult(403); } @@ -58,7 +58,7 @@ public virtual ActionResult DeleteCuratedPackage( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != Identity.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) { return new HttpStatusCodeResult(403); } @@ -89,7 +89,7 @@ public virtual ActionResult PatchCuratedPackage( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != Identity.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) { return new HttpStatusCodeResult(403); } @@ -119,7 +119,7 @@ public virtual ActionResult PostCuratedPackages( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != Identity.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) { return new HttpStatusCodeResult(403); } diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs index d1f1689323..6571a362f0 100644 --- a/src/NuGetGallery/Controllers/PackagesController.cs +++ b/src/NuGetGallery/Controllers/PackagesController.cs @@ -39,7 +39,6 @@ public partial class PackagesController : AppController private readonly IIndexingService _indexingService; private readonly ICacheService _cacheService; private readonly EditPackageService _editPackageService; - private readonly IPrincipal _currentUser; public PackagesController( IPackageService packageService, @@ -54,8 +53,7 @@ public PackagesController( IAppConfiguration config, IIndexingService indexingService, ICacheService cacheService, - EditPackageService editPackageService, - IPrincipal currentUser) + EditPackageService editPackageService) { _packageService = packageService; _uploadFileService = uploadFileService; @@ -70,14 +68,13 @@ public PackagesController( _indexingService = indexingService; _cacheService = cacheService; _editPackageService = editPackageService; - _currentUser = currentUser; } [Authorize] [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] public virtual ActionResult UploadPackageProgress() { - string username = _currentUser.Identity.Name; + string username = UserSession.Name; AsyncFileUploadProgress progress = _cacheService.GetProgress(username); if (progress == null) @@ -147,7 +144,7 @@ public virtual ActionResult UndoPendingEdits(string id, string version) [RequiresAccountConfirmation("upload a package")] public async virtual Task UploadPackage() { - var currentUser = _userService.FindByUsername(_currentUser.Identity.Name); + var currentUser = _userService.FindByUsername(UserSession.Name); using (var existingUploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { @@ -166,7 +163,7 @@ public async virtual Task UploadPackage() [ValidateAntiForgeryToken] public virtual async Task UploadPackage(HttpPostedFileBase uploadFile) { - var currentUser = _userService.FindByUsername(_currentUser.Identity.Name); + var currentUser = _userService.FindByUsername(UserSession.Name); using (var existingUploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { @@ -246,7 +243,7 @@ public virtual ActionResult DisplayPackage(string id, string version) } var model = new DisplayPackageViewModel(package); - if (package.IsOwner(_currentUser)) + if (package.IsOwner(UserSession)) { // Tell logged-in package owners not to cache the package page, so they won't be confused about the state of pending edits. Response.Cache.SetCacheability(HttpCacheability.NoCache); @@ -332,7 +329,7 @@ public virtual ActionResult ReportAbuse(string id, string version) if (Request.IsAuthenticated) { - var user = _userService.FindByUsername(HttpContext.User.Identity.Name); + var user = _userService.FindByUsername(UserSession.Name); // If user logged on in as owner a different tab, then clicked the link, we can redirect them to ReportMyPackage if (package.IsOwner(user)) @@ -362,7 +359,7 @@ public virtual ActionResult ReportAbuse(string id, string version) [RequiresAccountConfirmation("contact support about your package")] public virtual ActionResult ReportMyPackage(string id, string version) { - var user = _userService.FindByUsername(HttpContext.User.Identity.Name); + var user = _userService.FindByUsername(UserSession.Name); var package = _packageService.FindPackageByIdAndVersion(id, version); @@ -411,7 +408,7 @@ public virtual ActionResult ReportAbuse(string id, string version, ReportAbuseVi MailAddress from; if (Request.IsAuthenticated) { - user = _userService.FindByUsername(HttpContext.User.Identity.Name); + user = _userService.FindByUsername(UserSession.Name); from = user.ToMailAddress(); } else @@ -457,7 +454,7 @@ public virtual ActionResult ReportMyPackage(string id, string version, ReportAbu return HttpNotFound(); } - var user = _userService.FindByUsername(HttpContext.User.Identity.Name); + var user = _userService.FindByUsername(UserSession.Name); MailAddress from = user.ToMailAddress(); _messageService.ReportMyPackage( @@ -515,7 +512,7 @@ public virtual ActionResult ContactOwners(string id, ContactOwnersViewModel cont return HttpNotFound(); } - var user = _userService.FindByUsername(User.Identity.Name); + var user = _userService.FindByUsername(UserSession.Name); var fromAddress = new MailAddress(user.EmailAddress, user.Username); _messageService.SendContactOwnersMessage( fromAddress, package, contactForm.Message, Url.Action(MVC.Users.Edit(), protocol: Request.Url.Scheme)); @@ -610,7 +607,7 @@ public virtual ActionResult Edit(string id, string version, EditPackageRequest f return HttpNotFound(); } - var user = _userService.FindByUsername(HttpContext.User.Identity.Name); + var user = _userService.FindByUsername(UserSession.Name); if (user == null || !package.IsOwner(HttpContext.User)) { return new HttpStatusCodeResult(403, "Forbidden"); @@ -649,7 +646,7 @@ public virtual ActionResult ConfirmOwner(string id, string username, string toke return HttpNotFound(); } - if (!String.Equals(username, User.Identity.Name, StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) { return View(new PackageOwnerConfirmationModel() { @@ -670,7 +667,7 @@ public virtual ActionResult ConfirmOwner(string id, string username, string toke return HttpNotFound(); } - if (!String.Equals(user.Username, User.Identity.Name, StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(user.Username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) { return new HttpStatusCodeResult(403); } @@ -739,7 +736,7 @@ private ActionResult GetPackageOwnerActionFormResult(string id, string version) [RequiresAccountConfirmation("upload a package")] public virtual async Task VerifyPackage() { - var currentUser = _userService.FindByUsername(_currentUser.Identity.Name); + var currentUser = _userService.FindByUsername(UserSession.Name); IPackageMetadata packageMetadata; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) @@ -795,7 +792,7 @@ public virtual async Task VerifyPackage() [ValidateInput(false)] // Security note: Disabling ASP.Net input validation which does things like disallow angle brackets in submissions. See http://go.microsoft.com/fwlink/?LinkID=212874 public virtual async Task VerifyPackage(VerifyPackageRequest formData) { - var currentUser = _userService.FindByUsername(_currentUser.Identity.Name); + var currentUser = _userService.FindByUsername(UserSession.Name); Package package; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) @@ -888,7 +885,7 @@ public virtual async Task VerifyPackage(VerifyPackageRequest formD [ValidateAntiForgeryToken] public virtual async Task CancelUpload() { - var currentUser = _userService.FindByUsername(_currentUser.Identity.Name); + var currentUser = _userService.FindByUsername(UserSession.Name); await _uploadFileService.DeleteUploadFileAsync(currentUser.Key); return RedirectToAction("UploadPackage"); diff --git a/src/NuGetGallery/Controllers/UsersController.cs b/src/NuGetGallery/Controllers/UsersController.cs index 8359154261..60a8be66de 100644 --- a/src/NuGetGallery/Controllers/UsersController.cs +++ b/src/NuGetGallery/Controllers/UsersController.cs @@ -38,7 +38,7 @@ public UsersController( [Authorize] public virtual ActionResult Account() { - var user = UserService.FindByUsername(Identity.Name); + var user = UserService.FindByUsername(UserSession.Name); var curatedFeeds = CuratedFeedService.GetFeedsForManager(user.Key); var apiCredential = user .Credentials @@ -58,7 +58,7 @@ public virtual ActionResult Account() [HttpGet] public virtual ActionResult ConfirmationRequired() { - User user = UserService.FindByUsername(User.Identity.Name); + User user = UserService.FindByUsername(UserSession.Name); var model = new ConfirmationViewModel { ConfirmingNewAccount = !(user.Confirmed), @@ -72,7 +72,7 @@ public virtual ActionResult ConfirmationRequired() [ActionName("ConfirmationRequired")] public virtual ActionResult ConfirmationRequiredPost() { - User user = UserService.FindByUsername(User.Identity.Name); + User user = UserService.FindByUsername(UserSession.Name); var confirmationUrl = Url.ConfirmationUrl( MVC.Users.Confirm(), user.Username, user.EmailConfirmationToken, protocol: Request.Url.Scheme); @@ -90,7 +90,7 @@ public virtual ActionResult ConfirmationRequiredPost() [Authorize] public virtual ActionResult Edit() { - var user = UserService.FindByUsername(Identity.Name); + var user = UserService.FindByUsername(UserSession.Name); var model = new EditProfileViewModel { Username = user.Username, @@ -106,7 +106,7 @@ public virtual ActionResult Edit() [ValidateAntiForgeryToken] public virtual ActionResult Edit(EditProfileViewModel profile) { - var user = UserService.FindByUsername(Identity.Name); + var user = UserService.FindByUsername(UserSession.Name); if (user == null) { return HttpNotFound(); @@ -130,7 +130,7 @@ public virtual ActionResult Thanks() [Authorize] public virtual ActionResult Packages() { - var user = UserService.FindByUsername(Identity.Name); + var user = UserService.FindByUsername(UserSession.Name); var packages = PackageService.FindPackagesByOwner(user, includeUnlisted: true) .Select(p => new PackageViewModel(p) { @@ -151,7 +151,7 @@ public virtual ActionResult Packages() public virtual ActionResult GenerateApiKey() { // Get the user - var user = UserService.FindByUsername(User.Identity.Name); + var user = UserService.FindByUsername(UserSession.Name); // Generate an API Key var apiKey = Guid.NewGuid(); @@ -246,7 +246,7 @@ public virtual ActionResult Confirm(string username, string token) // By having this value present in the dictionary BUT null, we don't put "returnUrl" on the Login link at all ViewData[Constants.ReturnUrlViewDataKey] = null; - if (!String.Equals(username, Identity.Name, StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) { return View(new ConfirmationViewModel { @@ -337,7 +337,7 @@ public virtual ActionResult ChangeEmail(ChangeEmailRequestModel model) return View(model); } - User user = UserService.FindByUsernameAndPassword(Identity.Name, model.Password); + User user = UserService.FindByUsernameAndPassword(UserSession.Name, model.Password); if (user == null) { ModelState.AddModelError("Password", Strings.CurrentPasswordIncorrect); @@ -393,7 +393,7 @@ public virtual ActionResult ChangePassword(PasswordChangeViewModel model) return View(model); } - if (!UserService.ChangePassword(Identity.Name, model.OldPassword, model.NewPassword)) + if (!UserService.ChangePassword(UserSession.Name, model.OldPassword, model.NewPassword)) { ModelState.AddModelError( "OldPassword", diff --git a/src/NuGetGallery/ExtensionMethods.cs b/src/NuGetGallery/ExtensionMethods.cs index 697d01a870..b34268c4a0 100644 --- a/src/NuGetGallery/ExtensionMethods.cs +++ b/src/NuGetGallery/ExtensionMethods.cs @@ -8,7 +8,6 @@ using System.Net.Mail; using System.Runtime.Versioning; using System.Security; -using System.Security.Claims; using System.Security.Principal; using System.ServiceModel.Activation; using System.Text; @@ -16,7 +15,6 @@ using System.Web.Routing; using System.Web.WebPages; using NuGet; -using NuGetGallery.Authentication; namespace NuGetGallery { @@ -42,16 +40,6 @@ public static void AddOrSet(this ConcurrentDictionary val); } - public static UserSession AsUserSession(this IPrincipal self) - { - if (self == null) - { - return null; - } - // Direct cast because a non-ClaimsPrincipal is an error here. - return new UserSession((ClaimsPrincipal)self); - } - public static SecureString ToSecureString(this string str) { SecureString output = new SecureString(); @@ -187,17 +175,25 @@ public static bool IsOwner(this Package package, IPrincipal user) return package.PackageRegistration.IsOwner(user); } - public static bool IsOwner(this Package package, UserSession user) + public static bool IsOwner(this Package package, User user) { return package.PackageRegistration.IsOwner(user); } public static bool IsOwner(this PackageRegistration package, IPrincipal user) { - return IsOwner(package, user.AsUserSession()); + if (package == null) + { + throw new ArgumentNullException("package"); + } + if (user == null || user.Identity == null) + { + return false; + } + return user.IsAdministrator() || package.Owners.Any(u => u.Username == user.Identity.Name); } - public static bool IsOwner(this PackageRegistration package, UserSession user) + public static bool IsOwner(this PackageRegistration package, User user) { if (package == null) { diff --git a/src/NuGetGallery/ExtensionMethods.cs.rej b/src/NuGetGallery/ExtensionMethods.cs.rej new file mode 100644 index 0000000000..0f5a587e29 --- /dev/null +++ b/src/NuGetGallery/ExtensionMethods.cs.rej @@ -0,0 +1,63 @@ +diff a/src/NuGetGallery/ExtensionMethods.cs b/src/NuGetGallery/ExtensionMethods.cs (rejected hunks) +@@ -8,6 +8,7 @@ + using System.Net.Mail; + using System.Runtime.Versioning; + using System.Security; ++using System.Security.Claims; + using System.Security.Principal; + using System.ServiceModel.Activation; + using System.Text; +@@ -15,6 +16,7 @@ + using System.Web.Routing; + using System.Web.WebPages; + using NuGet; ++using NuGetGallery.Authentication; + + namespace NuGetGallery + { +@@ -40,6 +42,16 @@ + self.AddOrUpdate(key, val, (_, __) => val); + } + ++ public static UserSession AsUserSession(this IPrincipal self) ++ { ++ if (self == null) ++ { ++ return null; ++ } ++ // Direct cast because a non-ClaimsPrincipal is an error here. ++ return new UserSession((ClaimsPrincipal)self); ++ } ++ + public static SecureString ToSecureString(this string str) + { + SecureString output = new SecureString(); +@@ -175,25 +187,17 @@ + return package.PackageRegistration.IsOwner(user); + } + +- public static bool IsOwner(this Package package, User user) ++ public static bool IsOwner(this Package package, UserSession user) + { + return package.PackageRegistration.IsOwner(user); + } + + public static bool IsOwner(this PackageRegistration package, IPrincipal user) + { +- if (package == null) +- { +- throw new ArgumentNullException("package"); +- } +- if (user == null || user.Identity == null) +- { +- return false; +- } +- return user.IsAdministrator() || package.Owners.Any(u => u.Username == user.Identity.Name); ++ return IsOwner(package, user.AsUserSession()); + } + +- public static bool IsOwner(this PackageRegistration package, User user) ++ public static bool IsOwner(this PackageRegistration package, UserSession user) + { + if (package == null) + { diff --git a/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs b/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs index 3ef883c307..58c17d408a 100644 --- a/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs +++ b/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Security.Principal; using System.Web.Mvc; +using NuGetGallery.Authentication; namespace NuGetGallery.Filters { @@ -23,14 +24,14 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } var controller = ((AppController)filterContext.Controller); - IPrincipal user = controller.User; - if (!user.Identity.IsAuthenticated) + UserSession user = controller.UserSession; + if (!user.IsAuthenticated) { throw new InvalidOperationException("Requires account confirmation attribute is only valid on authenticated actions."); } var userService = controller.GetService(); - var currentUser = userService.FindByUsername(user.Identity.Name); + var currentUser = userService.FindByUsername(user.Name); if (!currentUser.Confirmed) { controller.TempData["ConfirmationRequiredMessage"] = String.Format( diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 1cb78f6bc7..179e51c414 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -319,7 +319,6 @@ - diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index a108696db1..5741c3a283 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -135,46 +135,6 @@ public void GivenMultipleMatchingCredentials_ItThrows() } } - public class TheGetActiveSessionMethod : TestContainer - { - [Fact] - public void GivenNoActiveSession_ItReturnsNull() - { - // Arrange - var service = Get(); - GetMock() - .Setup(c => c.Authentication.User) - .ReturnsNull(); - - // Act - var result = service.GetActiveSession(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GivenAValidSession_ItReturnsMatchingUserSessionObject() - { - // Arrange - var service = Get(); - var principal = Fakes.Admin.ToPrincipal(); - GetMock() - .Setup(c => c.Authentication.User) - .Returns(principal); - - // Act - var result = service.GetActiveSession(); - - // Assert - Assert.NotNull(result); - Assert.Equal(Fakes.Admin.Username, result.Username); - Assert.Equal(AuthenticationTypes.Session, result.AuthenticationType); - Assert.Same(principal, result.Principal); - Assert.True(result.IsInRole(Constants.AdminRoleName)); - } - } - public class TheCreateSessionMethod : TestContainer { [Fact] @@ -202,29 +162,6 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() Assert.Equal(passwordCred.Type, id.AuthenticationType); Assert.True(principal.IsInRole(Constants.AdminRoleName)); } - - [Fact] - public void GivenAUser_ItReturnsTheCreatedUserSession() - { - // Arrange - var service = Get(); - GetMock() - .Setup(c => c.Authentication.SignIn(It.IsAny())); - - var passwordCred = Fakes.Admin.Credentials.SingleOrDefault( - c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); - - var user = new AuthenticatedUser(Fakes.Admin, passwordCred); - - // Act - var session = service.CreateSession(user); - - // Assert - Assert.NotNull(session); - Assert.Equal(Fakes.Admin.Username, session.Username); - Assert.Equal(passwordCred.Type, session.AuthenticationType); - Assert.True(session.IsInRole(Constants.AdminRoleName)); - } } } } \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs index d4dd228051..7f278af2a5 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Security.Principal; using System.Web; using System.Web.Mvc; using Moq; +using NuGetGallery.Authentication; using Xunit; namespace NuGetGallery @@ -18,10 +20,7 @@ public TestableCuratedFeedsController() StubCuratedFeed = new CuratedFeed { Key = 0, Name = "aName", Managers = new HashSet(new[] { new User { Username = "aUsername" } }) }; StubCuratedFeedService = new Mock(); - StubIdentity = new Mock(); - - StubIdentity.Setup(stub => stub.IsAuthenticated).Returns(true); - StubIdentity.Setup(stub => stub.Name).Returns("aUsername"); + StubCuratedFeedService .Setup(stub => stub.GetFeedByName(It.IsAny(), It.IsAny())) .Returns(StubCuratedFeed); @@ -30,18 +29,16 @@ public TestableCuratedFeedsController() StubSearchService = new Mock(); SearchService = StubSearchService.Object; + + var httpContext = new Mock(); + TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, this); + this.SetUser("aUsername"); } public CuratedFeed StubCuratedFeed { get; set; } public Mock StubCuratedFeedService { get; private set; } public Mock StubSearchService { get; private set; } - public Mock StubIdentity { get; private set; } - - public override IIdentity Identity - { - get { return StubIdentity.Object; } - } - + protected internal override T GetService() { if (typeof(T) == typeof(ICuratedFeedService)) @@ -70,8 +67,8 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedFeedsController(); - controller.StubIdentity.Setup(stub => stub.Name).Returns("notAManager"); - + controller.SetUser("notAManager"); + var result = controller.CuratedFeed("aFeedName") as HttpStatusCodeResult; Assert.NotNull(result); @@ -94,7 +91,7 @@ public void WillPassTheCuratedFeedNameToTheView() public void WillPassTheCuratedFeedManagersToTheView() { var controller = new TestableCuratedFeedsController(); - controller.StubIdentity.Setup(stub => stub.Name).Returns("theManager"); + controller.SetUser("theManager"); controller.StubCuratedFeed.Name = "aFeedName"; controller.StubCuratedFeed.Managers = new HashSet(new[] { new User { Username = "theManager" } }); diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs index 41eeab2c7f..cd77042877 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Security.Principal; +using System.Web; using System.Web.Mvc; using Moq; using Xunit; @@ -17,12 +18,8 @@ public TestableCuratedPackagesController() { StubCuratedFeed = new CuratedFeed { Key = 0, Name = "aFeedName", Managers = new HashSet(new[] { new User { Username = "aUsername" } }) }; - StubIdentity = new Mock(); StubPackageRegistration = new PackageRegistration { Key = 0, Id = "anId" }; - StubIdentity.Setup(stub => stub.IsAuthenticated).Returns(true); - StubIdentity.Setup(stub => stub.Name).Returns("aUsername"); - EntitiesContext = new FakeEntitiesContext(); EntitiesContext.CuratedFeeds.Add(StubCuratedFeed); EntitiesContext.PackageRegistrations.Add(StubPackageRegistration); @@ -36,16 +33,15 @@ public TestableCuratedPackagesController() base.CuratedFeedService = new CuratedFeedService( curatedFeedRepository, curatedPackageRepository); + + var httpContext = new Mock(); + TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, this); + + this.SetUser("aUsername"); } public CuratedFeed StubCuratedFeed { get; set; } - public Mock StubIdentity { get; private set; } public PackageRegistration StubPackageRegistration { get; private set; } - - public override IIdentity Identity - { - get { return StubIdentity.Object; } - } } public class TheDeleteCuratedPackageAction @@ -75,9 +71,7 @@ public void WillReturn404IfTheCuratedPackageDoesNotExist() public void WillReturn403IfTheUserNotAManager() { var controller = new TestableCuratedPackagesController(); - controller.StubIdentity - .Setup(i => i.Name) - .Returns("notAManager"); + controller.SetUser("notAManager"); controller.StubCuratedFeed.Packages.Add( new CuratedPackage @@ -154,7 +148,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedPackagesController(); - controller.StubIdentity.Setup(stub => stub.Name).Returns("notAManager"); + controller.SetUser("notAManager"); var result = controller.GetCreateCuratedPackageForm("aFeedName") as HttpStatusCodeResult; @@ -202,9 +196,7 @@ public void WillReturn404IfTheCuratedPackageDoesNotExist() public void WillReturn403IfNotAFeedManager() { var controller = new TestableCuratedPackagesController(); - controller.StubIdentity - .Setup(i => i.Name) - .Returns("notAManager"); + controller.SetUser("notAManager"); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { @@ -310,7 +302,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedPackagesController(); - controller.StubIdentity.Setup(stub => stub.Name).Returns("notAManager"); + controller.SetUser("notAManager"); var result = controller.PostCuratedPackages( "aFeedName", diff --git a/tests/NuGetGallery.Facts/Controllers/JsonApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/JsonApiControllerFacts.cs index d46ef14d4d..8eee1c1b9f 100644 --- a/tests/NuGetGallery.Facts/Controllers/JsonApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/JsonApiControllerFacts.cs @@ -39,7 +39,9 @@ public void DoesNotAllowNonPackageOwnerToAddPackageOwner() public void ReturnsFailureWhenRequestedNewOwnerDoesNotExist() { var controller = GetController(); - controller.SetUser(Fakes.Owner); + GetMock() + .Setup(c => c.User) + .Returns(Fakes.Owner.ToPrincipal()); dynamic result = controller.AddPackageOwner(Fakes.Package.Id, "notARealUser"); @@ -51,7 +53,9 @@ public void ReturnsFailureWhenRequestedNewOwnerDoesNotExist() public void CreatesPackageOwnerRequestSendsEmailAndReturnsPendingState() { var controller = GetController(); - controller.SetUser(Fakes.Owner); + GetMock() + .Setup(c => c.User) + .Returns(Fakes.Owner.ToPrincipal()); GetMock() .Setup(p => p.CreatePackageOwnerRequest(Fakes.Package, Fakes.Owner, Fakes.User)) diff --git a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs index d93de64d02..ec84eb75bf 100644 --- a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs @@ -29,7 +29,6 @@ private static PackagesController CreateController( Mock messageService = null, Mock httpContext = null, Mock editPackageService = null, - IPrincipal fakeUser = null, Mock fakeNuGetPackage = null, Mock searchService = null, Exception readPackageException = null, @@ -83,15 +82,12 @@ private static PackagesController CreateController( config.Object, indexingService.Object, cacheService.Object, - editPackageService.Object, - fakeUser); + editPackageService.Object); controller.CallBase = true; - if (httpContext != null) - { - TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, controller.Object); - } - + httpContext = httpContext ?? new Mock(); + TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, controller.Object); + if (readPackageException != null) { controller.Setup(x => x.CreatePackage(It.IsAny())).Throws(readPackageException); @@ -131,8 +127,8 @@ public async Task DeletesTheInProgressPackageUpload() fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); await controller.CancelUpload(); @@ -148,8 +144,8 @@ public async Task RedirectsToUploadPageAfterDelete() fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.CancelUpload() as RedirectToRouteResult; @@ -203,8 +199,8 @@ public void GivenAValidPackageThatTheCurrentUserDoesNotOwnItDisplaysCurrentMetad // Arrange var packageService = new Mock(); var controller = CreateController( - packageService: packageService, - fakeUser: TestUtility.FakePrincipal); + packageService: packageService); + controller.SetUser(TestUtility.FakeUser); packageService.Setup(p => p.FindPackageByIdAndVersion("Foo", "1.1.1", true)) .Returns(new Package() @@ -240,8 +236,8 @@ public void GivenAValidPackageThatTheCurrentUserOwnsItDisablesResponseCaching() var controller = CreateController( packageService: packageService, editPackageService: editPackageService, - httpContext: httpContext, - fakeUser: TestUtility.FakePrincipal); + httpContext: httpContext); + controller.SetUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); httpCachePolicy.Setup(c => c.SetCacheability(HttpCacheability.NoCache)).Verifiable(); @@ -283,8 +279,8 @@ public void GivenAValidPackageThatTheCurrentUserOwnsWithNoEditsItDisplaysCurrent var controller = CreateController( packageService: packageService, editPackageService: editPackageService, - httpContext: httpContext, - fakeUser: TestUtility.FakePrincipal); + httpContext: httpContext); + controller.SetUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); var package = new Package() @@ -327,8 +323,8 @@ public void GivenAValidPackageThatTheCurrentUserOwnsWithEditsItDisplaysEditedMet var controller = CreateController( packageService: packageService, editPackageService: editPackageService, - httpContext: httpContext, - fakeUser: TestUtility.FakePrincipal); + httpContext: httpContext); + controller.SetUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); var package = new Package() { @@ -384,10 +380,8 @@ public void WithNonExistentPackageIdReturnsHttpNotFound() { var userService = new Mock(); userService.Setup(u => u.FindByUsername("username")).Returns(new User { Username = "username" }); - var httpContext = new Mock(); - httpContext.Setup(c => c.User.Identity.Name).Returns("username"); - var controller = CreateController(userService: userService, httpContext: httpContext); - + var controller = CreateController(userService: userService); + controller.SetUser("username"); var result = controller.ConfirmOwner("foo", "username", "token"); Assert.IsType(result); @@ -396,12 +390,11 @@ public void WithNonExistentPackageIdReturnsHttpNotFound() [Fact] public void WithNonExistentUserReturnsHttpNotFound() { - var httpContext = new Mock(); - httpContext.Setup(c => c.User.Identity.Name).Returns("username"); var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(new PackageRegistration()); - var controller = CreateController(packageService: packageService, httpContext: httpContext); - + var controller = CreateController(packageService: packageService); + controller.SetUser("username"); + var result = controller.ConfirmOwner("foo", "username", "token"); Assert.IsType(result); @@ -410,10 +403,8 @@ public void WithNonExistentUserReturnsHttpNotFound() [Fact] public void WithIdentityNotMatchingUserInRequestReturnsViewWithMessage() { - var httpContext = new Mock(); - httpContext.Setup(c => c.User.Identity.Name).Returns("userA"); - var controller = CreateController(httpContext: httpContext); - + var controller = CreateController(); + controller.SetUser("userA"); var result = controller.ConfirmOwner("foo", "userB", "token"); var model = ResultAssert.IsView(result); @@ -431,9 +422,8 @@ public void RequiresUserBeLoggedInToConfirm() packageService.Setup(p => p.ConfirmPackageOwner(package, user, "token")).Returns(ConfirmOwnershipResult.Success); var userService = new Mock(); userService.Setup(u => u.FindByUsername("username")).Returns(user); - var httpContext = new Mock(); - httpContext.Setup(c => c.User.Identity.Name).Returns("not-username"); - var controller = CreateController(packageService: packageService, userService: userService, httpContext: httpContext); + var controller = CreateController(packageService: packageService, userService: userService); + controller.SetUser("not-username"); var result = controller.ConfirmOwner("foo", "username", "token"); @@ -455,9 +445,8 @@ public void AcceptsResultOfPackageServiceIfOtherwiseValid(ConfirmOwnershipResult packageService.Setup(p => p.ConfirmPackageOwner(package, user, "token")).Returns(confirmationResult); var userService = new Mock(); userService.Setup(u => u.FindByUsername("username")).Returns(user); - var httpContext = new Mock(); - httpContext.Setup(c => c.User.Identity.Name).Returns("username"); - var controller = CreateController(packageService: packageService, userService: userService, httpContext: httpContext); + var controller = CreateController(packageService: packageService, userService: userService); + controller.SetUser("username"); var result = controller.ConfirmOwner("foo", "username", "token"); @@ -508,16 +497,14 @@ public void HtmlEncodesMessageContent() var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("factory")).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.User.Identity.Name).Returns("Montgomery"); var userService = new Mock(); userService.Setup(u => u.FindByUsername("Montgomery")).Returns( new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var controller = CreateController( packageService: packageService, messageService: messageService, - userService: userService, - httpContext: httpContext); + userService: userService); + controller.SetUser("Montgomery"); var model = new ContactOwnersViewModel { Message = "I like the cut of your jib. It's bold.", @@ -542,17 +529,14 @@ public void CallsSendContactOwnersMessageWithUserInfo() var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("factory")).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.User.Identity.Name).Returns("Montgomery"); var userService = new Mock(); userService.Setup(u => u.FindByUsername("Montgomery")).Returns( new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var controller = CreateController( packageService: packageService, messageService: messageService, - userService: userService, - httpContext: httpContext, - fakeUser: Fakes.User.ToPrincipal()); + userService: userService); + controller.SetUser("Montgomery"); var model = new ContactOwnersViewModel { Message = "I like the cut of your jib", @@ -648,7 +632,10 @@ public void TrimsSearchTerm() var httpContext = new Mock(); var searchService = new Mock(); - var controller = CreateController(fakeUser: TestUtility.FakePrincipal, httpContext: httpContext, searchService: searchService); + var controller = CreateController( + httpContext: httpContext, + searchService: searchService); + controller.SetUser(TestUtility.FakeUser); var result = controller.ListPackages(" test ") as ViewResult; @@ -673,7 +660,6 @@ public void SendsMessageToGalleryOwnerWithEmailOnlyWhenUnauthenticated() var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("mordor", "2.0.1", true)).Returns(package); var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(false); var controller = CreateController( packageService: packageService, messageService: messageService, @@ -713,16 +699,15 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("mordor", It.IsAny(), true)).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Frodo"); var userService = new Mock(); userService.Setup(u => u.FindByUsername("Frodo")).Returns(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 1 }); + var httpContext = new Mock(); var controller = CreateController( packageService: packageService, messageService: messageService, userService: userService, httpContext: httpContext); + controller.SetUser("Frodo"); var model = new ReportAbuseViewModel { Message = "Mordor took my finger", @@ -753,15 +738,14 @@ public void FormRedirectsPackageOwnerToReportMyPackage() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("Mordor", It.IsAny(), true)).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Sauron"); var userService = new Mock(); userService.Setup(u => u.FindByUsername("Sauron")).Returns(new User { EmailAddress = "darklord@mordor.com", Username = "Sauron" }); + var httpContext = new Mock(); var controller = CreateController( packageService: packageService, userService: userService, httpContext: httpContext); + controller.SetUser("Sauron"); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportAbuse("Mordor", "2.0.1"); @@ -822,15 +806,14 @@ public void FormRedirectsNonOwnersToReportAbuse() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("Mordor", It.IsAny(), true)).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Frodo"); var userService = new Mock(); userService.Setup(u => u.FindByUsername("Frodo")).Returns(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 2 }); + var httpContext = new Mock(); var controller = CreateController( packageService: packageService, userService: userService, httpContext: httpContext); + controller.SetUser("Frodo"); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportMyPackage("Mordor", "2.0.1"); @@ -849,9 +832,6 @@ public void HtmlEncodesMessageContent() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("mordor", "2.0.1", true)).Returns(package); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Sauron"); var userService = new Mock(); userService.Setup(u => u.FindByUsername(user.Username)).Returns(user); @@ -860,11 +840,13 @@ public void HtmlEncodesMessageContent() messageService .Setup(s => s.ReportMyPackage(It.IsAny())) .Callback(r => reportRequest = r); + var httpContext = new Mock(); var controller = CreateController( packageService: packageService, messageService: messageService, userService: userService, httpContext: httpContext); + controller.SetUser("Sauron"); var model = new ReportAbuseViewModel { Email = "frodo@hobbiton.example.com", @@ -896,8 +878,8 @@ public async Task WillRedirectToVerifyPackageActionWhenThereIsAlreadyAnUploadInP fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage() as RedirectToRouteResult; @@ -915,8 +897,8 @@ public async Task WillShowTheViewWhenThereIsNoUploadInProgress() fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage() as ViewResult; @@ -936,8 +918,8 @@ public async Task WillReturn409WhenThereIsAlreadyAnUploadInProgress() fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(null) as HttpStatusCodeResult; @@ -952,8 +934,8 @@ public async Task WillShowViewWithErrorsIfPackageFileIsNull() var fakeUserService = new Mock(); fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var controller = CreateController( - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(null) as ViewResult; @@ -970,8 +952,8 @@ public async Task WillShowViewWithErrorsIfFileIsNotANuGetPackage() var fakeUserService = new Mock(); fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var controller = CreateController( - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -991,8 +973,8 @@ public async Task WillShowViewWithErrorsIfNuGetPackageIsInvalid() var controller = CreateController( userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, readPackageException: readPackageException); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -1013,8 +995,8 @@ public async Task WillShowTheViewWithErrorsWhenThePackageIdIsAlreadyBeingUsed() fakePackageService.Setup(x => x.FindPackageRegistrationById(It.IsAny())).Returns(fakePackageRegistration); var controller = CreateController( packageService: fakePackageService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -1035,8 +1017,8 @@ public async Task WillShowTheViewWithErrorsWhenThePackageAlreadyExists() new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); var controller = CreateController( packageService: fakePackageService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -1066,8 +1048,8 @@ public async Task WillSaveTheUploadFile() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.UploadPackage(fakeUploadedFile.Object); @@ -1090,8 +1072,8 @@ public async Task WillRedirectToVerifyPackageActionAfterSaving() fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(42, It.IsAny())).Returns(Task.FromResult(0)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as RedirectToRouteResult; @@ -1113,8 +1095,8 @@ public async Task WillRedirectToUploadPackagePageWhenThereIsNoUploadInProgress() fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); + userService: fakeUserService); + controller.SetUser(TestUtility.FakeUser); var result = await controller.VerifyPackage() as RedirectToRouteResult; @@ -1135,8 +1117,8 @@ public async Task WillPassThePackageIdToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1157,8 +1139,8 @@ public async Task WillPassThePackageVersionToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1179,8 +1161,8 @@ public async Task WillPassThePackageTitleToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1201,8 +1183,8 @@ public async Task WillPassThePackageSummaryToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1223,8 +1205,8 @@ public async Task WillPassThePackageDescriptionToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1245,8 +1227,8 @@ public async Task WillPassThePackageLicenseAcceptanceRequirementToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1267,8 +1249,8 @@ public async Task WillPassThePackageLicenseUrlToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1289,8 +1271,8 @@ public async Task WillPassThePackageTagsToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1311,8 +1293,8 @@ public async Task WillPassThePackageProjectUrlToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1333,8 +1315,8 @@ public async Task WillPassThePackagAuthorsToTheView() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1354,10 +1336,10 @@ public async Task WillRedirectToUploadPageWhenThereIsNoUploadInProgress() fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal); - + userService: fakeUserService); TestUtility.SetupUrlHelperForUrlGeneration(controller, new Uri("http://uploadpackage.xyz")); + controller.SetUser(TestUtility.FakeUser); + var result = await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }) as RedirectResult; Assert.NotNull(result); @@ -1382,8 +1364,8 @@ public async Task WillCreateThePackage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1413,9 +1395,9 @@ public async Task WillSavePackageToFileStorage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage, packageFileService: fakePackageFileService); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1451,10 +1433,10 @@ public async Task WillUpdateIndexingService() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage, packageFileService: fakePackageFileService, indexingService: fakeIndexingService); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1487,9 +1469,9 @@ public async Task WillSaveChangesToEntitiesContext() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage, entitiesContext: entitiesContext); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1521,8 +1503,8 @@ public async Task WillNotCommitChangesToPackageService() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1551,8 +1533,8 @@ public async Task WillPublishThePackage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1578,8 +1560,8 @@ public async Task WillMarkThePackageUnlistedWhenListedArgumentIsFalse() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1608,8 +1590,8 @@ public async Task WillNotMarkThePackageUnlistedWhenListedArgumentIsNullorTrue(bo packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = listed.GetValueOrDefault(true), Edit = null }); @@ -1635,8 +1617,8 @@ public async Task WillDeleteTheUploadFile() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1663,8 +1645,8 @@ public async Task WillSetAFlashMessage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1690,8 +1672,8 @@ public async Task WillRedirectToPackagePage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage); + controller.SetUser(TestUtility.FakeUser); var result = await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }) as RedirectToRouteResult; @@ -1719,9 +1701,9 @@ public async Task WillCurateThePackage() packageService: fakePackageService, uploadFileService: fakeUploadFileService, userService: fakeUserService, - fakeUser: TestUtility.FakePrincipal, fakeNuGetPackage: fakeNuGetPackage, autoCuratePackageCmd: fakeAutoCuratePackageCmd); + controller.SetUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1751,9 +1733,9 @@ public async Task WillExtractNuGetExe() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - fakeUser: TestUtility.FakePrincipal, userService: fakeUserService, downloaderService: nugetExeDownloader); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1789,9 +1771,9 @@ public async Task WillNotExtractNuGetExeIfIsNotLatestStable() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - fakeUser: TestUtility.FakePrincipal, userService: fakeUserService, downloaderService: nugetExeDownloader); + controller.SetUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1824,12 +1806,12 @@ public async Task WillNotExtractNuGetExeIfIsItDoesNotMatchId(string id) var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - fakeUser: TestUtility.FakePrincipal, userService: fakeUserService, downloaderService: nugetExeDownloader); + TestUtility.SetupUrlHelperForUrlGeneration(controller, new Uri("http://1.1.1.1")); + controller.SetUser(TestUtility.FakeUser); // Act - TestUtility.SetupUrlHelperForUrlGeneration(controller, new Uri("http://1.1.1.1")); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); // Assert @@ -1872,7 +1854,8 @@ public void WillReturnHttpNotFoundForUnknownUser() var cacheService = new Mock(MockBehavior.Strict); cacheService.Setup(c => c.GetItem(FakeUploadName)).Returns(null); - var controller = CreateController(fakeUser: TestUtility.FakePrincipal, cacheService: cacheService); + var controller = CreateController(cacheService: cacheService); + controller.SetUser(TestUtility.FakeUser); // Act var result = controller.UploadPackageProgress(); @@ -1888,7 +1871,8 @@ public void WillReturnCorrectResultForKnownUser() cacheService.Setup(c => c.GetItem(FakeUploadName)) .Returns(new AsyncFileUploadProgress(100) { FileName = "haha", TotalBytesRead = 80 }); - var controller = CreateController(fakeUser: TestUtility.FakePrincipal, cacheService: cacheService); + var controller = CreateController(cacheService: cacheService); + controller.SetUser(TestUtility.FakeUser); // Act var result = controller.UploadPackageProgress() as JsonResult; diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 322c429bb7..44ae04a638 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -48,7 +48,7 @@ public static ClaimsPrincipal ToPrincipal(this User user) claims: Enumerable.Concat(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.Username), }, user.Roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r.Name))), - authenticationType: AuthenticationTypes.Session, + authenticationType: "Test", nameType: ClaimsIdentity.DefaultNameClaimType, roleType: ClaimsIdentity.DefaultRoleClaimType); return new ClaimsPrincipal(identity); diff --git a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs index effd7d5ddd..ea543ea8c8 100644 --- a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs +++ b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs @@ -1,24 +1,29 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading.Tasks; using System.Web.Mvc; using Moq; +using NuGetGallery.Authentication; -namespace NuGetGallery.Framework +namespace NuGetGallery { public static class TestExtensionMethods { - public static void SetUser(this Controller self, string userName) + public static void SetUser(this AppController self, string userName) { SetUser(self, new User(userName)); } - public static void SetUser(this Controller self, User user) + public static void SetUser(this AppController self, User user) { - Mock.Get(self.HttpContext).Setup(c => c.User).Returns(user.ToPrincipal()); + Mock.Get(self.HttpContext).Setup(c => c.Request.IsAuthenticated).Returns(true); + Mock.Get(self.HttpContext).Setup(c => c.User).Returns( + new ClaimsPrincipal( + AuthenticationService.CreateIdentity(user, "Test"))); } } } diff --git a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs index 1f7d91e6ed..3a038a17c2 100644 --- a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs +++ b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Reflection; @@ -17,10 +18,8 @@ public static class TestUtility public static readonly string FakeUserName = "theUsername"; public static readonly string FakeAdminName = "theAdmin"; - public static readonly IPrincipal FakePrincipal = new GenericPrincipal(new GenericIdentity(FakeUserName), new string[0]); - public static readonly IPrincipal FakeAdminPrincipal = new GenericPrincipal(new GenericIdentity(FakeAdminName), new [] { Constants.AdminRoleName }); public static readonly User FakeUser = new User() { Username = FakeUserName }; - public static readonly User FakeAdminUser = new User() { Username = FakeAdminName }; + public static readonly User FakeAdminUser = new User() { Username = FakeAdminName, Roles = new List() { new Role() { Name = Constants.AdminRoleName } } }; // We only need this method because testing URL generation is a pain. // Alternatively, we could write our own service for generating URLs. From c74be6ead0a3c55ef1f379628cacfa3da4989c29 Mon Sep 17 00:00:00 2001 From: anurse Date: Thu, 10 Oct 2013 16:17:12 -0700 Subject: [PATCH 06/18] Moved authentication logic to its own service --- .../Entities/EntitiesContext.cs | 1 + .../Entities/IEntitiesContext.cs | 1 + .../Authentication/AuthenticationService.cs | 246 +++- src/NuGetGallery/Controllers/ApiController.cs | 28 +- src/NuGetGallery/Controllers/AppController.cs | 9 + .../Controllers/AuthenticationController.cs | 51 +- .../Controllers/UsersController.cs | 29 +- .../Filters/ApiKeyAuthorizeAttribute.cs | 77 -- .../Infrastructure/CredentialBuilder.cs | 10 +- src/NuGetGallery/NuGetGallery.csproj | 1 - src/NuGetGallery/Services/IUserService.cs | 49 - src/NuGetGallery/Services/UserService.cs | 295 ----- .../AuthenticationServiceFacts.cs | 568 +++++++++- .../Controllers/ApiControllerFacts.cs | 408 ++----- .../AuthenticationControllerFacts.cs | 176 +-- .../Controllers/UsersControllerFacts.cs | 81 +- .../Filters/ApiKeyAuthorizeAttributeFacts.cs | 134 --- tests/NuGetGallery.Facts/Framework/Fakes.cs | 23 +- .../Framework/TestContainer.cs | 12 +- .../Framework/UnitTestBindings.cs | 11 +- .../NuGetGallery.Facts.csproj | 1 - .../Services/UserServiceFacts.cs | 1004 ----------------- .../TestUtils/FakeEntitiesContext.cs | 12 + .../TestUtils/MockExtensions.cs | 17 + 24 files changed, 1119 insertions(+), 2125 deletions(-) delete mode 100644 src/NuGetGallery/Filters/ApiKeyAuthorizeAttribute.cs delete mode 100644 tests/NuGetGallery.Facts/Filters/ApiKeyAuthorizeAttributeFacts.cs diff --git a/src/NuGetGallery.Core/Entities/EntitiesContext.cs b/src/NuGetGallery.Core/Entities/EntitiesContext.cs index 60abfab867..1978ceebc1 100644 --- a/src/NuGetGallery.Core/Entities/EntitiesContext.cs +++ b/src/NuGetGallery.Core/Entities/EntitiesContext.cs @@ -30,6 +30,7 @@ public EntitiesContext() public IDbSet CuratedFeeds { get; set; } public IDbSet CuratedPackages { get; set; } public IDbSet PackageRegistrations { get; set; } + public IDbSet Credentials { get; set; } public IDbSet Users { get; set; } IDbSet IEntitiesContext.Set() diff --git a/src/NuGetGallery.Core/Entities/IEntitiesContext.cs b/src/NuGetGallery.Core/Entities/IEntitiesContext.cs index 3a18c9b9c5..065f24b924 100644 --- a/src/NuGetGallery.Core/Entities/IEntitiesContext.cs +++ b/src/NuGetGallery.Core/Entities/IEntitiesContext.cs @@ -7,6 +7,7 @@ public interface IEntitiesContext IDbSet CuratedFeeds { get; set; } IDbSet CuratedPackages { get; set; } IDbSet PackageRegistrations { get; set; } + IDbSet Credentials { get; set; } IDbSet Users { get; set; } int SaveChanges(); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Set", Justification="This is to match the EF terminology.")] diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index 86b7bf6522..a02feabd4c 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -8,19 +8,22 @@ using System.Globalization; using Microsoft.Owin; using System.Security.Claims; +using NuGetGallery.Configuration; namespace NuGetGallery.Authentication { public class AuthenticationService { public IEntitiesContext Entities { get; private set; } - public IOwinContext Context { get; private set; } + public IAppConfiguration Config { get; private set; } private IDiagnosticsSource Trace { get; set; } - public AuthenticationService(IEntitiesContext entities, IOwinContext context, IDiagnosticsService diagnostics) + protected AuthenticationService() { } + + public AuthenticationService(IEntitiesContext entities, IAppConfiguration config, IDiagnosticsService diagnostics) { Entities = entities; - Context = context; + Config = config; Trace = diagnostics.SafeGetSource("AuthenticationService"); } @@ -28,7 +31,7 @@ public virtual AuthenticatedUser Authenticate(string userNameOrEmail, string pas { using (Trace.Activity("Authenticate:" + userNameOrEmail)) { - var user = FindByUserName(userNameOrEmail); + var user = FindByUserNameOrEmail(userNameOrEmail); // Check if the user exists if (user == null) @@ -44,6 +47,15 @@ public virtual AuthenticatedUser Authenticate(string userNameOrEmail, string pas Trace.Information("Password validation failed: " + userNameOrEmail); return null; } + + var passwordCredentials = user + .Credentials + .Where(c => c.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) + .ToList(); + if (passwordCredentials.Count > 1 || !passwordCredentials.Any(c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase))) + { + MigrateCredentials(user, passwordCredentials, password); + } // Return the result Trace.Verbose("Successfully authenticated '" + user.Username + "' with '" + matched.Type + "' credential"); @@ -74,12 +86,156 @@ public virtual AuthenticatedUser Authenticate(Credential credential) } } - public virtual void CreateSession(AuthenticatedUser user) + public virtual void CreateSession(IOwinContext owinContext, AuthenticatedUser user) { // Create a claims identity for the session ClaimsIdentity identity = CreateIdentity(user); + owinContext.Authentication.SignIn(identity); + } + + public virtual AuthenticatedUser Register(string username, string password, string emailAddress) + { + var existingUser = Entities.Users + .FirstOrDefault(u => u.Username == username || u.EmailAddress == emailAddress); + if (existingUser != null) + { + if (String.Equals(existingUser.Username, username, StringComparison.OrdinalIgnoreCase)) + { + throw new EntityException(Strings.UsernameNotAvailable, username); + } + else + { + throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress); + } + } + + var hashedPassword = CryptographyService.GenerateSaltedHash(password, Constants.PBKDF2HashAlgorithmId); + + var apiKey = Guid.NewGuid(); + var newUser = new User(username) + { + ApiKey = apiKey, + EmailAllowed = true, + UnconfirmedEmailAddress = emailAddress, + EmailConfirmationToken = CryptographyService.GenerateToken(), + HashedPassword = hashedPassword, + PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId, + CreatedUtc = DateTime.UtcNow + }; + + // Add a credential for the password and the API Key + var passCred = new Credential(CredentialTypes.Password.Pbkdf2, newUser.HashedPassword); + newUser.Credentials.Add(CredentialBuilder.CreateV1ApiKey(apiKey)); + newUser.Credentials.Add(passCred); + + if (!Config.ConfirmEmailAddresses) + { + newUser.ConfirmEmailAddress(); + } + + Entities.Users.Add(newUser); + Entities.SaveChanges(); + + return new AuthenticatedUser(newUser, passCred); + } + + public virtual void ReplaceCredential(string username, Credential credential) + { + var user = Entities + .Users + .Include(u => u.Credentials) + .SingleOrDefault(u => u.Username == username); + if (user == null) + { + throw new InvalidOperationException(Strings.UserNotFound); + } + ReplaceCredential(user, credential); + } + + public virtual void ReplaceCredential(User user, Credential credential) + { + ReplaceCredentialInternal(user, credential); + Entities.SaveChanges(); + } + + public virtual bool ResetPasswordWithToken(string username, string token, string newPassword) + { + if (String.IsNullOrEmpty(newPassword)) + { + throw new ArgumentNullException("newPassword"); + } + + var user = Entities + .Users + .Include(u => u.Credentials) + .SingleOrDefault(u => u.Username == username); + + if (user != null && String.Equals(user.PasswordResetToken, token, StringComparison.Ordinal) && !user.PasswordResetTokenExpirationDate.IsInThePast()) + { + if (!user.Confirmed) + { + throw new InvalidOperationException(Strings.UserIsNotYetConfirmed); + } + + ReplaceCredentialInternal(user, CredentialBuilder.CreatePbkdf2Password(newPassword)); + user.PasswordResetToken = null; + user.PasswordResetTokenExpirationDate = null; + Entities.SaveChanges(); + return true; + } - Context.Authentication.SignIn(identity); + return false; + } + + public virtual bool ChangePassword(string username, string oldPassword, string newPassword) + { + // Review: If the old password is hashed using something other than PBKDF2, we end up making an extra db call that changes the old hash password. + // This operation is rare enough that I'm not inclined to change it. + var authUser = Authenticate(username, oldPassword); + if (authUser == null) + { + return false; + } + + var cred = CredentialBuilder.CreatePbkdf2Password(newPassword); + ReplaceCredentialInternal(authUser.User, cred); + Entities.SaveChanges(); + return true; + } + + public virtual User GeneratePasswordResetToken(string usernameOrEmail, int expirationInMinutes) + { + if (String.IsNullOrEmpty(usernameOrEmail)) + { + throw new ArgumentNullException("usernameOrEmail"); + } + if (expirationInMinutes < 1) + { + throw new ArgumentException( + "Token expiration should give the user at least a minute to change their password", "tokenExpirationMinutes"); + } + + var user = FindByUserNameOrEmail(usernameOrEmail); + if (user == null) + { + return null; + } + + if (!user.Confirmed) + { + throw new InvalidOperationException(Strings.UserIsNotYetConfirmed); + } + + if (!String.IsNullOrEmpty(user.PasswordResetToken) && !user.PasswordResetTokenExpirationDate.IsInThePast()) + { + return user; + } + + user.PasswordResetToken = CryptographyService.GenerateToken(); + user.PasswordResetTokenExpirationDate = DateTime.UtcNow.AddMinutes(expirationInMinutes); + + Entities.SaveChanges(); + return user; } public static ClaimsIdentity CreateIdentity(AuthenticatedUser user) @@ -99,6 +255,25 @@ public static ClaimsIdentity CreateIdentity(User user, string authenticationType return identity; } + private void ReplaceCredentialInternal(User user, Credential credential) + { + // Find the credentials we're replacing, if any + var creds = user.Credentials + .Where(cred => + // If we're replacing a password credential, remove ALL password credentials + (credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase) && + cred.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) || + cred.Type == credential.Type) + .ToList(); + foreach (var cred in creds) + { + user.Credentials.Remove(cred); + Entities.DeleteOnCommit(cred); + } + + user.Credentials.Add(credential); + } + private Credential FindMatchingCredential(Credential credential) { var results = Entities @@ -129,16 +304,35 @@ private Credential FindMatchingCredential(Credential credential) } } - private User FindByUserName(string userNameOrEmail) + private User FindByUserNameOrEmail(string userNameOrEmail) { - return Entities - .Set() + var users = Entities + .Users .Include(u => u.Credentials) - .Include(u => u.Roles) - .SingleOrDefault(u => u.Username == userNameOrEmail); + .Include(u => u.Roles); + + var user = users.SingleOrDefault(u => u.Username == userNameOrEmail); + if (user == null) + { + var allMatches = users + .Where(u => u.EmailAddress == userNameOrEmail) + .Take(2) + .ToList(); + + if (allMatches.Count == 1) + { + user = allMatches[0]; + } + else + { + // If multiple matches, leave it null to signal no unique email address + Trace.Warning("Multiple user accounts with email address: " + userNameOrEmail + " found: " + String.Join(", ", allMatches.Select(u => u.Username))); + } + } + return user; } - private static bool ValidatePasswordCredential(IEnumerable creds, string password, out Credential matched) + public static bool ValidatePasswordCredential(IEnumerable creds, string password, out Credential matched) { matched = creds.FirstOrDefault(c => ValidatePasswordCredential(c, password)); return matched != null; @@ -149,7 +343,7 @@ private static bool ValidatePasswordCredential(IEnumerable creds, st { CredentialTypes.Password.Sha1, (password, cred) => CryptographyService.ValidateSaltedHash(cred.Value, password, Constants.Sha1HashAlgorithmId) } }; - private static bool ValidatePasswordCredential(Credential cred, string password) + public static bool ValidatePasswordCredential(Credential cred, string password) { Func validator; if (!_validators.TryGetValue(cred.Type, out validator)) @@ -158,5 +352,31 @@ private static bool ValidatePasswordCredential(Credential cred, string password) } return validator(password, cred); } + + private void MigrateCredentials(User user, List creds, string password) + { + var toRemove = creds.Where(c => + !String.Equals( + c.Type, + CredentialTypes.Password.Pbkdf2, + StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // Remove any non PBKDF2 credentials + foreach (var cred in toRemove) + { + creds.Remove(cred); + user.Credentials.Remove(cred); + } + + // Now add one if there are no credentials left + if (creds.Count == 0) + { + user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password(password)); + } + + // Save changes, if any + Entities.SaveChanges(); + } } } \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index 779b5d77b0..f9ad685f3f 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -11,6 +11,7 @@ using System.Web.UI; using Newtonsoft.Json.Linq; using NuGet; +using NuGetGallery.Authentication; using NuGetGallery.Filters; using NuGetGallery.Packaging; @@ -26,6 +27,7 @@ public partial class ApiController : AppController public IStatisticsService StatisticsService { get; set; } public IContentService ContentService { get; set; } public IIndexingService IndexingService { get; set; } + public AuthenticationService AuthService { get; set; } protected ApiController() { } @@ -36,7 +38,8 @@ public ApiController( IUserService userService, INuGetExeDownloaderService nugetExeDownloaderService, IContentService contentService, - IIndexingService indexingService) + IIndexingService indexingService, + AuthenticationService authService) { EntitiesContext = entitiesContext; PackageService = packageService; @@ -46,6 +49,7 @@ public ApiController( ContentService = contentService; StatisticsService = null; IndexingService = indexingService; + AuthService = authService; } public ApiController( @@ -56,8 +60,9 @@ public ApiController( INuGetExeDownloaderService nugetExeDownloaderService, IContentService contentService, IIndexingService indexingService, - IStatisticsService statisticsService) - : this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService, indexingService) + IStatisticsService statisticsService, + AuthenticationService authService) + : this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService, indexingService, authService) { StatisticsService = statisticsService; } @@ -161,7 +166,6 @@ public virtual Task GetNuGetExe() [HttpGet] [ActionName("VerifyPackageKeyApi")] - [ApiKeyAuthorize] public virtual ActionResult VerifyPackageKey(string apiKey, string id, string version) { if (!String.IsNullOrEmpty(id)) @@ -187,7 +191,6 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve [HttpPut] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - [ApiKeyAuthorize] public virtual Task CreatePackagePut(string apiKey) { var user = GetUserByApiKey(apiKey); @@ -197,7 +200,6 @@ public virtual Task CreatePackagePut(string apiKey) [HttpPost] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - [ApiKeyAuthorize] public virtual Task CreatePackagePost(string apiKey) { var user = GetUserByApiKey(apiKey); @@ -256,7 +258,6 @@ private async Task CreatePackageInternal(User user) [HttpDelete] [ActionName("DeletePackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - [ApiKeyAuthorize] public virtual ActionResult DeletePackage(string apiKey, string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); @@ -287,7 +288,6 @@ public virtual ActionResult DeletePackage(string apiKey, string id, string versi [HttpPost] [ActionName("PublishPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - [ApiKeyAuthorize] public virtual ActionResult PublishPackage(string apiKey, string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); @@ -426,19 +426,15 @@ public virtual async Task GetStatsDownloads(int? count) private User GetUserByApiKey(string apiKey) { - var cred = UserService.AuthenticateCredential(CredentialTypes.ApiKeyV1, apiKey.ToLowerInvariant()); - User user; - if (cred == null) + var authUser = AuthService.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); + if (authUser == null) { -#pragma warning disable 0618 - user = UserService.FindByApiKey(Guid.Parse(apiKey)); -#pragma warning restore 0618 + return null; } else { - user = cred.User; + return authUser.User; } - return user; } private static void QuietlyLogException(Exception e) diff --git a/src/NuGetGallery/Controllers/AppController.cs b/src/NuGetGallery/Controllers/AppController.cs index cdbb41a72f..42fca132ac 100644 --- a/src/NuGetGallery/Controllers/AppController.cs +++ b/src/NuGetGallery/Controllers/AppController.cs @@ -1,7 +1,9 @@ using System; using System.Security.Claims; using System.Security.Principal; +using System.Web; using System.Web.Mvc; +using Microsoft.Owin; using NuGetGallery.Authentication; namespace NuGetGallery @@ -9,6 +11,7 @@ namespace NuGetGallery public abstract partial class AppController : Controller { private Lazy _session; + private IOwinContext _overrideContext; [Obsolete("Use UserSession instead!")] public virtual IIdentity Identity @@ -27,6 +30,12 @@ public UserSession UserSession get { return _session.Value; } } + public IOwinContext OwinContext + { + get { return _overrideContext ?? HttpContext.GetOwinContext(); } + set { _overrideContext = value; } + } + public AppController() { _session = new Lazy(LoadSession); diff --git a/src/NuGetGallery/Controllers/AuthenticationController.cs b/src/NuGetGallery/Controllers/AuthenticationController.cs index 1ff0a5acf9..2cb9554aa5 100644 --- a/src/NuGetGallery/Controllers/AuthenticationController.cs +++ b/src/NuGetGallery/Controllers/AuthenticationController.cs @@ -1,14 +1,15 @@ using System; +using System.Web; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; +using NuGetGallery.Authentication; namespace NuGetGallery { - public partial class AuthenticationController : Controller + public partial class AuthenticationController : AppController { - public IFormsAuthenticationService FormsAuth { get; protected set; } - public IUserService UserService { get; protected set; } + public AuthenticationService AuthService { get; protected set; } // For sub-classes to initialize services themselves protected AuthenticationController() @@ -16,11 +17,9 @@ protected AuthenticationController() } public AuthenticationController( - IFormsAuthenticationService formsAuthService, - IUserService userService) + AuthenticationService authService) { - FormsAuth = formsAuthService; - UserService = userService; + AuthService = authService; } [RequireRemoteHttps(OnlyWhenAuthenticated = false)] @@ -29,7 +28,7 @@ public virtual ActionResult LogOn(string returnUrl) // I think it should be obvious why we don't want the current URL to be the return URL here ;) ViewData[Constants.ReturnUrlViewDataKey] = returnUrl; - if (User != null && User.Identity != null && User.Identity.IsAuthenticated) + if (Request.IsAuthenticated) { TempData["Message"] = "You are already logged in!"; return Redirect(returnUrl); @@ -46,7 +45,7 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) // I think it should be obvious why we don't want the current URL to be the return URL here ;) ViewData[Constants.ReturnUrlViewDataKey] = returnUrl; - if (User != null && User.Identity != null && User.Identity.IsAuthenticated) + if (Request.IsAuthenticated) { ModelState.AddModelError(String.Empty, "You are already logged in!"); return View(); @@ -57,9 +56,7 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) return View(); } - var user = UserService.FindByUsernameOrEmailAddressAndPassword( - request.UserNameOrEmail, - request.Password); + var user = AuthService.Authenticate(request.UserNameOrEmail, request.Password); if (user == null) { @@ -70,7 +67,7 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) return View(); } - SetAuthenticationCookie(user); + AuthService.CreateSession(HttpContext.GetOwinContext(), user); return SafeRedirect(returnUrl); } @@ -82,7 +79,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) // I think it should be obvious why we don't want the current URL to be the return URL here ;) ViewData[Constants.ReturnUrlViewDataKey] = returnUrl; - if (User != null && User.Identity != null && User.Identity.IsAuthenticated) + if (Request.IsAuthenticated) { ModelState.AddModelError(String.Empty, "You are already logged in!"); return View(); @@ -93,10 +90,10 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) return View(); } - User user; + AuthenticatedUser user; try { - user = UserService.Create( + user = AuthService.Register( request.Username, request.Password, request.EmailAddress); @@ -107,7 +104,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) return View(); } - SetAuthenticationCookie(user); + AuthService.CreateSession(HttpContext.GetOwinContext(), user); if (RedirectHelper.SafeRedirectUrl(Url, returnUrl) != RedirectHelper.SafeRedirectUrl(Url, null)) { @@ -122,10 +119,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) public virtual ActionResult LogOff(string returnUrl) { - // TODO: this should really be a POST - - FormsAuth.SignOut(); - + OwinContext.Authentication.SignOut(); return SafeRedirect(returnUrl); } @@ -134,20 +128,5 @@ protected virtual ActionResult SafeRedirect(string returnUrl) { return Redirect(RedirectHelper.SafeRedirectUrl(Url, returnUrl)); } - - [NonAction] - protected virtual void SetAuthenticationCookie(User user) - { - IEnumerable roles = null; - if (user.Roles.AnySafe()) - { - roles = user.Roles.Select(r => r.Name); - } - - FormsAuth.SetAuthCookie( - user.Username, - true, - roles); - } } } diff --git a/src/NuGetGallery/Controllers/UsersController.cs b/src/NuGetGallery/Controllers/UsersController.cs index 60a8be66de..78f60eb567 100644 --- a/src/NuGetGallery/Controllers/UsersController.cs +++ b/src/NuGetGallery/Controllers/UsersController.cs @@ -4,6 +4,7 @@ using System.Net.Mail; using System.Security.Principal; using System.Web.Mvc; +using NuGetGallery.Authentication; using NuGetGallery.Configuration; namespace NuGetGallery @@ -15,7 +16,7 @@ public partial class UsersController : AppController public IPackageService PackageService { get; protected set; } public IAppConfiguration Config { get; protected set; } public IUserService UserService { get; protected set; } - public IPrincipal CurrentUser { get; protected set; } + public AuthenticationService AuthService { get; protected set; } protected UsersController() { } @@ -25,14 +26,14 @@ public UsersController( IPackageService packageService, IMessageService messageService, IAppConfiguration config, - IPrincipal currentUser) : this() + AuthenticationService authService) : this() { CuratedFeedService = feedsQuery; UserService = userService; PackageService = packageService; MessageService = messageService; Config = config; - CurrentUser = currentUser; + AuthService = authService; } [Authorize] @@ -160,7 +161,7 @@ public virtual ActionResult GenerateApiKey() user.ApiKey = apiKey; // Add/Replace the API Key credential, and save to the database - UserService.ReplaceCredential(user, CredentialBuilder.CreateV1ApiKey(apiKey)); + AuthService.ReplaceCredential(user, CredentialBuilder.CreateV1ApiKey(apiKey)); return RedirectToAction(MVC.Users.Account()); } @@ -183,7 +184,7 @@ public virtual ActionResult ForgotPassword(ForgotPasswordViewModel model) if (ModelState.IsValid) { - var user = UserService.GeneratePasswordResetToken(model.Email, Constants.DefaultPasswordResetTokenExpirationHours * 60); + var user = AuthService.GeneratePasswordResetToken(model.Email, Constants.DefaultPasswordResetTokenExpirationHours * 60); if (user != null) { var resetPasswordUrl = Url.ConfirmationUrl( @@ -229,7 +230,7 @@ public virtual ActionResult ResetPassword(string username, string token, Passwor // By having this value present in the dictionary BUT null, we don't put "returnUrl" on the Login link at all ViewData[Constants.ReturnUrlViewDataKey] = null; - ViewBag.ResetTokenValid = UserService.ResetPasswordWithToken(username, token, model.NewPassword); + ViewBag.ResetTokenValid = AuthService.ResetPasswordWithToken(username, token, model.NewPassword); if (!ViewBag.ResetTokenValid) { @@ -337,14 +338,14 @@ public virtual ActionResult ChangeEmail(ChangeEmailRequestModel model) return View(model); } - User user = UserService.FindByUsernameAndPassword(UserSession.Name, model.Password); - if (user == null) + var authUser = AuthService.Authenticate(UserSession.Name, model.Password); + if (authUser == null) { ModelState.AddModelError("Password", Strings.CurrentPasswordIncorrect); return View(model); } - if (String.Equals(model.NewEmail, user.LastSavedEmailAddress, StringComparison.OrdinalIgnoreCase)) + if (String.Equals(model.NewEmail, authUser.User.LastSavedEmailAddress, StringComparison.OrdinalIgnoreCase)) { // email address unchanged - accept return RedirectToAction(MVC.Users.Edit()); @@ -352,7 +353,7 @@ public virtual ActionResult ChangeEmail(ChangeEmailRequestModel model) try { - UserService.ChangeEmailAddress(user, model.NewEmail); + UserService.ChangeEmailAddress(authUser.User, model.NewEmail); } catch (EntityException e) { @@ -360,11 +361,11 @@ public virtual ActionResult ChangeEmail(ChangeEmailRequestModel model) return View(model); } - if (user.Confirmed) + if (authUser.User.Confirmed) { var confirmationUrl = Url.ConfirmationUrl( - MVC.Users.Confirm(), user.Username, user.EmailConfirmationToken, protocol: Request.Url.Scheme); - MessageService.SendEmailChangeConfirmationNotice(new MailAddress(user.UnconfirmedEmailAddress, user.Username), confirmationUrl); + MVC.Users.Confirm(), authUser.User.Username, authUser.User.EmailConfirmationToken, protocol: Request.Url.Scheme); + MessageService.SendEmailChangeConfirmationNotice(new MailAddress(authUser.User.UnconfirmedEmailAddress, authUser.User.Username), confirmationUrl); TempData["Message"] = "Your email address has been changed! We sent a confirmation email to verify your new email. When you confirm the new email address, it will take effect and we will forget the old one."; @@ -393,7 +394,7 @@ public virtual ActionResult ChangePassword(PasswordChangeViewModel model) return View(model); } - if (!UserService.ChangePassword(UserSession.Name, model.OldPassword, model.NewPassword)) + if (!AuthService.ChangePassword(UserSession.Name, model.OldPassword, model.NewPassword)) { ModelState.AddModelError( "OldPassword", diff --git a/src/NuGetGallery/Filters/ApiKeyAuthorizeAttribute.cs b/src/NuGetGallery/Filters/ApiKeyAuthorizeAttribute.cs deleted file mode 100644 index 1fa1c096ea..0000000000 --- a/src/NuGetGallery/Filters/ApiKeyAuthorizeAttribute.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Globalization; -using System.Net; -using System.Web.Mvc; -using Ninject; - -namespace NuGetGallery.Filters -{ - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] - public sealed class ApiKeyAuthorizeAttribute : ActionFilterAttribute - { - private IUserService _userService; // for tests - - public IUserService UserService - { - get { return _userService ?? Container.Kernel.TryGet(); } - set { _userService = value; } - } - - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - if (filterContext == null) - { - throw new ArgumentNullException("filterContext"); - } - - var controller = filterContext.Controller; - string apiKeyStr = (string)((Controller)controller).RouteData.Values["apiKey"]; - filterContext.Result = CheckForResult(apiKeyStr); - } - - public ActionResult CheckForResult(string apiKeyStr) - { - if (String.IsNullOrEmpty(apiKeyStr)) - { - return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKeyStr)); - } - - Guid _; - if (!Guid.TryParse(apiKeyStr, out _)) - { - return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, String.Format(CultureInfo.CurrentCulture, Strings.InvalidApiKey, apiKeyStr)); - } - - User user = GetUserByApiKey(apiKeyStr); - if (user == null) - { - return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "push")); - } - - if (!user.Confirmed) - { - return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, Strings.ApiKeyUserAccountIsUnconfirmed); - } - - return null; - } - - // Temporary helper, not necessary after removing the old credential storage - private User GetUserByApiKey(string apiKey) - { - var cred = UserService.AuthenticateCredential(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; - } - } -} \ No newline at end of file diff --git a/src/NuGetGallery/Infrastructure/CredentialBuilder.cs b/src/NuGetGallery/Infrastructure/CredentialBuilder.cs index 5b21ce2a5b..aed8395560 100644 --- a/src/NuGetGallery/Infrastructure/CredentialBuilder.cs +++ b/src/NuGetGallery/Infrastructure/CredentialBuilder.cs @@ -17,10 +17,7 @@ public static Credential CreateV1ApiKey() public static Credential CreateV1ApiKey(Guid apiKey) { - var value = apiKey - .ToString() - .ToLowerInvariant(); - return new Credential(CredentialTypes.ApiKeyV1, value); + return CreateV1ApiKey(apiKey.ToString()); } public static Credential CreatePbkdf2Password(string plaintextPassword) @@ -36,5 +33,10 @@ public static Credential CreateSha1Password(string plaintextPassword) CredentialTypes.Password.Sha1, CryptographyService.GenerateSaltedHash(plaintextPassword, Constants.Sha1HashAlgorithmId)); } + + internal static Credential CreateV1ApiKey(string apiKey) + { + return new Credential(CredentialTypes.ApiKeyV1, apiKey.ToLowerInvariant()); + } } } \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 179e51c414..08add5605a 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -612,7 +612,6 @@ - diff --git a/src/NuGetGallery/Services/IUserService.cs b/src/NuGetGallery/Services/IUserService.cs index 147a57a576..60b5689b89 100644 --- a/src/NuGetGallery/Services/IUserService.cs +++ b/src/NuGetGallery/Services/IUserService.cs @@ -5,13 +5,8 @@ namespace NuGetGallery { public interface IUserService { - User Create(string username, string password, string emailAddress); - void UpdateProfile(User user, bool emailAllowed); - [Obsolete("Use AuthenticateCredential instead")] - User FindByApiKey(Guid apiKey); - User FindByEmailAddress(string emailAddress); IList FindAllByEmailAddress(string emailAddress); @@ -20,52 +15,8 @@ public interface IUserService User FindByUsername(string username); - User FindByUsernameAndPassword(string username, string password); - - User FindByUsernameOrEmailAddressAndPassword(string usernameOrEmail, string password); - - [Obsolete("Use ReplaceCredential instead")] - string GenerateApiKey(string username); - bool ConfirmEmailAddress(User user, string token); void ChangeEmailAddress(User user, string newEmailAddress); - - bool ChangePassword(string username, string oldPassword, string newPassword); - - User GeneratePasswordResetToken(string usernameOrEmail, int tokenExpirationMinutes); - - bool ResetPasswordWithToken(string username, string token, string newPassword); - - /// - /// Gets an authenticated credential, that is it returns a credential IF AND ONLY IF - /// one exists with exactly the specified type and value. - /// - /// The type of the credential, see - /// The value of the credential (such as an OAuth ID, API Key, etc.) - /// - /// null if there is no credential matching the request, or a - /// object WITH the associated object eagerly loaded if there is - /// a matching credential - /// - Credential AuthenticateCredential(string type, string value); - - /// - /// Creates a new credential for the specified user, overwriting the - /// previous credential of the same type, if any. Immediately saves - /// changes to the database. - /// - /// The name of the user to create a credential for - /// The credential to create - void ReplaceCredential(string userName, Credential credential); - - /// - /// Creates a new credential for the specified user, overwriting the - /// previous credential of the same type, if any. Immediately saves - /// changes to the database. - /// - /// The user object to create a credential for - /// The credential to create - void ReplaceCredential(User user, Credential credential); } } \ No newline at end of file diff --git a/src/NuGetGallery/Services/UserService.cs b/src/NuGetGallery/Services/UserService.cs index 727e687ffa..b4f382f07f 100644 --- a/src/NuGetGallery/Services/UserService.cs +++ b/src/NuGetGallery/Services/UserService.cs @@ -26,55 +26,6 @@ public UserService( CredentialRepository = credentialRepository; } - public virtual User Create( - string username, - string password, - string emailAddress) - { - // TODO: validate input - // TODO: consider encrypting email address with a public key, and having the background process that send messages have the private key to decrypt - - var existingUser = FindByUsername(username); - if (existingUser != null) - { - throw new EntityException(Strings.UsernameNotAvailable, username); - } - - var existingUsers = FindAllByEmailAddress(emailAddress); - if (existingUsers.AnySafe()) - { - throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress); - } - - var hashedPassword = Crypto.GenerateSaltedHash(password, Constants.PBKDF2HashAlgorithmId); - - var apiKey = Guid.NewGuid(); - var newUser = new User(username) - { - ApiKey = apiKey, - EmailAllowed = true, - UnconfirmedEmailAddress = emailAddress, - EmailConfirmationToken = Crypto.GenerateToken(), - HashedPassword = hashedPassword, - PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId, - CreatedUtc = DateTime.UtcNow - }; - - // Add a credential for the password and the API Key - newUser.Credentials.Add(CredentialBuilder.CreateV1ApiKey(apiKey)); - newUser.Credentials.Add(new Credential(CredentialTypes.Password.Pbkdf2, newUser.HashedPassword)); - - if (!Config.ConfirmEmailAddresses) - { - newUser.ConfirmEmailAddress(); - } - - UserRepository.InsertOnCommit(newUser); - UserRepository.CommitChanges(); - - return newUser; - } - public void UpdateProfile(User user, bool emailAllowed) { if (user == null) @@ -86,12 +37,6 @@ public void UpdateProfile(User user, bool emailAllowed) UserRepository.CommitChanges(); } - [Obsolete("Use AuthenticateCredential instead")] - public User FindByApiKey(Guid apiKey) - { - return UserRepository.GetAll().SingleOrDefault(u => u.ApiKey == apiKey); - } - public virtual User FindByEmailAddress(string emailAddress) { var allMatches = UserRepository.GetAll() @@ -134,35 +79,6 @@ public virtual User FindByUsername(string username) .SingleOrDefault(u => u.Username == username); } - public virtual User FindByUsernameAndPassword(string username, string password) - { - var user = FindByUsername(username); - - return AuthenticatePassword(password, user); - } - - public virtual User FindByUsernameOrEmailAddressAndPassword(string usernameOrEmail, string password) - { - var user = FindByUsername(usernameOrEmail) ?? FindByEmailAddress(usernameOrEmail); - - return AuthenticatePassword(password, user); - } - - [Obsolete("Use ReplaceCredential instead")] - public string GenerateApiKey(string username) - { - var user = FindByUsername(username); - if (user == null) - { - return null; - } - - var newApiKey = Guid.NewGuid(); - user.ApiKey = newApiKey; - UserRepository.CommitChanges(); - return newApiKey.ToString(); - } - public void ChangeEmailAddress(User user, string newEmailAddress) { var existingUsers = FindAllByEmailAddress(newEmailAddress); @@ -175,21 +91,6 @@ public void ChangeEmailAddress(User user, string newEmailAddress) UserRepository.CommitChanges(); } - public bool ChangePassword(string username, string oldPassword, string newPassword) - { - // Review: If the old password is hashed using something other than PBKDF2, we end up making an extra db call that changes the old hash password. - // This operation is rare enough that I'm not inclined to change it. - var user = FindByUsernameAndPassword(username, oldPassword); - if (user == null) - { - return false; - } - - ChangePasswordInternal(user, newPassword); - UserRepository.CommitChanges(); - return true; - } - public bool ConfirmEmailAddress(User user, string token) { if (user == null) @@ -218,201 +119,5 @@ public bool ConfirmEmailAddress(User user, string token) UserRepository.CommitChanges(); return true; } - - public virtual User GeneratePasswordResetToken(string usernameOrEmail, int tokenExpirationMinutes) - { - if (String.IsNullOrEmpty(usernameOrEmail)) - { - throw new ArgumentNullException("usernameOrEmail"); - } - if (tokenExpirationMinutes < 1) - { - throw new ArgumentException( - "Token expiration should give the user at least a minute to change their password", "tokenExpirationMinutes"); - } - - var user = FindByEmailAddress(usernameOrEmail); - if (user == null) - { - return null; - } - - if (!user.Confirmed) - { - throw new InvalidOperationException(Strings.UserIsNotYetConfirmed); - } - - if (!String.IsNullOrEmpty(user.PasswordResetToken) && !user.PasswordResetTokenExpirationDate.IsInThePast()) - { - return user; - } - - user.PasswordResetToken = Crypto.GenerateToken(); - user.PasswordResetTokenExpirationDate = DateTime.UtcNow.AddMinutes(tokenExpirationMinutes); - - UserRepository.CommitChanges(); - return user; - } - - public bool ResetPasswordWithToken(string username, string token, string newPassword) - { - if (String.IsNullOrEmpty(newPassword)) - { - throw new ArgumentNullException("newPassword"); - } - - var user = FindByUsername(username); - - if (user != null && user.PasswordResetToken == token && !user.PasswordResetTokenExpirationDate.IsInThePast()) - { - if (!user.Confirmed) - { - throw new InvalidOperationException(Strings.UserIsNotYetConfirmed); - } - - ChangePasswordInternal(user, newPassword); - user.PasswordResetToken = null; - user.PasswordResetTokenExpirationDate = null; - UserRepository.CommitChanges(); - return true; - } - - return false; - } - - public Credential AuthenticateCredential(string type, string value) - { - // Search for the cred - return CredentialRepository - .GetAll() - .Include(c => c.User) - .SingleOrDefault(c => c.Type == type && c.Value == value); - } - - public void ReplaceCredential(string userName, Credential credential) - { - var user = UserRepository - .GetAll() - .Include(u => u.Credentials) - .SingleOrDefault(u => u.Username == userName); - if (user == null) - { - throw new InvalidOperationException(Strings.UserNotFound); - } - ReplaceCredential(user, credential); - } - - public void ReplaceCredential(User user, Credential credential) - { - ReplaceCredentialInternal(user, credential); - UserRepository.CommitChanges(); - } - - private User AuthenticatePassword(string password, User user) - { - if (user == null) - { - return null; - } - - // Check for a credential - var creds = user.Credentials - .Where(c => c.Type.StartsWith( - CredentialTypes.Password.Prefix, - StringComparison.OrdinalIgnoreCase)).ToList(); - - bool valid; - if (creds.Count > 0) - { - valid = ValidatePasswordCredential(creds, password); - - if (valid && - (creds.Count > 1 || - !creds.Any(c => String.Equals( - c.Type, - CredentialTypes.Password.Pbkdf2, - StringComparison.OrdinalIgnoreCase)))) - { - MigrateCredentials(user, creds, password); - } - } - else - { - valid = Crypto.ValidateSaltedHash( - user.HashedPassword, - password, - user.PasswordHashAlgorithm); - } - - return valid ? user : null; - } - - private void MigrateCredentials(User user, List creds, string password) - { - var toRemove = creds.Where(c => - !String.Equals( - c.Type, - CredentialTypes.Password.Pbkdf2, - StringComparison.OrdinalIgnoreCase)) - .ToList(); - - // Remove any non PBKDF2 credentials - foreach (var cred in toRemove) - { - creds.Remove(cred); - user.Credentials.Remove(cred); - } - - // Now add one if there are no credentials left - if (creds.Count == 0) - { - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password(password)); - } - - // Save changes, if any - UserRepository.CommitChanges(); - } - - private static bool ValidatePasswordCredential(IEnumerable creds, string password) - { - return creds.Any(c => ValidatePasswordCredential(c, password)); - } - - private static readonly Dictionary> _validators = new Dictionary>(StringComparer.OrdinalIgnoreCase) { - { CredentialTypes.Password.Pbkdf2, (password, cred) => Crypto.ValidateSaltedHash(cred.Value, password, Constants.PBKDF2HashAlgorithmId) }, - { CredentialTypes.Password.Sha1, (password, cred) => Crypto.ValidateSaltedHash(cred.Value, password, Constants.Sha1HashAlgorithmId) } - }; - private static bool ValidatePasswordCredential(Credential cred, string password) - { - Func validator; - if (!_validators.TryGetValue(cred.Type, out validator)) - { - return false; - } - return validator(password, cred); - } - - private void ChangePasswordInternal(User user, string newPassword) - { - var cred = CredentialBuilder.CreatePbkdf2Password(newPassword); - user.PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId; - user.HashedPassword = cred.Value; - ReplaceCredentialInternal(user, cred); - } - - private void ReplaceCredentialInternal(User user, Credential credential) - { - // Find the credentials we're replacing, if any - var creds = user.Credentials - .Where(cred => cred.Type == credential.Type) - .ToList(); - foreach (var cred in creds) - { - user.Credentials.Remove(cred); - CredentialRepository.DeleteOnCommit(cred); - } - - user.Credentials.Add(credential); - } } } diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 5741c3a283..e47134f060 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -8,6 +8,7 @@ using Microsoft.Owin; using Microsoft.Owin.Security; using Moq; +using NuGetGallery.Configuration; using NuGetGallery.Framework; using Xunit; @@ -15,6 +16,21 @@ namespace NuGetGallery.Authentication { public class AuthenticationServiceFacts { + public static bool VerifyPasswordHash(string hash, string algorithm, string password) + { + bool canAuthenticate = CryptographyService.ValidateSaltedHash( + hash, + password, + algorithm); + + bool sanity = CryptographyService.ValidateSaltedHash( + hash, + "not_the_password", + algorithm); + + return canAuthenticate && !sanity; + } + public class TheAuthenticateMethod : TestContainer { [Fact] @@ -133,6 +149,40 @@ public void GivenMultipleMatchingCredentials_ItThrows() cred.Type, cred.Key), ex.Message); } + + [Fact] + public void GivenOnlyASHA1PasswordItAuthenticatesUserAndReplacesItWithAPBKDF2Password() + { + var user = Fakes.CreateUser("tempUser", CredentialBuilder.CreateSha1Password("thePassword")); + var service = Get(); + service.Entities.Users.Add(user); + + var foundByUserName = service.Authenticate("theUsername", "thePassword"); + + var cred = foundByUserName.User.Credentials.Single(); + Assert.Same(user, foundByUserName); + Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); + Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); + service.Entities.VerifyCommitChanges(); + } + + [Fact] + public void GivenASHA1AndAPBKDF2PasswordItAuthenticatesUserAndRemovesTheSHA1Password() + { + var user = Fakes.CreateUser("tempUser", + CredentialBuilder.CreateSha1Password("thePassword"), + CredentialBuilder.CreatePbkdf2Password("thePassword")); + var service = Get(); + service.Entities.Users.Add(user); + + var foundByUserName = service.Authenticate("theUsername", "thePassword"); + + var cred = foundByUserName.User.Credentials.Single(); + Assert.Same(user, foundByUserName); + Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); + Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); + service.Entities.VerifyCommitChanges(); + } } public class TheCreateSessionMethod : TestContainer @@ -143,7 +193,8 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() // Arrange var service = Get(); ClaimsIdentity id = null; - GetMock() + var context = GetMock(); + context .Setup(c => c.Authentication.SignIn(It.IsAny())) .Callback(ids => id = ids.SingleOrDefault()); @@ -153,7 +204,7 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() var user = new AuthenticatedUser(Fakes.Admin, passwordCred); // Act - service.CreateSession(user); + service.CreateSession(context.Object, user); // Assert Assert.NotNull(id); @@ -163,5 +214,518 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() Assert.True(principal.IsInRole(Constants.AdminRoleName)); } } + + public class TheChangePasswordMethod : TestContainer + { + [Fact] + public void ReturnsFalseIfUserIsNotFound() + { + // Arrange + var service = Get(); + + // Act + var changed = service.ChangePassword("totallyNotARealUser", "oldpwd", "newpwd"); + + // Assert + Assert.False(changed); + } + + [Fact] + public void ReturnsFalseIfPasswordDoesNotMatchUser_SHA1() + { + // Arrange + var service = Get(); + var user = Fakes.CreateUser("tempUser", + CredentialBuilder.CreateSha1Password("oldpwd")); + service.Entities + .Set() + .Add(user); + + // Act + var changed = service.ChangePassword(user.Username, "not_the_password", "newpwd"); + + // Assert + Assert.False(changed); + } + + [Fact] + public void ReturnsFalseIfPasswordDoesNotMatchUser_PBKDF2() + { + // Arrange + var service = Get(); + var user = Fakes.CreateUser("tempUser", + CredentialBuilder.CreatePbkdf2Password("oldpwd")); + service.Entities + .Set() + .Add(user); + + // Act + var changed = service.ChangePassword(user.Username, "not_the_password", "newpwd"); + + // Assert + Assert.False(changed); + } + + [Fact] + public void ReturnsTrueWhenSuccessful() + { + // Arrange + var service = Get(); + var user = Fakes.CreateUser( + "tempUser", + CredentialBuilder.CreateSha1Password("oldpwd")); + service.Entities + .Set() + .Add(user); + + // Act + var changed = service.ChangePassword(user.Username, "oldpwd", "newpwd"); + + // Assert + Assert.True(changed); + } + + [Fact] + public void UpdatesThePasswordCredential() + { + // Arrange + var service = Get(); + var user = Fakes.CreateUser( + "tempUser", + CredentialBuilder.CreatePbkdf2Password("oldpwd")); + service.Entities + .Set() + .Add(user); + + // Act + var changed = service.ChangePassword(user.Username, "oldpwd", "newpwd"); + + // Assert + var cred = user.Credentials.Single(); + Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); + Assert.True(VerifyPasswordHash(cred.Value, Constants.PBKDF2HashAlgorithmId, "newpwd")); + service.Entities.VerifyCommitChanges(); + } + + [Fact] + public void MigratesPasswordIfHashAlgorithmIsNotPBKDF2() + { + // Arrange + var service = Get(); + var user = Fakes.CreateUser( + "tempUser", + CredentialBuilder.CreateSha1Password("oldpwd")); + service.Entities + .Set() + .Add(user); + + // Act + var changed = service.ChangePassword(user.Username, "oldpwd", "newpwd"); + + // Assert + var cred = user.Credentials.Single(c => c.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)); + Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); + Assert.True(VerifyPasswordHash(cred.Value, Constants.PBKDF2HashAlgorithmId, "newpwd")); + service.Entities.VerifyCommitChanges(); + } + } + + public class TheRegisterMethod : TestContainer + { + [Fact] + public void WillThrowIfTheUsernameIsAlreadyInUse() + { + // Arrange + var auth = Get(); + + // Act + var ex = Assert.Throws(() => + auth.Register( + Fakes.User.Username, + "thePassword", + "theEmailAddress")); + + // Assert + Assert.Equal(String.Format(Strings.UsernameNotAvailable, Fakes.User.Username), ex.Message); + } + + [Fact] + public void WillThrowIfTheEmailAddressIsAlreadyInUse() + { + // Arrange + var auth = Get(); + + // Act + var ex = Assert.Throws( + () => + auth.Register( + "theUsername", + "thePassword", + Fakes.User.EmailAddress)); + + // Assert + Assert.Equal(String.Format(Strings.EmailAddressBeingUsed, Fakes.User.EmailAddress), ex.Message); + } + + [Fact] + public void WillHashThePasswordWithPBKDF2() + { + // Arrange + var auth = Get(); + + // Act + var authUser = auth.Register( + "aNewUser", + "thePassword", + "theEmailAddress"); + + // Assert + Credential matched; + Assert.True(AuthenticationService.ValidatePasswordCredential(authUser.User.Credentials, "thePassword", out matched)); + Assert.Equal(CredentialTypes.Password.Pbkdf2, matched.Type); + } + + [Fact] + public void WillSaveTheNewUser() + { + // Arrange + var auth = Get(); + + // Arrange + var authUser = auth.Register( + "theUsername", + "thePassword", + "theEmailAddress"); + + // Assert + Assert.True(auth.Entities.Users.Contains(authUser.User)); + auth.Entities.VerifyCommitChanges(); + } + + [Fact] + public void WillSaveTheNewUserAsConfirmedWhenConfigured() + { + // Arrange + var auth = Get(); + GetMock() + .Setup(x => x.ConfirmEmailAddresses) + .Returns(false); + + // Act + var authUser = auth.Register( + "theUsername", + "thePassword", + "theEmailAddress"); + + // Assert + Assert.True(auth.Entities.Users.Contains(authUser.User)); + Assert.True(authUser.User.Confirmed); + auth.Entities.VerifyCommitChanges(); + } + + [Fact] + public void SetsAnApiKey() + { + // Arrange + var auth = Get(); + + // Arrange + var authUser = auth.Register( + "theUsername", + "thePassword", + "theEmailAddress"); + + // Assert + Assert.True(auth.Entities.Users.Contains(authUser.User)); + auth.Entities.VerifyCommitChanges(); + + var apiKeyCred = authUser.User.Credentials.FirstOrDefault(c => c.Type == CredentialTypes.ApiKeyV1); + Assert.NotNull(apiKeyCred); + } + + [Fact] + public void SetsAConfirmationToken() + { + // Arrange + var auth = Get(); + GetMock() + .Setup(c => c.ConfirmEmailAddresses) + .Returns(true); + + // Arrange + var authUser = auth.Register( + "theUsername", + "thePassword", + "theEmailAddress"); + + // Assert + Assert.True(auth.Entities.Users.Contains(authUser.User)); + auth.Entities.VerifyCommitChanges(); + + Assert.NotNull(authUser.User.EmailConfirmationToken); + Assert.False(authUser.User.Confirmed); + } + + [Fact] + public void SetsCreatedDate() + { + // Arrange + var auth = Get(); + + // Arrange + var authUser = auth.Register( + "theUsername", + "thePassword", + "theEmailAddress"); + + // Assert + Assert.True(auth.Entities.Users.Contains(authUser.User)); + auth.Entities.VerifyCommitChanges(); + + // Allow for up to 5 secs of time to have elapsed between Create call and now. Should be plenty + Assert.True((DateTime.UtcNow - authUser.User.CreatedUtc) < TimeSpan.FromSeconds(5)); + } + } + + public class TheReplaceCredentialMethod : TestContainer + { + [Fact] + public void ThrowsExceptionIfNoUserWithProvidedUserName() + { + // Arrange + var service = Get(); + + // Act + var ex = Assert.Throws(() => + service.ReplaceCredential("definitelyNotARealUser", new Credential())); + + // Assert + Assert.Equal(Strings.UserNotFound, ex.Message); + } + + [Fact] + public void AddsNewCredentialIfNoneWithSameTypeForUser() + { + // Arrange + var existingCred = new Credential("foo", "bar"); + var newCred = new Credential("baz", "boz"); + var user = Fakes.CreateUser("foo", existingCred); + var service = Get(); + service.Entities.Users.Add(user); + + // Act + service.ReplaceCredential(user.Username, newCred); + + // Assert + Assert.Equal(new[] { existingCred, newCred }, user.Credentials.ToArray()); + service.Entities.VerifyCommitChanges(); + } + + [Fact] + public void ReplacesExistingCredentialIfOneWithSameTypeExistsForUser() + { + // Arrange + var frozenCred = new Credential("foo", "bar"); + var existingCred = new Credential("baz", "bar"); + var newCred = new Credential("baz", "boz"); + var user = Fakes.CreateUser("foo", existingCred, frozenCred); + var service = Get(); + service.Entities.Users.Add(user); + + // Act + service.ReplaceCredential(user.Username, newCred); + + // Assert + Assert.Equal(new[] { frozenCred, newCred }, user.Credentials.ToArray()); + Assert.DoesNotContain(existingCred, service.Entities.Credentials); + service.Entities.VerifyCommitChanges(); + } + } + + public class TheResetPasswordWithTokenMethod : TestContainer + { + [Fact] + public void ReturnsFalseIfUserNotFound() + { + // Arrange + var authService = Get(); + + // Act + bool result = authService.ResetPasswordWithToken("definitelyAFakeUser", "some-token", "new-password"); + + // Assert + Assert.False(result); + } + + [Fact] + public void ThrowsExceptionIfUserNotConfirmed() + { + // Arrange + var user = new User + { + Username = "tempUser", + PasswordResetToken = "some-token", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) + }; + var authService = Get(); + authService.Entities.Users.Add(user); + + // Act/Assert + Assert.Throws(() => authService.ResetPasswordWithToken("tempUser", "some-token", "new-password")); + } + + [Fact] + public void ResetsPasswordCredential() + { + // Arrange + var oldCred = CredentialBuilder.CreatePbkdf2Password("thePassword"); + var user = new User + { + Username = "user", + EmailAddress = "confirmed@example.com", + PasswordResetToken = "some-token", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1), + Credentials = new List() { oldCred } + }; + + var authService = Get(); + authService.Entities.Users.Add(user); + + // Act + bool result = authService.ResetPasswordWithToken("user", "some-token", "new-password"); + + // Assert + Assert.True(result); + var newCred = user.Credentials.Single(); + Assert.Equal(CredentialTypes.Password.Pbkdf2, newCred.Type); + Assert.True(VerifyPasswordHash(newCred.Value, Constants.PBKDF2HashAlgorithmId, "new-password")); + authService.Entities.VerifyCommitChanges(); + } + + [Fact] + public void ResetsPasswordMigratesPasswordHash() + { + var oldCred = CredentialBuilder.CreateSha1Password("thePassword"); + var user = new User + { + Username = "user", + EmailAddress = "confirmed@example.com", + PasswordResetToken = "some-token", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1), + Credentials = new List() { oldCred } + }; + + var authService = Get(); + authService.Entities.Users.Add(user); + + bool result = authService.ResetPasswordWithToken("user", "some-token", "new-password"); + + // Assert + Assert.True(result); + var newCred = user.Credentials.Single(); + Assert.Equal(CredentialTypes.Password.Pbkdf2, newCred.Type); + Assert.True(VerifyPasswordHash(newCred.Value, Constants.PBKDF2HashAlgorithmId, "new-password")); + authService.Entities.VerifyCommitChanges(); + } + } + + public class TheGeneratePasswordResetTokenMethod : TestContainer + { + [Fact] + public void ReturnsNullIfEmailIsNotFound() + { + // Arrange + var authService = Get(); + + // Act + var token = authService.GeneratePasswordResetToken("nobody@nowhere.com", 1440); + + // Assert + Assert.Null(token); + } + + [Fact] + public void ThrowsExceptionIfUserIsNotConfirmed() + { + // Arrange + var user = new User("user") { UnconfirmedEmailAddress = "unique@example.com" }; + var authService = Get(); + authService.Entities.Users.Add(user); + + // Act/Assert + Assert.Throws(() => authService.GeneratePasswordResetToken(user.Username, 1440)); + } + + [Fact] + public void SetsPasswordResetTokenUsingEmail() + { + // Arrange + var user = new User + { + Username = "user", + EmailAddress = "unique@example.com", + PasswordResetToken = null + }; + var authService = Get(); + authService.Entities.Users.Add(user); + var currentDate = DateTime.UtcNow; + + // Act + var returnedUser = authService.GeneratePasswordResetToken(user.EmailAddress, 1440); + + // Assert + Assert.Same(user, returnedUser); + Assert.NotNull(user.PasswordResetToken); + Assert.NotEmpty(user.PasswordResetToken); + Assert.True(user.PasswordResetTokenExpirationDate >= currentDate.AddMinutes(1440)); + } + + [Fact] + public void WithExistingNotYetExpiredTokenReturnsExistingToken() + { + // Arrange + var user = new User + { + Username = "user", + EmailAddress = "unique@example.com", + PasswordResetToken = "existing-token", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) + }; + var authService = Get(); + authService.Entities.Users.Add(user); + + // Act + var returnedUser = authService.GeneratePasswordResetToken(user.EmailAddress, 1440); + + // Assert + Assert.Same(user, returnedUser); + Assert.Equal("existing-token", user.PasswordResetToken); + } + + [Fact] + public void WithExistingExpiredTokenReturnsNewToken() + { + // Arrange + var user = new User + { + Username = "user", + EmailAddress = "unique@example.com", + PasswordResetToken = "existing-token", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddMilliseconds(-1) + }; + var authService = Get(); + authService.Entities.Users.Add(user); + var currentDate = DateTime.UtcNow; + + // Act + var returnedUser = authService.GeneratePasswordResetToken(user.EmailAddress, 1440); + + // Assert + Assert.Same(user, returnedUser); + Assert.NotEmpty(user.PasswordResetToken); + Assert.NotEqual("existing-token", user.PasswordResetToken); + Assert.True(user.PasswordResetTokenExpirationDate >= currentDate.AddMinutes(1440)); + } + } } } \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs index ee8dbc7cc3..efbeef42c2 100644 --- a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs @@ -12,6 +12,7 @@ using Moq; using Newtonsoft.Json.Linq; using NuGet; +using NuGetGallery.Authentication; using NuGetGallery.Framework; using NuGetGallery.Packaging; using Xunit; @@ -29,6 +30,7 @@ class TestableApiController : ApiController public Mock MockContentService { get; private set; } public Mock MockStatisticsService { get; private set; } public Mock MockIndexingService { get; private set; } + public Mock MockAuthService { get; set; } private INupkg PackageFromInputStream { get; set; } @@ -41,6 +43,7 @@ public TestableApiController(MockBehavior behavior = MockBehavior.Default) ContentService = (MockContentService = new Mock()).Object; StatisticsService = (MockStatisticsService = new Mock()).Object; IndexingService = (MockIndexingService = new Mock()).Object; + AuthService = (MockAuthService = new Mock()).Object; MockPackageFileService = new Mock(MockBehavior.Strict); MockPackageFileService.Setup(p => p.SavePackageFileAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); @@ -77,7 +80,7 @@ public async Task CreatePackageWillSavePackageFileToFileStorage() var controller = new TestableApiController(); controller.MockPackageFileService.Setup(p => p.SavePackageFileAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)).Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(user); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(guid), user); controller.MockPackageService.Setup(p => p.FindPackageRegistrationById(It.IsAny())).Returns(packageRegistration); var nuGetPackage = new Mock(); @@ -99,15 +102,16 @@ public void WillFindTheUserThatMatchesTheApiKey() nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(new User()); + controller.MockAuthService.SetupAuth( + CredentialBuilder.CreateV1ApiKey(apiKey), new User()).Verifiable(); controller.SetupPackageFromInputStream(nuGetPackage); - var apiKey = Guid.NewGuid(); - + controller.CreatePackagePut(apiKey.ToString()); - controller.MockUserService.Verify(x => x.FindByApiKey(apiKey)); + controller.MockAuthService.VerifyAll(); } [Fact] @@ -126,13 +130,14 @@ public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionA Owners = new List { user } }; + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageRegistrationById("theId")).Returns(packageRegistration); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(user); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); controller.SetupPackageFromInputStream(nuGetPackage); // Act - var result = await controller.CreatePackagePut(Guid.NewGuid().ToString()); + var result = await controller.CreatePackagePut(apiKey.ToString()); // Assert ResultAssert.IsStatusCode( @@ -142,7 +147,7 @@ public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionA } [Fact] - public async Task WillFindUserUsingFindByApiKey() + public async Task WillFindUserUsingAuthenticate() { var nuGetPackage = new Mock(); nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); @@ -152,7 +157,7 @@ public async Task WillFindUserUsingFindByApiKey() var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(user); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); controller.SetupPackageFromInputStream(nuGetPackage); ResultAssert.IsStatusCode( @@ -163,63 +168,6 @@ await controller.CreatePackagePut(apiKey.ToString()), p.CreatePackage(nuGetPackage.Object, user, true)); } - [Fact] - public async Task WillFindUserUsingAuthenticateCredential() - { - var nuGetPackage = new Mock(); - nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); - nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - - var user = new User(); - var apiKey = Guid.NewGuid(); - - var controller = new TestableApiController(); - controller.MockUserService.Setup( - x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = user }); - controller.SetupPackageFromInputStream(nuGetPackage); - - ResultAssert.IsStatusCode( - await controller.CreatePackagePut(apiKey.ToString().ToUpperInvariant()), - HttpStatusCode.Created); - - controller.MockPackageService.Verify(p => - p.CreatePackage(nuGetPackage.Object, user, true)); - } - - [Fact] - public async Task WillUseUserFoundByAuthenticateCredentialOverFindByApiKey() - { - var nuGetPackage = new Mock(); - nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); - nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - - var correctUser = new User(); - var incorrectUser = new User(); - var apiKey = Guid.NewGuid(); - - var controller = new TestableApiController(); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = correctUser }); - controller.MockUserService - .Setup(x => x.FindByApiKey(apiKey)) - .Returns(incorrectUser); - - controller.SetupPackageFromInputStream(nuGetPackage); - - ResultAssert.IsStatusCode( - await controller.CreatePackagePut(apiKey.ToString().ToUpperInvariant()), - HttpStatusCode.Created); - - controller.MockPackageService.Verify(p => - p.CreatePackage(nuGetPackage.Object, correctUser, true)); - } - [Fact] public void WillCreateAPackageFromTheNuGetPackage() { @@ -228,10 +176,11 @@ public void WillCreateAPackageFromTheNuGetPackage() nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(matchingUser); + var apiKey = Guid.NewGuid(); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); controller.SetupPackageFromInputStream(nuGetPackage); - controller.CreatePackagePut(Guid.NewGuid().ToString()); + controller.CreatePackagePut(apiKey.ToString()); controller.MockPackageService.Verify(x => x.CreatePackage(nuGetPackage.Object, It.IsAny(), true)); } @@ -244,10 +193,11 @@ public void WillCreateAPackageWithTheUserMatchingTheApiKey() nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(matchingUser); + var apiKey = Guid.NewGuid(); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); controller.SetupPackageFromInputStream(nuGetPackage); - controller.CreatePackagePut(Guid.NewGuid().ToString()); + controller.CreatePackagePut(apiKey.ToString()); controller.MockPackageService.Verify(x => x.CreatePackage(It.IsAny(), matchingUser, true)); } @@ -261,14 +211,15 @@ public void CreatePackageRefreshesNuGetExeIfCommandLinePackageIsUploaded() nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); + var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny(), true)) .Returns(new Package { IsLatestStable = true }); controller.MockNuGetExeDownloaderService.Setup(s => s.UpdateExecutableAsync(nuGetPackage.Object)).Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(matchingUser); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); controller.SetupPackageFromInputStream(nuGetPackage); // Act - controller.CreatePackagePut(Guid.NewGuid().ToString()); + controller.CreatePackagePut(apiKey.ToString()); // Assert controller.MockNuGetExeDownloaderService.Verify(); @@ -283,14 +234,15 @@ public void CreatePackageDoesNotRefreshNuGetExeIfItIsNotLatestStable() nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("2.0.0-alpha")); var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); + var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny(), true)) .Returns(new Package { IsLatest = true, IsLatestStable = false }); controller.MockNuGetExeDownloaderService.Setup(s => s.UpdateExecutableAsync(nuGetPackage.Object)).Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(It.IsAny())).Returns(matchingUser); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); controller.SetupPackageFromInputStream(nuGetPackage); // Act - controller.CreatePackagePut(Guid.NewGuid().ToString()); + controller.CreatePackagePut(apiKey.ToString()); // Assert controller.MockNuGetExeDownloaderService.Verify(s => s.UpdateExecutableAsync(It.IsAny()), Times.Never()); @@ -322,8 +274,8 @@ public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns((Package)null); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(new User()); - + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); + var result = controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42"); Assert.IsType(result); @@ -336,14 +288,14 @@ public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() [Fact] public void WillNotDeleteThePackageIfApiKeyDoesNotBelongToAnOwner() { - var owner = new User { Key = 1 }; + var notOwner = new User { Key = 1 }; var package = new Package { PackageRegistration = new PackageRegistration { Owners = new[] { new User() } } }; var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), notOwner); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns(package); var result = controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42"); @@ -356,7 +308,7 @@ public void WillNotDeleteThePackageIfApiKeyDoesNotBelongToAnOwner() } [Fact] - public void WillUnlistThePackageIfApiKeyBelongsToAnOwnerUsingFindByApiKey() + public void WillUnlistThePackageIfApiKeyBelongsToAnOwner() { var apiKey = Guid.NewGuid(); var owner = new User { Key = 1, ApiKey = apiKey }; @@ -366,93 +318,13 @@ public void WillUnlistThePackageIfApiKeyBelongsToAnOwnerUsingFindByApiKey() }; var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); - - ResultAssert.IsEmpty(controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42")); - - controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(package, true)); - controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); - } - - [Fact] - public void WillUnlistThePackageIfApiKeyBelongsToAnOwnerUsingAuthenticateCredential() - { - var apiKey = Guid.NewGuid(); - var owner = new Credential() { User = new User { Key = 1 } }; - var package = new Package - { - PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner.User } } - }; - var controller = new TestableApiController(); - controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(owner); - - ResultAssert.IsEmpty(controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42")); - - controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(package, true)); - controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); - } - - [Fact] - public void WillUseUserFromAuthenticateCredentialOverFindByApiKey() - { - var apiKey = Guid.NewGuid(); - var owner = new Credential() { User = new User { Key = 1 } }; - var nonOwner = new User() { ApiKey = apiKey }; - var package = new Package - { - PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner.User } } - }; - var controller = new TestableApiController(); - controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(nonOwner); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(owner); - + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); + ResultAssert.IsEmpty(controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42")); controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(package, true)); controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); } - - [Fact] - public void WillFailIfUserFromAuthenticateCredentialIsNotOwner() - { - // Arrange - var apiKey = Guid.NewGuid(); - var nonOwner = new Credential() { User = new User { Key = 1 } }; - var owner = new User() { ApiKey = apiKey }; - var package = new Package - { - PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner } } - }; - var controller = new TestableApiController(); - controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(nonOwner); - - // Act - var result = controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42"); - - // Assert - ResultAssert.IsStatusCode( - result, - HttpStatusCode.Forbidden, - String.Format(Strings.ApiKeyNotAuthorized, "delete")); - - controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(package, true), Times.Never()); - } } public class TheGetPackageAction @@ -479,11 +351,11 @@ public async Task GetPackageReturns400ForEvilPackageVersion() public async Task GetPackageReturns404IfPackageIsNotFound() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(MockBehavior.Strict); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("Baz", "1.0.1", false)).Returns((Package)null).Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(guid)).Returns(new User()); - + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); + // Act var result = await controller.GetPackage("Baz", "1.0.1"); @@ -499,7 +371,7 @@ public async Task GetPackageReturnsPackageIfItExists() { // Arrange const string PackageId = "Baz"; - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var package = new Package() { Version = "1.0.01", NormalizedVersion = "1.0.1" }; var actionResult = new EmptyResult(); var controller = new TestableApiController(MockBehavior.Strict); @@ -508,7 +380,7 @@ public async Task GetPackageReturnsPackageIfItExists() controller.MockPackageFileService.Setup(s => s.CreateDownloadPackageActionResultAsync(HttpRequestUrl, PackageId, package.NormalizedVersion)) .Returns(Task.FromResult(actionResult)) .Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(guid)).Returns(new User()); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); NameValueCollection headers = new NameValueCollection(); headers.Add("NuGet-Operation", "Install"); @@ -538,7 +410,7 @@ public async Task GetPackageReturnsPackageIfItExists() public async Task GetPackageReturnsSpecificPackageEvenIfDatabaseIsOffline() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var package = new Package(); var actionResult = new EmptyResult(); @@ -576,7 +448,6 @@ public async Task GetPackageReturnsLatestPackageIfNoVersionIsProvided() { // Arrange const string PackageId = "Baz"; - var guid = Guid.NewGuid(); var package = new Package() { Version = "1.2.0408", NormalizedVersion = "1.2.408" }; var actionResult = new EmptyResult(); var controller = new TestableApiController(MockBehavior.Strict); @@ -586,7 +457,6 @@ public async Task GetPackageReturnsLatestPackageIfNoVersionIsProvided() controller.MockPackageFileService.Setup(s => s.CreateDownloadPackageActionResultAsync(HttpRequestUrl, PackageId, package.NormalizedVersion)) .Returns(Task.FromResult(actionResult)) .Verifiable(); - controller.MockUserService.Setup(x => x.FindByApiKey(guid)).Returns(new User()); NameValueCollection headers = new NameValueCollection(); headers.Add("NuGet-Operation", "Install"); @@ -616,14 +486,11 @@ public async Task GetPackageReturnsLatestPackageIfNoVersionIsProvided() public async Task GetPackageReturns503IfNoVersionIsProvidedAndDatabaseUnavailable() { // Arrange - var guid = Guid.NewGuid(); var package = new Package(); var actionResult = new EmptyResult(); var controller = new TestableApiController(MockBehavior.Strict); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("Baz", "", false)).Throws(new DataException("Oh noes, database broked!")); - controller.MockUserService.Setup(x => x.FindByApiKey(guid)).Returns(new User()); - NameValueCollection headers = new NameValueCollection(); headers.Add("NuGet-Operation", "Install"); @@ -647,33 +514,10 @@ public async Task GetPackageReturns503IfNoVersionIsProvidedAndDatabaseUnavailabl controller.MockPackageService.Verify(); controller.MockUserService.Verify(); } - - [Fact] - public void WillThrowIfTheApiKeyDoesNotExist() - { - // Arrange - var controller = new TestableApiController(); - var apiKey = Guid.NewGuid(); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).ReturnsNull(); - controller.MockPackageService - .Setup(p => p.FindPackageByIdAndVersion("theId", "1.0.42", true)) - .Returns(new Package()); - - // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); - - // Assert - ResultAssert.IsStatusCode( - result, - HttpStatusCode.Forbidden, - String.Format(Strings.ApiKeyNotAuthorized, "publish")); - controller.MockPackageService.Verify(x => x.MarkPackageListed(It.IsAny(), It.IsAny()), Times.Never()); - } } public class ThePublishPackageAction { - [Fact] public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() { @@ -681,7 +525,7 @@ public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns((Package)null); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(new User()); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); // Act var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); @@ -707,7 +551,7 @@ public void WillNotListThePackageIfApiKeyDoesNotBelongToAnOwner() var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); // Act var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); @@ -722,7 +566,7 @@ public void WillNotListThePackageIfApiKeyDoesNotBelongToAnOwner() } [Fact] - public void WillListThePackageIfApiKeyBelongsToAnOwnerUsingFindByApiKey() + public void WillListThePackageIfApiKeyBelongsToAnOwner() { // Arrange var apiKey = Guid.NewGuid(); @@ -734,7 +578,7 @@ public void WillListThePackageIfApiKeyBelongsToAnOwnerUsingFindByApiKey() var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); // Act var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); @@ -744,90 +588,23 @@ public void WillListThePackageIfApiKeyBelongsToAnOwnerUsingFindByApiKey() controller.MockPackageService.Verify(x => x.MarkPackageListed(package, It.IsAny())); controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); } - - [Fact] - public void WillListThePackageIfApiKeyBelongsToAnOwnerUsingAuthorizeCredential() - { - // Arrange - - var apiKey = Guid.NewGuid(); - var owner = new Credential { User = new User { Key = 1 } }; - var package = new Package - { - PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner.User } } - }; - - var controller = new TestableApiController(); - controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(owner); - - // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); - - // Assert - ResultAssert.IsEmpty(result); - controller.MockPackageService.Verify(x => x.MarkPackageListed(package, It.IsAny())); - controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); - } - - [Fact] - public void WillFailIfUserFromAuthenticateCredentialIsNotOwner() - { - // Arrange - var apiKey = Guid.NewGuid(); - var nonOwner = new Credential { User = new User { Key = 1 } }; - var owner = new User(); - var package = new Package - { - PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner } } - }; - - var controller = new TestableApiController(); - controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockUserService.Setup(x => x.FindByApiKey(apiKey)).Returns(owner); - controller.MockUserService - .Setup(x => x.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(nonOwner); - - // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); - - // Assert - ResultAssert.IsStatusCode( - result, - HttpStatusCode.Forbidden, - String.Format(Strings.ApiKeyNotAuthorized, "publish")); - - controller.MockPackageService.Verify(x => x.MarkPackageListed(package, It.IsAny()), Times.Never()); - } } public class TheVerifyPackageKeyAction : TestContainer { [Fact] - public void VerifyPackageKeyReturns403IfUserDoesNotExistByFindByApiKeyOrAuthorizeCredential() + public void VerifyPackageKeyReturns403IfUserDoesNotExist() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockUserService.Setup(s => s.FindByApiKey(guid)).Returns(null); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .ReturnsNull(); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), null); controller.MockPackageService .Setup(p => p.FindPackageByIdAndVersion("foo", "1.0.0", true)) .Returns(new Package()); // Act - var result = controller.VerifyPackageKey(guid.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); // Assert ResultAssert.IsStatusCode( @@ -837,39 +614,15 @@ public void VerifyPackageKeyReturns403IfUserDoesNotExistByFindByApiKeyOrAuthoriz } [Fact] - public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsInUserRecordAndIdAndVersionAreEmpty() + public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsButIdAndVersionAreEmpty() { // Arrange - var guid = Guid.NewGuid(); - var controller = new TestableApiController(); - controller.MockUserService.Setup(s => s.FindByApiKey(guid)).Returns(new User()); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .ReturnsNull(); - - // Act - var result = controller.VerifyPackageKey(guid.ToString(), null, null); - - // Assert - ResultAssert.IsEmpty(result); - } - - [Fact] - public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsInCredentialsAndIdAndVersionAreEmpty() - { - // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = new User() }); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); // Act - var result = controller.VerifyPackageKey(guid.ToString(), null, null); + var result = controller.VerifyPackageKey(apiKey.ToString(), null, null); // Assert ResultAssert.IsEmpty(result); @@ -879,18 +632,17 @@ public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsInCredentialsAndIdAn public void VerifyPackageKeyReturns404IfPackageDoesNotExist() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var controller = GetController(); var user = new User { EmailAddress = "confirmed@email.com" }; - GetMock() - .Setup(s => s.FindByApiKey(guid)) - .Returns(user); + GetMock() + .SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); GetMock() .Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)) .ReturnsNull(); // Act - var result = controller.VerifyPackageKey(guid.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); // Assert ResultAssert.IsStatusCode( @@ -900,24 +652,19 @@ public void VerifyPackageKeyReturns404IfPackageDoesNotExist() } [Fact] - public void VerifyPackageKeyReturns403IfUserInCredentialsTableIsNotAnOwner() + public void VerifyPackageKeyReturns403IfUserIsNotAnOwner() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); var owner = new User(); var nonOwner = new User(); - controller.MockUserService.Setup(s => s.FindByApiKey(guid)).Returns(owner); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = nonOwner }); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), nonOwner); controller.MockPackageService.Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)).Returns( new Package { PackageRegistration = new PackageRegistration() }); // Act - var result = controller.VerifyPackageKey(guid.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); // Assert ResultAssert.IsStatusCode( @@ -927,48 +674,19 @@ public void VerifyPackageKeyReturns403IfUserInCredentialsTableIsNotAnOwner() } [Fact] - public void VerifyPackageKeyReturns200IfUserHasNoCredentialRecordButIsAnOwner() - { - // Arrange - var guid = Guid.NewGuid(); - var user = new User { EmailAddress = "confirmed@email.com" }; - var package = new Package { PackageRegistration = new PackageRegistration() }; - package.PackageRegistration.Owners.Add(user); - var controller = new TestableApiController(); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .ReturnsNull(); - controller.MockUserService.Setup(s => s.FindByApiKey(guid)).Returns(user); - controller.MockPackageService.Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)).Returns(package); - - // Act - var result = controller.VerifyPackageKey(guid.ToString(), "foo", "1.0.0"); - - // Assert - ResultAssert.IsEmpty(result); - } - - [Fact] - public void VerifyPackageKeyReturns200IfUserHasCredentialRecordAndIsAnOwner() + public void VerifyPackageKeyReturns200IfUserIsAnOwner() { // Arrange - var guid = Guid.NewGuid(); + var apiKey = Guid.NewGuid(); var user = new User(); var package = new Package { PackageRegistration = new PackageRegistration() }; package.PackageRegistration.Owners.Add(user); var controller = new TestableApiController(); - controller.MockUserService - .Setup(s => s.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - guid.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = user }); - controller.MockUserService.Setup(s => s.FindByApiKey(guid)).ReturnsNull(); + controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); controller.MockPackageService.Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)).Returns(package); // Act - var result = controller.VerifyPackageKey(guid.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); // Assert ResultAssert.IsEmpty(result); diff --git a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs index 81d0aef27f..643722fcc2 100644 --- a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs @@ -5,6 +5,8 @@ using Xunit; using System.Net.Mail; using NuGetGallery.Framework; +using NuGetGallery.Authentication; +using Microsoft.Owin; namespace NuGetGallery.Controllers { @@ -16,10 +18,13 @@ public class TheLogOffAction : TestContainer public void WillLogTheUserOff() { var controller = GetController(); + var mockContext = new Mock(); + mockContext.Setup(c => c.Authentication.SignOut()).Verifiable(); + controller.OwinContext = mockContext.Object; controller.LogOff("theReturnUrl"); - GetMock().Verify(x => x.SignOut()); + mockContext.VerifyAll(); } [Fact] @@ -51,93 +56,77 @@ public void WillShowTheViewWithErrorsIfTheModelStateIsInvalid() [Fact] public void CanLogTheUserOnWithUserName() { + // Arrange + var authUser = new AuthenticatedUser( + new User("theUsername") { EmailAddress = "confirmed@example.com" }, + new Credential() { Type = "Foo" }); + GetMock() + .Setup(x => x.Authenticate(authUser.User.Username, "thePassword")) + .Returns(authUser); var controller = GetController(); - var user = new User("theUsername") { EmailAddress = "confirmed@example.com" }; - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) - .Returns(user); - + + // Act controller.SignIn( - new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + new SignInRequest { UserNameOrEmail = authUser.User.Username, Password = "thePassword" }, "theReturnUrl"); - GetMock().Verify( - x => x.SetAuthCookie( - "theUsername", - true, - null)); + // Assert + GetMock() + .Verify(a => a.CreateSession(It.IsAny(), authUser)); } [Fact] public void CanLogTheUserOnWithEmailAddress() { + // Arrange + var authUser = new AuthenticatedUser( + new User("theUsername") { EmailAddress = "confirmed@example.com" }, + new Credential() { Type = "Foo" }); + GetMock() + .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) + .Returns(authUser); var controller = GetController(); - var user = new User("theUsername") { EmailAddress = "confirmed@example.com" }; - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword("confirmed@example.com", "thePassword")) - .Returns(user); - + + // Act controller.SignIn( new SignInRequest { UserNameOrEmail = "confirmed@example.com", Password = "thePassword" }, "theReturnUrl"); - GetMock().Verify( - x => x.SetAuthCookie( - "theUsername", - true, - null)); + // Assert + GetMock() + .Verify(a => a.CreateSession(It.IsAny(), authUser)); } [Fact] public void WillLogTheUserOnWithUsernameEvenWithoutConfirmedEmailAddress() { + // Arrange + var authUser = new AuthenticatedUser( + new User("theUsername") { UnconfirmedEmailAddress = "unconfirmed@example.com" }, + new Credential() { Type = "Foo" }); + GetMock() + .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) + .Returns(authUser); var controller = GetController(); - var user = new User { Key = 42, Username = "theUsername", UnconfirmedEmailAddress = "anAddress@email.org" }; - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) - .Returns(user); - - controller.SignIn( - new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, - "theReturnUrl"); - - GetMock() - .Verify( - x => x.SetAuthCookie(It.IsAny(), It.IsAny(), It.IsAny>())); - } - - [Fact] - public void WillLogTheUserOnWithRoles() - { - var controller = GetController(); - var user = new User("theUsername") - { - Roles = new[] { new Role { Name = "Administrators" } }, - EmailAddress = "confirmed@example.com" - }; - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword")) - .Returns(user); - + + // Act controller.SignIn( new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, "theReturnUrl"); - GetMock().Verify( - x => x.SetAuthCookie( - "theUsername", - true, - new[] { "Administrators" })); + // Assert + GetMock() + .Verify(a => a.CreateSession(It.IsAny(), authUser)); } [Fact] public void WillInvalidateModelStateAndShowTheViewWithErrorsWhenTheUsernameAndPasswordAreNotValid() { - var controller = GetController(); - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword(It.IsAny(), It.IsAny())) + GetMock() + .Setup(x => x.Authenticate(It.IsAny(), It.IsAny())) .ReturnsNull(); - + var controller = GetController(); + var result = controller.SignIn(new SignInRequest(), "theReturnUrl") as ViewResult; Assert.NotNull(result); @@ -147,17 +136,40 @@ public void WillInvalidateModelStateAndShowTheViewWithErrorsWhenTheUsernameAndPa } [Fact] - public void WillRedirectToTheReturnUrl() + public void WillRedirectToHomeIfReturnUrlNotLocal() { + var authUser = new AuthenticatedUser( + new User("theUsername") { UnconfirmedEmailAddress = "unconfirmed@example.com" }, + new Credential() { Type = "Foo" }); + GetMock() + .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) + .Returns(authUser); var controller = GetController(); - GetMock() - .Setup(x => x.FindByUsernameOrEmailAddressAndPassword(It.IsAny(), It.IsAny())) - .Returns(new User("theUsername") { EmailAddress = "confirmed@example.com" }); - - var result = controller.SignIn(new SignInRequest(), "theReturnUrl"); + + var result = controller.SignIn( + new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + "http://www.microsoft.com"); ResultAssert.IsRedirectTo(result, "/"); } + + [Fact] + public void WillRedirectToTheReturnUrlIfLocal() + { + var authUser = new AuthenticatedUser( + new User("theUsername") { UnconfirmedEmailAddress = "unconfirmed@example.com" }, + new Credential() { Type = "Foo" }); + GetMock() + .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) + .Returns(authUser); + var controller = GetController(); + + var result = controller.SignIn( + new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + "/packages/upload"); + + ResultAssert.IsRedirectTo(result, "/packages/upload"); + } } public class TheRegisterAction : TestContainer @@ -177,14 +189,14 @@ public void WillShowTheViewWithErrorsIfTheModelStateIsInvalid() } [Fact] - public void WillCreateTheUser() + public void WillCreateAndLogInTheUser() { + var authUser = new AuthenticatedUser(new User("theUsername"), new Credential()); + GetMock() + .Setup(x => x.Register(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(authUser); var controller = GetController(); - var user = new User("theUsername"); - GetMock() - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(user); - + controller.Register( new RegisterRequest { @@ -193,18 +205,20 @@ public void WillCreateTheUser() EmailAddress = "theEmailAddress", }, null); - GetMock() - .Verify(x => x.Create("theUsername", "thePassword", "theEmailAddress")); + GetMock() + .Verify(x => x.Register("theUsername", "thePassword", "theEmailAddress")); + GetMock() + .Verify(x => x.CreateSession(It.IsAny(), authUser)); } [Fact] public void WillInvalidateModelStateAndShowTheViewWhenAnEntityExceptionIsThrow() { - var controller = GetController(); - GetMock() - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) + GetMock() + .Setup(x => x.Register(It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new EntityException("aMessage")); - + var controller = GetController(); + var request = new RegisterRequest { Username = "theUsername", @@ -221,12 +235,12 @@ public void WillInvalidateModelStateAndShowTheViewWhenAnEntityExceptionIsThrow() [Fact] public void WillRedirectToTheReturnUrl() { - var controller = GetController(); var user = new User("theUsername") { UnconfirmedEmailAddress = "unconfirmed@example.com" }; - GetMock() - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(user); - + GetMock() + .Setup(x => x.Register("theUsername", "thepassword", "unconfirmed@example.com")) + .Returns(new AuthenticatedUser(user, new Credential())); + var controller = GetController(); + var result = controller.Register(new RegisterRequest { EmailAddress = "unconfirmed@example.com", diff --git a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs index c8c274da68..bdd476eac6 100644 --- a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs @@ -6,6 +6,7 @@ using System.Web; using System.Web.Mvc; using Moq; +using NuGetGallery.Authentication; using NuGetGallery.Configuration; using NuGetGallery.Framework; using Xunit; @@ -23,15 +24,15 @@ public void WillGetTheCurrentUserUsingTheRequestIdentityName() var user = new User { Username = "theUsername" }; controller.SetUser(user); GetMock() - .Setup(s => s.FindByUsername(It.IsAny())) - .Returns(user); + .Setup(s => s.FindByUsername(It.IsAny())) + .Returns(user); //act controller.Account(); // verify GetMock() - .Verify(stub => stub.FindByUsername("theUsername")); + .Verify(stub => stub.FindByUsername("theUsername")); } [Fact] @@ -184,7 +185,7 @@ public void SendsEmailWithPasswordResetUrl() GetMock() .Setup(s => s.FindByEmailAddress(It.IsAny())) .Returns(user); - GetMock() + GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns(user); var model = new ForgotPasswordViewModel { Email = "user" }; @@ -200,7 +201,7 @@ public void RedirectsAfterGeneratingToken() { var user = new User { EmailAddress = "some@example.com", Username = "somebody" }; var controller = GetController(); - GetMock() + GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns(user) .Verifiable(); @@ -209,7 +210,7 @@ public void RedirectsAfterGeneratingToken() var result = controller.ForgotPassword(model) as RedirectToRouteResult; Assert.NotNull(result); - GetMock() + GetMock() .Verify(s => s.GeneratePasswordResetToken("user", 1440)); } @@ -217,7 +218,7 @@ public void RedirectsAfterGeneratingToken() public void ReturnsSameViewIfTokenGenerationFails() { var controller = GetController(); - GetMock() + GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns((User)null); @@ -256,7 +257,7 @@ public void PutsNewCredentialInOldField() .Setup(u => u.FindByUsername("the-username")) .Returns(user); Credential created = null; - GetMock() + GetMock() .Setup(u => u.ReplaceCredential(user, It.IsAny())) .Callback((_, c) => created = c); @@ -281,7 +282,7 @@ public void ReplacesTheApiKeyCredential() controller.GenerateApiKey(); - GetMock() + GetMock() .Verify(u => u.ReplaceCredential( user, It.Is(c => c.Type == CredentialTypes.ApiKeyV1))); @@ -302,9 +303,9 @@ public void DoesNotLetYouUseSomeoneElsesConfirmedEmailAddress() var controller = GetController(); controller.SetUser(user); - GetMock() - .Setup(u => u.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) - .Returns(user); + GetMock() + .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Throws(new EntityException("msg")); @@ -327,9 +328,9 @@ public void SendsEmailChangeConfirmationNoticeWhenChangingAConfirmedEmailAddress var controller = GetController(); controller.SetUser(user); - GetMock() - .Setup(u => u.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) - .Returns(user); + GetMock() + .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "token")); @@ -352,9 +353,9 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenAddressDoesntChange() var controller = GetController(); controller.SetUser(user); - GetMock() - .Setup(u => u.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) - .Returns(user); + GetMock() + .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("old@example.com", () => "new-token")); @@ -380,9 +381,9 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenUserWasNotConfirmed() var controller = GetController(); controller.SetUser(user); - GetMock() - .Setup(u => u.FindByUsernameAndPassword(It.IsAny(), It.IsAny())) - .Returns(user); + GetMock() + .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "new-token")); @@ -605,9 +606,6 @@ public void WillSendNewUserEmailWhenPosted() GetMock() .Setup(x => x.FindByUsername(It.IsAny())) .Returns(user); - GetMock() - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(user); GetMock() .Setup(m => m.SendNewAccountEmail(It.IsAny(), It.IsAny())) .Callback((to, url) => @@ -647,12 +645,13 @@ public void ReturnsViewIfModelStateInvalid() public void AddsModelErrorIfUserServiceFails() { // Arrange - var controller = GetController(); - controller.SetUser("user"); - GetMock() + GetMock() .Setup(u => u.ChangePassword("user", "old", "new")) .Returns(false); - + + var controller = GetController(); + controller.SetUser("user"); + var inputModel = new PasswordChangeViewModel() { OldPassword = "old", @@ -679,11 +678,11 @@ public void AddsModelErrorIfUserServiceFails() public void RedirectsToPasswordChangedIfUserServiceSucceeds() { // Arrange - var controller = GetController(); - controller.SetUser("user"); - GetMock() + GetMock() .Setup(u => u.ChangePassword("user", "old", "new")) .Returns(true); + var controller = GetController(); + controller.SetUser("user"); var inputModel = new PasswordChangeViewModel() { OldPassword = "old", @@ -708,10 +707,10 @@ public class TheResetPasswordMethod : TestContainer [Fact] public void ShowsErrorIfTokenExpired() { - var controller = GetController(); - GetMock() + GetMock() .Setup(u => u.ResetPasswordWithToken("user", "token", "newpwd")) .Returns(false); + var controller = GetController(); var model = new PasswordResetViewModel { ConfirmPassword = "pwd", @@ -721,17 +720,17 @@ public void ShowsErrorIfTokenExpired() controller.ResetPassword("user", "token", model); Assert.Equal("The Password Reset Token is not valid or expired.", controller.ModelState[""].Errors[0].ErrorMessage); - GetMock() + GetMock() .Verify(u => u.ResetPasswordWithToken("user", "token", "newpwd")); } [Fact] public void ResetsPasswordForValidToken() { - var controller = GetController(); - GetMock() + GetMock() .Setup(u => u.ResetPasswordWithToken("user", "token", "newpwd")) .Returns(true); + var controller = GetController(); var model = new PasswordResetViewModel { ConfirmPassword = "pwd", @@ -741,7 +740,7 @@ public void ResetsPasswordForValidToken() var result = controller.ResetPassword("user", "token", model) as RedirectToRouteResult; Assert.NotNull(result); - GetMock() + GetMock() .Verify(u => u.ResetPasswordWithToken("user", "token", "newpwd")); } } @@ -751,11 +750,11 @@ public class TheThanksMethod : TestContainer [Fact] public void ShowsDefaultThanksView() { - var controller = GetController(); GetMock() .Setup(x => x.ConfirmEmailAddresses) .Returns(true); - + var controller = GetController(); + var result = controller.Thanks() as ViewResult; Assert.Empty(result.ViewName); @@ -765,11 +764,11 @@ public void ShowsDefaultThanksView() [Fact] public void ShowsConfirmViewWithModelWhenConfirmingEmailAddressIsNotRequired() { - var controller = GetController(); GetMock() .Setup(x => x.ConfirmEmailAddresses) .Returns(false); - + var controller = GetController(); + ResultAssert.IsView(controller.Thanks()); } } diff --git a/tests/NuGetGallery.Facts/Filters/ApiKeyAuthorizeAttributeFacts.cs b/tests/NuGetGallery.Facts/Filters/ApiKeyAuthorizeAttributeFacts.cs deleted file mode 100644 index 851074a3f7..0000000000 --- a/tests/NuGetGallery.Facts/Filters/ApiKeyAuthorizeAttributeFacts.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Web.Mvc; -using System.Web.Routing; -using Moq; -using NuGetGallery.Framework; -using Xunit; -using Xunit.Extensions; - -namespace NuGetGallery.Filters -{ - public class ApiKeyAuthorizeAttributeFacts : TestContainer - { - [Fact] - public void UsesCredentialTableToFindUser() - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - var apiKey = Guid.NewGuid(); - var mockFilterContext = CreateActionFilterContext(apiKey.ToString()); - - GetMock() - .Setup(us => us.AuthenticateCredential( - CredentialTypes.ApiKeyV1, - apiKey.ToString().ToLowerInvariant())) - .Returns(new Credential() { User = Fakes.Owner }); - - // Act - attribute.OnActionExecuting(mockFilterContext.Object); - - // Assert - Assert.Null(mockFilterContext.Object.Result); - } - - [Fact] - public void UsesApiKeyColumnToFindUserIfNoRecordInCredentialTable() - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - var apiKey = Guid.NewGuid(); - var mockFilterContext = CreateActionFilterContext(apiKey.ToString()); - - GetMock() - .Setup(us => us.FindByApiKey(apiKey)) - .Returns(Fakes.Owner); - - // Act - attribute.OnActionExecuting(mockFilterContext.Object); - - // Assert - Assert.Null(mockFilterContext.Object.Result); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void ApiKeyAuthorizeAttributeReturns400WhenApiKeyIsMissing(string value) - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - - // Act - var result = attribute.CheckForResult(value); - - // Assert - ResultAssert.IsStatusCode(result, 400, String.Format(Strings.InvalidApiKey, "")); - } - - [Fact] - public void ApiKeyAuthorizeAttributeReturns400WhenApiKeyFormatIsInvalid() - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - - // Act - var result = attribute.CheckForResult("invalid-key"); - - // Assert - ResultAssert.IsStatusCode(result, 400, String.Format(Strings.InvalidApiKey, "invalid-key")); - } - - [Fact] - public void ApiKeyAuthorizeAttributeReturns403WhenApiKeyDoesNotBelongToAUser() - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - string unknownApiKey = Guid.NewGuid().ToString(); - - // Act - var result = attribute.CheckForResult(unknownApiKey); - - // Assert - ResultAssert.IsStatusCode(result, 403, String.Format(Strings.ApiKeyNotAuthorized, "push")); - } - - [Fact] - public void ApiKeyAuthorizeAttributeReturns403WhenUserIsNotYetConfirmed() - { - ApiKeyAuthorizeAttribute attribute = CreateAttribute(); - var user = new User - { - UnconfirmedEmailAddress = "unconfirmed@example.com", - ApiKey = Guid.NewGuid() - }; - - GetMock() - .Setup(us => us.FindByApiKey(user.ApiKey)) - .Returns(user); - - // Act - var result = attribute.CheckForResult(user.ApiKey.ToString()); - - // Assert - ResultAssert.IsStatusCode(result, 403, Strings.ApiKeyUserAccountIsUnconfirmed); - } - - private static Mock CreateActionFilterContext(string apiKey) - { - var mockFilterContext = new Mock(); - var mockController = new Mock(); - - mockFilterContext.Setup(ctx => ctx.Controller).Returns(mockController.Object); - mockController.Object.ControllerContext = new ControllerContext - { - RouteData = new RouteData - { - Values = { { "apiKey", apiKey } } - } - }; - return mockFilterContext; - } - - private ApiKeyAuthorizeAttribute CreateAttribute() - { - ApiKeyAuthorizeAttribute attribute = Get(); - attribute.UserService = Get(); - return attribute; - } - } -} \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 44ae04a638..8779f45049 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -16,13 +16,25 @@ public static class Fakes public static readonly User User = new User("testUser") { Key = 42, + EmailAddress = "confirmed1@example.com", Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password), CredentialBuilder.CreateV1ApiKey(Guid.Parse("519e180e-335c-491a-ac26-e83c4bd31d65")) - } + } + }; + + public static readonly User ShaUser = new User("testShaUser") + { + Key = 42, + EmailAddress = "confirmed2@example.com", + Credentials = new List() { + CredentialBuilder.CreateSha1Password(Password), + CredentialBuilder.CreateV1ApiKey(Guid.Parse("b9704a41-4107-4cd2-bcfa-70d84e021ab2")) + } }; public static readonly User Admin = new User("testAdmin") { Key = 43, + EmailAddress = "confirmed3@example.com", Credentials = new List() { CredentialBuilder.CreatePbkdf2Password(Password) }, Roles = new List() { new Role() { Name = Constants.AdminRoleName } } }; @@ -42,6 +54,14 @@ public static class Fakes } }; + public static User CreateUser(string userName, params Credential[] credentials) + { + return new User(userName) + { + Credentials = new List(credentials) + }; + } + public static ClaimsPrincipal ToPrincipal(this User user) { ClaimsIdentity identity = new ClaimsIdentity( @@ -64,6 +84,7 @@ internal static void ConfigureEntitiesContext(FakeEntitiesContext ctxt) // Add Users var users = ctxt.Set(); users.Add(User); + users.Add(ShaUser); users.Add(Admin); users.Add(Owner); diff --git a/tests/NuGetGallery.Facts/Framework/TestContainer.cs b/tests/NuGetGallery.Facts/Framework/TestContainer.cs index 36733c02ff..653ce2820c 100644 --- a/tests/NuGetGallery.Facts/Framework/TestContainer.cs +++ b/tests/NuGetGallery.Facts/Framework/TestContainer.cs @@ -17,15 +17,19 @@ public class TestContainer : TestClass, IDisposable { public IKernel Kernel { get; private set; } - public TestContainer() : this(UnitTestBindings.CreateContainer()) { } + public TestContainer() : this(UnitTestBindings.CreateContainer(autoMock: true)) { } protected TestContainer(IKernel kernel) { // Initialize the container - Kernel = UnitTestBindings.CreateContainer(); + Kernel = kernel; } protected TController GetController() where TController : Controller { + if (!Kernel.GetBindings(typeof(TController)).Any()) + { + Kernel.Bind().ToSelf(); + } var c = Kernel.Get(); c.ControllerContext = new ControllerContext( new RequestContext(Kernel.Get(), new RouteData()), c); @@ -65,6 +69,10 @@ protected T Get() protected Mock GetMock() where T : class { + if (!Kernel.GetBindings(typeof(T)).Any()) + { + Kernel.Bind().ToConstant((new Mock() { CallBase = true }).Object); + } T instance = Kernel.Get(); return Mock.Get(instance); } diff --git a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs index a71469d549..9fa7103919 100644 --- a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs +++ b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs @@ -19,16 +19,9 @@ namespace NuGetGallery.Framework { internal class UnitTestBindings : NinjectModule { - internal static IKernel CreateContainer() + internal static IKernel CreateContainer(bool autoMock) { - var kernel = new TestKernel(new UnitTestBindings()); - return kernel; - } - - internal static IKernel CreateContainer() - { - var kernel = new TestKernel(new UnitTestBindings()); - kernel.Bind().ToSelf(); + var kernel = autoMock ? new TestKernel(new UnitTestBindings()) : new StandardKernel(new UnitTestBindings()); return kernel; } diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index 938fd3718c..2dd35c91f8 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -234,7 +234,6 @@ - diff --git a/tests/NuGetGallery.Facts/Services/UserServiceFacts.cs b/tests/NuGetGallery.Facts/Services/UserServiceFacts.cs index 66b2acc34e..4706580274 100644 --- a/tests/NuGetGallery.Facts/Services/UserServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Services/UserServiceFacts.cs @@ -88,165 +88,6 @@ private static UserService CreateMockUserService(Action> setup return userService.Object; } - public class TheChangePasswordMethod - { - [Fact] - public void ReturnsFalseIfUserIsNotFound() - { - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(Enumerable.Empty().AsQueryable()); - - var changed = service.ChangePassword("username", "oldpwd", "newpwd"); - - Assert.False(changed); - } - - [Fact] - public void ReturnsFalseIfPasswordDoesNotMatchUser_SHA1() - { - var user = new User - { - Username = "user", - HashedPassword = CryptographyService.GenerateSaltedHash("oldpwd", "SHA1"), - PasswordHashAlgorithm = "SHA1", - }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "not_the_password", "newpwd"); - - Assert.False(changed); - } - - [Fact] - public void ReturnsFalseIfPasswordDoesNotMatchUser_PBKDF2() - { - var user = new User - { - Username = "user", - HashedPassword = CryptographyService.GenerateSaltedHash("oldpwd", "PBKDF2"), - PasswordHashAlgorithm = "PBKDF2", - }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user}.AsQueryable()); - - var changed = service.ChangePassword("user", "not_the_password", "newpwd"); - - Assert.False(changed); - } - - [Fact] - public void ReturnsFalseIfPasswordDoesNotMatchUser_SHA1Credential() - { - var user = new User - { - Username = "user", - HashedPassword = "not_the_password", - PasswordHashAlgorithm = "SHA1", - }; - user.Credentials.Add(CredentialBuilder.CreateSha1Password("oldpwd")); - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "not_the_password", "newpwd"); - - Assert.False(changed); - } - - [Fact] - public void ReturnsFalseIfPasswordDoesNotMatchUser_PBKDF2Credential() - { - var user = new User - { - Username = "user", - HashedPassword = "not_the_password", - PasswordHashAlgorithm = "SHA1", - }; - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password("oldpwd")); - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "not_the_password", "newpwd"); - - Assert.False(changed); - } - - [Fact] - public void ReturnsTrueWhenSuccessful() - { - var hash = CryptographyService.GenerateSaltedHash("oldpwd", "PBKDF2"); - var user = new User { Username = "user", HashedPassword = hash, PasswordHashAlgorithm = "PBKDF2" }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "oldpwd", "newpwd"); - - Assert.True(changed); - } - - [Fact] - public void UpdatesTheHashedPassword() - { - var hash = CryptographyService.GenerateSaltedHash("oldpwd", "PBKDF2"); - var user = new User { Username = "user", HashedPassword = hash, PasswordHashAlgorithm = "PBKDF2" }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "oldpwd", "newpwd"); - Assert.True(VerifyPasswordHash(user.HashedPassword, user.PasswordHashAlgorithm, "newpwd")); - service.MockUserRepository.VerifyCommitted(); - } - - [Fact] - public void UpdatesThePasswordCredential() - { - var hash = CryptographyService.GenerateSaltedHash("oldpwd", "PBKDF2"); - var user = new User { - Username = "user", - Credentials = new List() - { - new Credential(CredentialTypes.Password.Pbkdf2, hash) - } - }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "oldpwd", "newpwd"); - var cred = user.Credentials.Single(); - Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); - Assert.True(VerifyPasswordHash(cred.Value, Constants.PBKDF2HashAlgorithmId, "newpwd")); - service.MockUserRepository.VerifyCommitted(); - } - - [Fact] - public void MigratesPasswordIfHashAlgorithmIsNotPBKDF2() - { - var user = new User { - Username = "user", - HashedPassword = CryptographyService.GenerateSaltedHash("oldpwd", "SHA1"), - PasswordHashAlgorithm = "SHA1" - }; - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()).Returns(new[] { user }.AsQueryable()); - - var changed = service.ChangePassword("user", "oldpwd", "newpwd"); - - Assert.True(changed); - Assert.True(VerifyPasswordHash(user.HashedPassword, user.PasswordHashAlgorithm, "newpwd")); - Assert.Equal("PBKDF2", user.PasswordHashAlgorithm); - service.MockUserRepository.VerifyCommitted(); - } - } - public class TheConfirmEmailAddressMethod { [Fact] @@ -332,187 +173,6 @@ public void WithEmptyTokenThrowsArgumentNullException() } } - public class TheCreateMethod - { - [Fact] - public void WillThrowIfTheUsernameIsAlreadyInUse() - { - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByUsername("theUsername")) - .Returns(new User())); - - var ex = Assert.Throws( - () => - userService.Create( - "theUsername", - "thePassword", - "theEmailAddress")); - Assert.Equal(String.Format(Strings.UsernameNotAvailable, "theUsername"), ex.Message); - } - - [Fact] - public void WillThrowIfTheEmailAddressIsAlreadyInUse() - { - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindAllByEmailAddress("theEmailAddress")) - .Returns(new List { new User() })); - - var ex = Assert.Throws( - () => - userService.Create( - "theUsername", - "thePassword", - "theEmailAddress")); - Assert.Equal(String.Format(Strings.EmailAddressBeingUsed, "theEmailAddress"), ex.Message); - } - - [Fact] - public void WillHashThePassword() - { - var userService = new TestableUserService(); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - Assert.Equal("PBKDF2", user.PasswordHashAlgorithm); - Assert.True(VerifyPasswordHash(user.HashedPassword, user.PasswordHashAlgorithm, "thePassword")); - } - - [Fact] - public void WillSaveTheNewUser() - { - var userService = new TestableUserService(); - - - userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - userService.MockUserRepository - .Verify(x => x.InsertOnCommit( - It.Is( - u => - u.Username == "theUsername" && - u.UnconfirmedEmailAddress == "theEmailAddress"))); - userService.MockUserRepository - .Verify(x => x.CommitChanges()); - } - - [Fact] - public void WillSaveThePasswordInTheCredentialsTable() - { - var userService = new TestableUserService(); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - Assert.NotNull(user); - var passwordCred = user.Credentials.FirstOrDefault(c => c.Type == CredentialTypes.Password.Pbkdf2); - Assert.NotNull(passwordCred); - Assert.Equal(CredentialTypes.Password.Pbkdf2, passwordCred.Type); - Assert.True(VerifyPasswordHash(passwordCred.Value, Constants.PBKDF2HashAlgorithmId, "thePassword")); - - userService.MockUserRepository - .Verify(x => x.InsertOnCommit(user)); - userService.MockUserRepository - .Verify(x => x.CommitChanges()); - } - - [Fact] - public void WillSaveTheNewUserAsConfirmedWhenConfigured() - { - var userService = new TestableUserService(); - - userService.MockConfig - .Setup(x => x.ConfirmEmailAddresses) - .Returns(false); - - userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - userService.MockUserRepository - .Verify(x => x.InsertOnCommit( - It.Is( - u => - u.Username == "theUsername" && - u.Confirmed))); - userService.MockUserRepository - .Verify(x => x.CommitChanges()); - } - - [Fact] - public void SetsAnApiKey() - { - var userService = new TestableUserService(); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - userService.MockUserRepository - .Verify(x => x.InsertOnCommit(user)); - Assert.NotEqual(Guid.Empty, user.ApiKey); - - var apiKeyCred = user.Credentials.FirstOrDefault(c => c.Type == CredentialTypes.ApiKeyV1); - Assert.NotNull(apiKeyCred); - Assert.Equal(user.ApiKey.ToString().ToLowerInvariant(), apiKeyCred.Value); - } - - [Fact] - public void SetsAConfirmationToken() - { - var userService = new TestableUserService(); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - Assert.NotEmpty(user.EmailConfirmationToken); - Assert.False(user.Confirmed); - } - - [Fact] - public void SetsCreatedDate() - { - var userService = new TestableUserService(); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - Assert.NotNull(user.CreatedUtc); - - // Allow for up to 5 secs of time to have elapsed between Create call and now. Should be plenty - Assert.True((DateTime.UtcNow - user.CreatedUtc) < TimeSpan.FromSeconds(5)); - } - - [Fact] - public void SetsTheUserToConfirmedWhenEmailConfirmationIsNotEnabled() - { - var userService = new TestableUserService(); - userService.MockConfig - .Setup(x => x.ConfirmEmailAddresses) - .Returns(false); - - var user = userService.Create( - "theUsername", - "thePassword", - "theEmailAddress"); - - Assert.Equal(true, user.Confirmed); - } - } - public class TheFindByEmailAddressMethod { [Fact] @@ -530,670 +190,6 @@ public void ReturnsNullIfMultipleMatchesExist() } } - public class TheFindByUsernameAndPasswordMethod - { - [Fact] - public void FindsUsersByUserName() - { - var user = CreateAUser("theUsername", "thePassword", "test@example.com"); - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void WillNotFindsUsersByEmailAddress() - { - var user = CreateAUser("theUsername", "thePassword", "test@example.com"); - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - var foundByEmailAddress = service.FindByUsernameAndPassword("test@example.com", "thePassword"); - - Assert.Null(foundByEmailAddress); - } - - [Fact] - public void DoesNotReturnUserIfPasswordIsInvalid() - { - var user = CreateAUser("theUsername", "thePassword", "test@example.com"); - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "theWrongPassword"); - - Assert.Null(foundByUserName); - } - - [Fact] - public void FindsUserBasedOnPasswordInCredentialsTable() - { - var user = CreateAUser("theUsername", "test@example.com"); - user.Credentials.Add(CreatePasswordCredential("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void IfSomehowBothPasswordsExistItFindsUserBasedOnPasswordInCredentialsTable() - { - var user = CreateAUser("theUsername", "theWrongPassword", "test@example.com"); - user.Credentials.Add(CreatePasswordCredential("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void GivenASHA1PasswordColumnAndNoCredentialsItAuthenticatesUser() - { - var user = CreateAUser("theUsername", "thePassword", "test@example.com", hashAlgorithm: Constants.Sha1HashAlgorithmId); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - Assert.Same(user, foundByUserName); - Assert.Empty(user.Credentials); - } - - [Fact] - public void GivenAPBKDF2PasswordColumnAndNoCredentialsItAuthenticatesUser() - { - var user = CreateAUser("theUsername", "thePassword", "test@example.com", hashAlgorithm: Constants.PBKDF2HashAlgorithmId); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - Assert.Same(user, foundByUserName); - Assert.Empty(user.Credentials); - } - - [Fact] - public void GivenOnlyASHA1CredentialItAuthenticatesUserAndReplacesItWithAPBKDF2Credential() - { - var user = CreateAUser("theUsername", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - var cred = foundByUserName.Credentials.Single(); - Assert.Same(user, foundByUserName); - Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); - Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); - } - - [Fact] - public void GivenASHA1AndAPBKDF2CredentialItAuthenticatesUserWithEitherCredential() - { - var user = CreateAUser("theUsername", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword1")); - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password("thePassword2")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByPassword1 = service.FindByUsernameAndPassword("theUsername", "thePassword1"); - var foundByPassword2 = service.FindByUsernameAndPassword("theUsername", "thePassword2"); - Assert.Same(user, foundByPassword1); - Assert.Same(foundByPassword1, foundByPassword2); - } - - [Fact] - public void GivenASHA1AndAPBKDF2CredentialItAuthenticatesUserAndRemovesTheSHA1Cred() - { - var user = CreateAUser("theUsername", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword")); - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameAndPassword("theUsername", "thePassword"); - - var cred = foundByUserName.Credentials.Single(); - Assert.Same(user, foundByUserName); - Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); - Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); - } - } - - public class TheFindByUsernameOrEmailAddressAndPasswordMethod - { - [Fact] - public void FindsUsersByUserName() - { - var user = new User - { - Username = "theUsername", - HashedPassword = CryptographyService.GenerateSaltedHash("thePassword", Constants.PBKDF2HashAlgorithmId), - EmailAddress = "test@example.com", - PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId, - }; - - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("theUsername", "thePassword"); - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void FindsUsersByEmailAddress() - { - var user = new User - { - Username = "theUsername", - HashedPassword = CryptographyService.GenerateSaltedHash("thePassword", Constants.PBKDF2HashAlgorithmId), - EmailAddress = "test@example.com", - PasswordHashAlgorithm = "PBKDF2" - }; - - var service = new TestableUserService(); - service.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - var foundByEmailAddress = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - Assert.NotNull(foundByEmailAddress); - Assert.Same(user, foundByEmailAddress); - } - - [Fact] - public void FindsUserBasedOnPasswordInCredentialsTable() - { - var user = CreateAUser("theUsername", "test@example.com"); - user.Credentials.Add(CreatePasswordCredential("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void IfSomehowBothPasswordsExistItFindsUserBasedOnPasswordInCredentialsTable() - { - var user = CreateAUser("theUsername", "theWrongPassword", "test@example.com"); - user.Credentials.Add(CreatePasswordCredential("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - Assert.NotNull(foundByUserName); - Assert.Same(user, foundByUserName); - } - - [Fact] - public void GivenASHA1PasswordColumnAndNoCredentialsItAuthenticatesUser() - { - var user = CreateAUser("test@example.com", "thePassword", "test@example.com", hashAlgorithm: Constants.Sha1HashAlgorithmId); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - Assert.Same(user, foundByUserName); - Assert.Empty(user.Credentials); - } - - [Fact] - public void GivenAPBKDF2PasswordColumnAndNoCredentialsItAuthenticatesUser() - { - var user = CreateAUser("test@example.com", "thePassword", "test@example.com", hashAlgorithm: Constants.PBKDF2HashAlgorithmId); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - Assert.Same(user, foundByUserName); - Assert.Empty(user.Credentials); - } - - [Fact] - public void GivenOnlyASHA1CredentialItAuthenticatesUserAndReplacesItWithAPBKDF2Credential() - { - var user = CreateAUser("test@example.com", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - var cred = foundByUserName.Credentials.Single(); - Assert.Same(user, foundByUserName); - Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); - Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); - } - - [Fact] - public void GivenASHA1AndAPBKDF2CredentialItAuthenticatesUserWithEitherCredential() - { - var user = CreateAUser("test@example.com", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword1")); - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password("thePassword2")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByPassword1 = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword1"); - var foundByPassword2 = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword2"); - Assert.Same(user, foundByPassword1); - Assert.Same(foundByPassword1, foundByPassword2); - } - - [Fact] - public void GivenASHA1AndAPBKDF2CredentialItAuthenticatesUserAndRemovesTheSHA1Cred() - { - var user = CreateAUser("theUsername", password: null, emailAddress: "test@example.com"); - user.Credentials.Add(CredentialBuilder.CreateSha1Password("thePassword")); - user.Credentials.Add(CredentialBuilder.CreatePbkdf2Password("thePassword")); - var service = new TestableUserService(); - service.MockUserRepository.HasData(user); - service.MockCredentialRepository.HasData(user.Credentials); - - var foundByUserName = service.FindByUsernameOrEmailAddressAndPassword("test@example.com", "thePassword"); - - var cred = foundByUserName.Credentials.Single(); - Assert.Same(user, foundByUserName); - Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); - Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); - } - } - - public class TheAuthenticateCredentialMethod - { - [Fact] - public void ReturnsNullIfNoCredentialOfSpecifiedTypeExists() - { - // Arrange - var creds = new List() { - new Credential("foo", "bar") - }; - var service = new TestableUserService(); - service.MockCredentialRepository.HasData(creds); - - // Act - var result = service.AuthenticateCredential(type: "baz", value: "bar"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ReturnsNullIfNoCredentialOfSpecifiedTypeWithSpecifiedValueExists() - { - // Arrange - var creds = new List() { - new Credential("foo", "bar") - }; - var service = new TestableUserService(); - service.MockCredentialRepository.HasData(creds); - - // Act - var result = service.AuthenticateCredential(type: "foo", value: "baz"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ReturnsCredentialIfOneExistsWithSpecifiedTypeAndValue() - { - // Arrange - var creds = new List() { - new Credential("foo", "bar") - }; - var service = new TestableUserService(); - service.MockCredentialRepository.HasData(creds); - - // Act - var result = service.AuthenticateCredential(type: "foo", value: "bar"); - - // Assert - Assert.Same(creds[0], result); - } - } - - public class TheReplaceCredentialMethod - { - [Fact] - public void ThrowsExceptionIfNoUserWithProvidedUserName() - { - // Arrange - var users = new List() { - new User("foo") - }; - var service = new TestableUserService(); - service.MockUserRepository.HasData(users); - - // Act - var ex = Assert.Throws(() => - service.ReplaceCredential("biz", new Credential())); - - // Assert - Assert.Equal(Strings.UserNotFound, ex.Message); - } - - [Fact] - public void AddsNewCredentialIfNoneWithSameTypeForUser() - { - // Arrange - var existingCred = new Credential("foo", "bar"); - var newCred = new Credential("baz", "boz"); - var users = new List() { - new User("foo") { - Credentials = new List() { - existingCred - } - } - }; - var service = new TestableUserService(); - service.MockUserRepository.HasData(users); - - // Act - service.ReplaceCredential("foo", newCred); - - // Assert - Assert.Equal(2, users[0].Credentials.Count); - Assert.Equal(new[] { existingCred, newCred }, users[0].Credentials.ToArray()); - service.MockUserRepository.VerifyCommitted(); - } - - [Fact] - public void ReplacesExistingCredentialIfOneWithSameTypeExistsForUser() - { - // Arrange - var frozenCred = new Credential("foo", "bar"); - var existingCred = new Credential("baz", "bar"); - var newCred = new Credential("baz", "boz"); - var users = new List() { - new User("foo") { - Credentials = new List() { - existingCred, - frozenCred - } - } - }; - var service = new TestableUserService(); - service.MockUserRepository.HasData(users); - - // Act - service.ReplaceCredential("foo", newCred); - - // Assert - Assert.Equal(2, users[0].Credentials.Count); - Assert.Equal(new[] { frozenCred, newCred }, users[0].Credentials.ToArray()); - service.MockCredentialRepository - .Verify(x => x.DeleteOnCommit(existingCred)); - service.MockUserRepository.VerifyCommitted(); - } - } - - public class TheGenerateApiKeyMethod - { - [Fact] - public void SetsApiKeyToNewGuid() - { - var user = new User { ApiKey = Guid.Empty }; - var userRepo = new Mock>(); - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByUsername("theUsername")) - .Returns(user), - userRepo: userRepo); - - var apiKey = userService.GenerateApiKey("theUsername"); - - Assert.NotEqual(Guid.Empty, user.ApiKey); - Assert.Equal(apiKey, user.ApiKey.ToString()); - userRepo.Verify(r => r.CommitChanges()); - } - } - - public class TheGeneratePasswordResetTokenMethod - { - [Fact] - public void ReturnsNullIfEmailIsNotFound() - { - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByEmailAddress("email@example.com")) - .Returns((User)null)); - - var token = userService.GeneratePasswordResetToken("email@example.com", 1440); - Assert.Null(token); - } - - [Fact] - public void ThrowsExceptionIfUserIsNotConfirmed() - { - var user = new User { Username = "user" }; - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByEmailAddress("user@example.com")) - .Returns(user)); - - Assert.Throws(() => userService.GeneratePasswordResetToken("user@example.com", 1440)); - } - - [Fact] - public void SetsPasswordResetTokenUsingEmail() - { - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - PasswordResetToken = null - }; - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByEmailAddress("email@example.com")) - .Returns(user)); - var currentDate = DateTime.UtcNow; - - var returnedUser = userService.GeneratePasswordResetToken("email@example.com", 1440); - - Assert.Same(user, returnedUser); - Assert.NotNull(user.PasswordResetToken); - Assert.NotEmpty(user.PasswordResetToken); - Assert.True(user.PasswordResetTokenExpirationDate >= currentDate.AddMinutes(1440)); - } - - [Fact] - public void WithExistingNotYetExpiredTokenReturnsExistingToken() - { - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - PasswordResetToken = "existing-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) - }; - var userService = CreateMockUserService( - setup: u => u.Setup(x => x.FindByEmailAddress("user@example.com")) - .Returns(user)); - - var returnedUser = userService.GeneratePasswordResetToken("user@example.com", 1440); - - Assert.Same(user, returnedUser); - Assert.Equal("existing-token", user.PasswordResetToken); - } - - [Fact] - public void WithExistingExpiredTokenReturnsNewToken() - { - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - PasswordResetToken = "existing-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddMilliseconds(-1) - }; - var userService = CreateMockUserService( - setup: mockUserService => - { - mockUserService - .Setup(x => x.FindByEmailAddress("user@example.com")) - .Returns(user); - }); - var currentDate = DateTime.UtcNow; - - var returnedUser = userService.GeneratePasswordResetToken("user@example.com", 1440); - - Assert.Same(user, returnedUser); - Assert.NotEmpty(user.PasswordResetToken); - Assert.NotEqual("existing-token", user.PasswordResetToken); - Assert.True(user.PasswordResetTokenExpirationDate >= currentDate.AddMinutes(1440)); - } - } - - public class TheResetPasswordWithTokenMethod - { - [Fact] - public void ReturnsFalseIfUserNotFound() - { - var userService = new TestableUserService(); - userService.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(Enumerable.Empty().AsQueryable()); - - bool result = userService.ResetPasswordWithToken("user", "some-token", "new-password"); - - Assert.False(result); - } - - [Fact] - public void ThrowsExceptionIfUserNotConfirmed() - { - var user = new User - { - Username = "user", - PasswordResetToken = "some-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) - }; - var userService = new TestableUserService(); - userService.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - Assert.Throws(() => userService.ResetPasswordWithToken("user", "some-token", "new-password")); - } - - [Fact] - public void ResetsPasswordAndPasswordTokenAndPasswordTokenDate() - { - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - PasswordResetToken = "some-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1), - HashedPassword = CryptographyService.GenerateSaltedHash("thePassword", Constants.PBKDF2HashAlgorithmId), - PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId, - }; - - var userService = new TestableUserService(); - userService.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - bool result = userService.ResetPasswordWithToken("user", "some-token", "new-password"); - - Assert.True(result); - Assert.True(VerifyPasswordHash(user.HashedPassword, user.PasswordHashAlgorithm, "new-password")); - Assert.Null(user.PasswordResetToken); - Assert.Null(user.PasswordResetTokenExpirationDate); - userService.MockUserRepository.VerifyCommitted(); - } - - [Fact] - public void ResetsPasswordCredential() - { - var oldCred = CredentialBuilder.CreatePbkdf2Password("thePassword"); - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - PasswordResetToken = "some-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1), - HashedPassword = oldCred.Value, - PasswordHashAlgorithm = Constants.PBKDF2HashAlgorithmId, - Credentials = new List() { oldCred } - }; - - var userService = new TestableUserService(); - userService.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - bool result = userService.ResetPasswordWithToken("user", "some-token", "new-password"); - - Assert.True(result); - var newCred = user.Credentials.Single(); - Assert.Equal(CredentialTypes.Password.Pbkdf2, newCred.Type); - Assert.True(VerifyPasswordHash(newCred.Value, Constants.PBKDF2HashAlgorithmId, "new-password")); - userService.MockUserRepository.VerifyCommitted(); - } - - [Fact] - public void ResetsPasswordMigratesPasswordHash() - { - var user = new User - { - Username = "user", - EmailAddress = "confirmed@example.com", - HashedPassword = CryptographyService.GenerateSaltedHash("thePassword", "SHA1"), - PasswordHashAlgorithm = "SHA1", - PasswordResetToken = "some-token", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1), - }; - var userService = new TestableUserService(); - userService.MockUserRepository - .Setup(r => r.GetAll()) - .Returns(new[] { user }.AsQueryable()); - - bool result = userService.ResetPasswordWithToken("user", "some-token", "new-password"); - - Assert.True(result); - Assert.Equal("PBKDF2", user.PasswordHashAlgorithm); - Assert.True(VerifyPasswordHash(user.HashedPassword, user.PasswordHashAlgorithm, "new-password")); - Assert.Null(user.PasswordResetToken); - Assert.Null(user.PasswordResetTokenExpirationDate); - userService.MockUserRepository.VerifyCommitted(); - } - } - public class TheChangeEmailMethod { User CreateUser(string username, string password, string emailAddress) diff --git a/tests/NuGetGallery.Facts/TestUtils/FakeEntitiesContext.cs b/tests/NuGetGallery.Facts/TestUtils/FakeEntitiesContext.cs index 0fba1096ab..5bd890a89c 100644 --- a/tests/NuGetGallery.Facts/TestUtils/FakeEntitiesContext.cs +++ b/tests/NuGetGallery.Facts/TestUtils/FakeEntitiesContext.cs @@ -59,6 +59,18 @@ public IDbSet Packages } } + public IDbSet Credentials + { + get + { + return Set(); + } + set + { + throw new NotSupportedException(); + } + } + public IDbSet Users { get diff --git a/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs b/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs index 06e7b6c09b..f46f598a86 100644 --- a/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs +++ b/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Linq; using Moq; +using NuGetGallery.Authentication; +using System; +using Xunit; namespace NuGetGallery { @@ -46,6 +49,12 @@ public static IReturnsResult> HasData(this Mock e.GetAll()).Returns(fakeData.AsQueryable()); } + public static void VerifyCommitChanges(this IEntitiesContext self) + { + FakeEntitiesContext context = Assert.IsAssignableFrom(self); + context.VerifyCommitChanges(); + } + public static void VerifyCommitted(this Mock> self) where T : class, IEntity, new() { @@ -56,5 +65,13 @@ public static void VerifyCommitted(this Mock self) { self.Verify(e => e.SaveChanges()); } + + public static IReturnsResult SetupAuth(this Mock self, Credential cred, User user) + { + return self.Setup(us => us.Authenticate(It.Is(c => + String.Equals(c.Type, cred.Type, StringComparison.OrdinalIgnoreCase) && + String.Equals(c.Value, cred.Value, StringComparison.Ordinal)))) + .Returns(new AuthenticatedUser(user, cred)); + } } } From 1146cc4c001ebc113d55c1d1e21c2d8cebf5386a Mon Sep 17 00:00:00 2001 From: anurse Date: Tue, 15 Oct 2013 11:53:51 -0700 Subject: [PATCH 07/18] Fixed up infrastructure --- .../Controllers/AuthenticationController.cs | 4 +- .../AuthenticationServiceFacts.cs | 8 +- .../Controllers/ApiControllerFacts.cs | 2 +- .../AuthenticationControllerFacts.cs | 51 ++++++---- .../Controllers/UsersControllerFacts.cs | 93 ++++++++++--------- .../Framework/TestContainer.cs | 7 ++ .../TestUtils/MockExtensions.cs | 2 +- 7 files changed, 97 insertions(+), 70 deletions(-) diff --git a/src/NuGetGallery/Controllers/AuthenticationController.cs b/src/NuGetGallery/Controllers/AuthenticationController.cs index 2cb9554aa5..7ebafedf17 100644 --- a/src/NuGetGallery/Controllers/AuthenticationController.cs +++ b/src/NuGetGallery/Controllers/AuthenticationController.cs @@ -67,7 +67,7 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) return View(); } - AuthService.CreateSession(HttpContext.GetOwinContext(), user); + AuthService.CreateSession(OwinContext, user); return SafeRedirect(returnUrl); } @@ -104,7 +104,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) return View(); } - AuthService.CreateSession(HttpContext.GetOwinContext(), user); + AuthService.CreateSession(OwinContext, user); if (RedirectHelper.SafeRedirectUrl(Url, returnUrl) != RedirectHelper.SafeRedirectUrl(Url, null)) { diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index e47134f060..17303f4846 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -157,10 +157,10 @@ public void GivenOnlyASHA1PasswordItAuthenticatesUserAndReplacesItWithAPBKDF2Pas var service = Get(); service.Entities.Users.Add(user); - var foundByUserName = service.Authenticate("theUsername", "thePassword"); + var foundByUserName = service.Authenticate("tempUser", "thePassword"); var cred = foundByUserName.User.Credentials.Single(); - Assert.Same(user, foundByUserName); + Assert.Same(user, foundByUserName.User); Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); service.Entities.VerifyCommitChanges(); @@ -175,10 +175,10 @@ public void GivenASHA1AndAPBKDF2PasswordItAuthenticatesUserAndRemovesTheSHA1Pass var service = Get(); service.Entities.Users.Add(user); - var foundByUserName = service.Authenticate("theUsername", "thePassword"); + var foundByUserName = service.Authenticate("tempUser", "thePassword"); var cred = foundByUserName.User.Credentials.Single(); - Assert.Same(user, foundByUserName); + Assert.Same(user, foundByUserName.User); Assert.Equal(CredentialTypes.Password.Pbkdf2, cred.Type); Assert.True(CryptographyService.ValidateSaltedHash(cred.Value, "thePassword", Constants.PBKDF2HashAlgorithmId)); service.Entities.VerifyCommitChanges(); diff --git a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs index efbeef42c2..ef9b40d66c 100644 --- a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs @@ -633,13 +633,13 @@ public void VerifyPackageKeyReturns404IfPackageDoesNotExist() { // Arrange var apiKey = Guid.NewGuid(); - var controller = GetController(); var user = new User { EmailAddress = "confirmed@email.com" }; GetMock() .SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); GetMock() .Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)) .ReturnsNull(); + var controller = GetController(); // Act var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); diff --git a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs index 643722fcc2..d36294158a 100644 --- a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs @@ -17,19 +17,20 @@ public class TheLogOffAction : TestContainer [Fact] public void WillLogTheUserOff() { + GetMock() + .Setup(c => c.Authentication.SignOut()); var controller = GetController(); - var mockContext = new Mock(); - mockContext.Setup(c => c.Authentication.SignOut()).Verifiable(); - controller.OwinContext = mockContext.Object; - + controller.LogOff("theReturnUrl"); - mockContext.VerifyAll(); + GetMock() + .Verify(c => c.Authentication.SignOut()); } [Fact] public void WillRedirectToTheReturnUrl() { + GetMock().Setup(c => c.Authentication.SignOut()); var controller = GetController(); var result = controller.LogOff("theReturnUrl"); @@ -64,6 +65,9 @@ public void CanLogTheUserOnWithUserName() .Setup(x => x.Authenticate(authUser.User.Username, "thePassword")) .Returns(authUser); var controller = GetController(); + GetMock() + .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Verifiable(); // Act controller.SignIn( @@ -71,8 +75,7 @@ public void CanLogTheUserOnWithUserName() "theReturnUrl"); // Assert - GetMock() - .Verify(a => a.CreateSession(It.IsAny(), authUser)); + GetMock().VerifyAll(); } [Fact] @@ -86,6 +89,9 @@ public void CanLogTheUserOnWithEmailAddress() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); var controller = GetController(); + GetMock() + .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Verifiable(); // Act controller.SignIn( @@ -93,8 +99,7 @@ public void CanLogTheUserOnWithEmailAddress() "theReturnUrl"); // Assert - GetMock() - .Verify(a => a.CreateSession(It.IsAny(), authUser)); + GetMock().VerifyAll(); } [Fact] @@ -108,15 +113,17 @@ public void WillLogTheUserOnWithUsernameEvenWithoutConfirmedEmailAddress() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); var controller = GetController(); + GetMock() + .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Verifiable(); // Act controller.SignIn( - new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + new SignInRequest { UserNameOrEmail = "confirmed@example.com", Password = "thePassword" }, "theReturnUrl"); // Assert - GetMock() - .Verify(a => a.CreateSession(It.IsAny(), authUser)); + GetMock().VerifyAll(); } [Fact] @@ -144,10 +151,12 @@ public void WillRedirectToHomeIfReturnUrlNotLocal() GetMock() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); + GetMock() + .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); var controller = GetController(); var result = controller.SignIn( - new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + new SignInRequest { UserNameOrEmail = "confirmed@example.com", Password = "thePassword" }, "http://www.microsoft.com"); ResultAssert.IsRedirectTo(result, "/"); @@ -162,10 +171,12 @@ public void WillRedirectToTheReturnUrlIfLocal() GetMock() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); + GetMock() + .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); var controller = GetController(); var result = controller.SignIn( - new SignInRequest { UserNameOrEmail = "theUsername", Password = "thePassword" }, + new SignInRequest { UserNameOrEmail = "confirmed@example.com", Password = "thePassword" }, "/packages/upload"); ResultAssert.IsRedirectTo(result, "/packages/upload"); @@ -193,8 +204,11 @@ public void WillCreateAndLogInTheUser() { var authUser = new AuthenticatedUser(new User("theUsername"), new Credential()); GetMock() - .Setup(x => x.Register(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Register("theUsername", "thePassword", "theEmailAddress")) .Returns(authUser); + GetMock() + .Setup(x => x.CreateSession(It.IsAny(), authUser)) + .Verifiable(); var controller = GetController(); controller.Register( @@ -205,10 +219,7 @@ public void WillCreateAndLogInTheUser() EmailAddress = "theEmailAddress", }, null); - GetMock() - .Verify(x => x.Register("theUsername", "thePassword", "theEmailAddress")); - GetMock() - .Verify(x => x.CreateSession(It.IsAny(), authUser)); + GetMock().VerifyAll(); } [Fact] @@ -239,6 +250,8 @@ public void WillRedirectToTheReturnUrl() GetMock() .Setup(x => x.Register("theUsername", "thepassword", "unconfirmed@example.com")) .Returns(new AuthenticatedUser(user, new Credential())); + GetMock() + .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); var controller = GetController(); var result = controller.Register(new RegisterRequest diff --git a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs index bdd476eac6..7f99e180ea 100644 --- a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs @@ -179,7 +179,6 @@ public void SendsEmailWithPasswordResetUrl() PasswordResetToken = "confirmation", PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) }; - var controller = GetController(); GetMock() .Setup(s => s.SendPasswordResetInstructions(user, resetUrl)); GetMock() @@ -188,6 +187,7 @@ public void SendsEmailWithPasswordResetUrl() GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns(user); + var controller = GetController(); var model = new ForgotPasswordViewModel { Email = "user" }; controller.ForgotPassword(model); @@ -200,11 +200,12 @@ public void SendsEmailWithPasswordResetUrl() public void RedirectsAfterGeneratingToken() { var user = new User { EmailAddress = "some@example.com", Username = "somebody" }; - var controller = GetController(); GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns(user) .Verifiable(); + var controller = GetController(); + var model = new ForgotPasswordViewModel { Email = "user" }; var result = controller.ForgotPassword(model) as RedirectToRouteResult; @@ -217,11 +218,11 @@ public void RedirectsAfterGeneratingToken() [Fact] public void ReturnsSameViewIfTokenGenerationFails() { - var controller = GetController(); GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns((User)null); - + var controller = GetController(); + var model = new ForgotPasswordViewModel { Email = "user" }; var result = controller.ForgotPassword(model) as ViewResult; @@ -236,12 +237,13 @@ public class TheGenerateApiKeyMethod : TestContainer [Fact] public void RedirectsToAccountPage() { - var controller = GetController(); var user = new User { Username = "the-username" }; - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername(It.IsAny())) .Returns(user); + var controller = GetController(); + controller.SetUser(user); + var result = controller.GenerateApiKey(); ResultAssert.IsRedirectToRoute(result, new { action = "Account", controller = "Users" }); @@ -250,9 +252,7 @@ public void RedirectsToAccountPage() [Fact] public void PutsNewCredentialInOldField() { - var controller = GetController(); var user = new User("the-username") { ApiKey = Guid.NewGuid() }; - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername("the-username")) .Returns(user); @@ -260,6 +260,8 @@ public void PutsNewCredentialInOldField() GetMock() .Setup(u => u.ReplaceCredential(user, It.IsAny())) .Callback((_, c) => created = c); + var controller = GetController(); + controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername(It.IsAny())) @@ -273,19 +275,21 @@ public void PutsNewCredentialInOldField() [Fact] public void ReplacesTheApiKeyCredential() { - var controller = GetController(); var user = new User("the-username") { ApiKey = Guid.NewGuid() }; - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername("the-username")) .Returns(user); + GetMock() + .Setup(u => u.ReplaceCredential( + user, + It.Is(c => c.Type == CredentialTypes.ApiKeyV1))) + .Verifiable(); + var controller = GetController(); + controller.SetUser(user); controller.GenerateApiKey(); - GetMock() - .Verify(u => u.ReplaceCredential( - user, - It.Is(c => c.Type == CredentialTypes.ApiKeyV1))); + GetMock().VerifyAll(); } } @@ -301,14 +305,14 @@ public void DoesNotLetYouUseSomeoneElsesConfirmedEmailAddress() Key = 1, }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Throws(new EntityException("msg")); + var controller = GetController(); + controller.SetUser(user); var result = controller.ChangeEmail(new ChangeEmailRequestModel { NewEmail = "new@example.com" }); Assert.False(controller.ModelState.IsValid); @@ -325,15 +329,15 @@ public void SendsEmailChangeConfirmationNoticeWhenChangingAConfirmedEmailAddress EmailAllowed = true }; - var controller = GetController(); - controller.SetUser(user); - GetMock() .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "token")); + var controller = GetController(); + controller.SetUser(user); + var model = new ChangeEmailRequestModel { NewEmail = "new@example.com" }; var result = controller.ChangeEmail(model); @@ -351,15 +355,15 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenAddressDoesntChange() Username = "aUsername", }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("old@example.com", () => "new-token")); - + var controller = GetController(); + controller.SetUser(user); + var model = new ChangeEmailRequestModel { NewEmail = "old@example.com" }; controller.ChangeEmail(model); @@ -379,14 +383,15 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenUserWasNotConfirmed() UnconfirmedEmailAddress = "old@example.com", }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "new-token")); + var controller = GetController(); + controller.SetUser(user); + var model = new ChangeEmailRequestModel{ NewEmail = "new@example.com" }; controller.ChangeEmail(model); @@ -411,10 +416,10 @@ public void ConfirmsTheUser() EmailConfirmationToken = "aToken", }; + GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); var controller = GetController(); controller.SetUser(user); - GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); - + var result = controller.Confirm("aUsername", "aToken"); GetMock().Verify(u => u.ConfirmEmailAddress(user, "aToken")); @@ -430,10 +435,10 @@ public void ShowsAnErrorForWrongUsername() EmailConfirmationToken = "aToken", }; + GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); var controller = GetController(); controller.SetUser(user); - GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); - + var result = controller.Confirm("wrongUsername", "aToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -451,15 +456,15 @@ public void ShowsAnErrorForWrongToken() EmailConfirmationToken = "aToken", }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername("aUsername")) .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, It.IsAny())) .Returns(false); - + var controller = GetController(); + controller.SetUser(user); + var result = controller.Confirm("aUsername", "wrongToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -476,15 +481,15 @@ public void ShowsAnErrorForConflictingEmailAddress() EmailConfirmationToken = "aToken", }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername("aUsername")) .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, It.IsAny())) .Throws(new EntityException("msg")); - + var controller = GetController(); + controller.SetUser(user); + var result = controller.Confirm("aUsername", "aToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -502,8 +507,6 @@ public void SendsAccountChangedNoticeWhenConfirmingChangedEmail() UnconfirmedEmailAddress = "new@example.com", EmailConfirmationToken = "the-token" }; - var controller = GetController(); - controller.SetUser(user); GetMock() .Setup(u => u.FindByUsername("username")) @@ -511,6 +514,8 @@ public void SendsAccountChangedNoticeWhenConfirmingChangedEmail() GetMock() .Setup(u => u.ConfirmEmailAddress(user, "the-token")) .Returns(true); + var controller = GetController(); + controller.SetUser(user); var result = controller.Confirm("username", "the-token"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -531,13 +536,14 @@ public void DoesntSendAccountChangedEmailsWhenNoOldConfirmedAddress() UnconfirmedEmailAddress = "new@example.com", EmailConfirmationToken = "the-token" }; - var controller = GetController(); + GetMock() .Setup(u => u.FindByUsername("username")) .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, "the-token")) .Returns(true); + var controller = GetController(); controller.SetUser(user); // act: @@ -563,15 +569,16 @@ public void DoesntSendAccountChangedEmailsIfConfirmationTokenDoesntMatch() UnconfirmedEmailAddress = "new@example.com", EmailConfirmationToken = "the-token" }; - var controller = GetController(); - controller.SetUser(user); + GetMock() .Setup(u => u.FindByUsername(It.IsAny())) .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, "faketoken")) .Returns(false); - + var controller = GetController(); + controller.SetUser(user); + // act: var model = (controller.Confirm("username", "faketoken") as ViewResult).Model as ConfirmationViewModel; @@ -600,9 +607,6 @@ public void WillSendNewUserEmailWhenPosted() string sentConfirmationUrl = null; MailAddress sentToAddress = null; - var controller = GetController(); - controller.SetUser(user); - GetMock() .Setup(x => x.FindByUsername(It.IsAny())) .Returns(user); @@ -614,6 +618,9 @@ public void WillSendNewUserEmailWhenPosted() sentConfirmationUrl = url; }); + var controller = GetController(); + controller.SetUser(user); + controller.ConfirmationRequiredPost(); // We use a catch-all route for unit tests so we can see the parameters diff --git a/tests/NuGetGallery.Facts/Framework/TestContainer.cs b/tests/NuGetGallery.Facts/Framework/TestContainer.cs index 653ce2820c..c6c5fa6935 100644 --- a/tests/NuGetGallery.Facts/Framework/TestContainer.cs +++ b/tests/NuGetGallery.Facts/Framework/TestContainer.cs @@ -6,6 +6,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Owin; using Moq; using Ninject; using Ninject.Modules; @@ -37,6 +38,12 @@ protected TController GetController() where TController : Controlle var routeCollection = new RouteCollection(); Routes.RegisterRoutes(routeCollection); c.Url = new UrlHelper(c.ControllerContext.RequestContext, routeCollection); + + var appCtrl = c as AppController; + if (appCtrl != null) + { + appCtrl.OwinContext = Kernel.Get(); + } return c; } diff --git a/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs b/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs index f46f598a86..1a2898b48a 100644 --- a/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs +++ b/tests/NuGetGallery.Facts/TestUtils/MockExtensions.cs @@ -71,7 +71,7 @@ public static IReturnsResult SetupAuth(this Mock us.Authenticate(It.Is(c => String.Equals(c.Type, cred.Type, StringComparison.OrdinalIgnoreCase) && String.Equals(c.Value, cred.Value, StringComparison.Ordinal)))) - .Returns(new AuthenticatedUser(user, cred)); + .Returns(user == null ? null : new AuthenticatedUser(user, cred)); } } } From 07335b6e9d9927766707414e7658b1e999f3d91b Mon Sep 17 00:00:00 2001 From: anurse Date: Tue, 15 Oct 2013 14:28:27 -0700 Subject: [PATCH 08/18] Switched to default auth cookie name --- src/NuGetGallery/App_Start/OwinStartup.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 6d9412f117..423ae31787 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -22,7 +22,6 @@ public static void Configuration(IAppBuilder app) { AuthenticationMode = AuthenticationMode.Active, CookieHttpOnly = true, - CookieName = System.Web.Security.FormsAuthentication.FormsCookieName, LoginPath = "/users/account/logon" }); } From 617d0782515a06313825558eb9ea72d5415ff857 Mon Sep 17 00:00:00 2001 From: anurse Date: Tue, 15 Oct 2013 15:50:37 -0700 Subject: [PATCH 09/18] C is for COOKIE AUTH! --- src/NuGetGallery/App_Start/OwinStartup.cs | 2 ++ .../Authentication/AuthenticationService.cs | 20 +++++++++++-------- .../Authentication/AuthenticationTypes.cs | 12 +++++++++++ .../Controllers/AuthenticationController.cs | 4 ++-- src/NuGetGallery/NuGetGallery.csproj | 1 + .../AuthenticationServiceFacts.cs | 6 +++--- .../AuthenticationControllerFacts.cs | 14 ++++++------- 7 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 src/NuGetGallery/Authentication/AuthenticationTypes.cs diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 423ae31787..8085b362e6 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -8,6 +8,7 @@ using Microsoft.Owin.Diagnostics; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; +using NuGetGallery.Authentication; [assembly: OwinStartup(typeof(NuGetGallery.OwinStartup))] @@ -20,6 +21,7 @@ public static void Configuration(IAppBuilder app) { app.UseCookieAuthentication(new CookieAuthenticationOptions() { + AuthenticationType = AuthenticationTypes.Cookie, AuthenticationMode = AuthenticationMode.Active, CookieHttpOnly = true, LoginPath = "/users/account/logon" diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index a02feabd4c..799ef0b7ee 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -9,6 +9,7 @@ using Microsoft.Owin; using System.Security.Claims; using NuGetGallery.Configuration; +using Microsoft.Owin.Security; namespace NuGetGallery.Authentication { @@ -86,11 +87,19 @@ public virtual AuthenticatedUser Authenticate(Credential credential) } } - public virtual void CreateSession(IOwinContext owinContext, AuthenticatedUser user) + public virtual void CreateSession(IOwinContext owinContext, User user, string authenticationType) { // Create a claims identity for the session - ClaimsIdentity identity = CreateIdentity(user); - owinContext.Authentication.SignIn(identity); + ClaimsIdentity identity = CreateIdentity(user, authenticationType); + + // Create authentication properties + var props = new AuthenticationProperties() + { + IsPersistent = true + }; + + // Issue the session token + owinContext.Authentication.SignIn(props, identity); } public virtual AuthenticatedUser Register(string username, string password, string emailAddress) @@ -238,11 +247,6 @@ public virtual User GeneratePasswordResetToken(string usernameOrEmail, int expir return user; } - public static ClaimsIdentity CreateIdentity(AuthenticatedUser user) - { - return CreateIdentity(user.User, user.CredentialUsed.Type); - } - public static ClaimsIdentity CreateIdentity(User user, string authenticationType) { ClaimsIdentity identity = new ClaimsIdentity( diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs new file mode 100644 index 0000000000..e794a186be --- /dev/null +++ b/src/NuGetGallery/Authentication/AuthenticationTypes.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NuGetGallery.Authentication +{ + public static class AuthenticationTypes + { + public static readonly string Cookie = "cookie"; + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/AuthenticationController.cs b/src/NuGetGallery/Controllers/AuthenticationController.cs index 7ebafedf17..3b0fcdcebd 100644 --- a/src/NuGetGallery/Controllers/AuthenticationController.cs +++ b/src/NuGetGallery/Controllers/AuthenticationController.cs @@ -67,7 +67,7 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) return View(); } - AuthService.CreateSession(OwinContext, user); + AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Cookie); return SafeRedirect(returnUrl); } @@ -104,7 +104,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) return View(); } - AuthService.CreateSession(OwinContext, user); + AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Cookie); if (RedirectHelper.SafeRedirectUrl(Url, returnUrl) != RedirectHelper.SafeRedirectUrl(Url, null)) { diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 08add5605a..d6b05e3a51 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -319,6 +319,7 @@ + diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 17303f4846..0e5b155c02 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -201,16 +201,16 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() var passwordCred = Fakes.Admin.Credentials.SingleOrDefault( c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); - var user = new AuthenticatedUser(Fakes.Admin, passwordCred); + var authUser = new AuthenticatedUser(Fakes.Admin, passwordCred); // Act - service.CreateSession(context.Object, user); + service.CreateSession(context.Object, authUser.User, AuthenticationTypes.Cookie); // Assert Assert.NotNull(id); var principal = new ClaimsPrincipal(id); Assert.Equal(Fakes.Admin.Username, id.Name); - Assert.Equal(passwordCred.Type, id.AuthenticationType); + Assert.Equal(AuthenticationTypes.Cookie, id.AuthenticationType); Assert.True(principal.IsInRole(Constants.AdminRoleName)); } } diff --git a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs index d36294158a..9c1d56369d 100644 --- a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs @@ -66,7 +66,7 @@ public void CanLogTheUserOnWithUserName() .Returns(authUser); var controller = GetController(); GetMock() - .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Setup(a => a.CreateSession(controller.OwinContext, authUser.User, AuthenticationTypes.Cookie)) .Verifiable(); // Act @@ -90,7 +90,7 @@ public void CanLogTheUserOnWithEmailAddress() .Returns(authUser); var controller = GetController(); GetMock() - .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Setup(a => a.CreateSession(controller.OwinContext, authUser.User, AuthenticationTypes.Cookie)) .Verifiable(); // Act @@ -114,7 +114,7 @@ public void WillLogTheUserOnWithUsernameEvenWithoutConfirmedEmailAddress() .Returns(authUser); var controller = GetController(); GetMock() - .Setup(a => a.CreateSession(controller.OwinContext, authUser)) + .Setup(a => a.CreateSession(controller.OwinContext, authUser.User, AuthenticationTypes.Cookie)) .Verifiable(); // Act @@ -152,7 +152,7 @@ public void WillRedirectToHomeIfReturnUrlNotLocal() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); GetMock() - .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); + .Setup(x => x.CreateSession(It.IsAny(), authUser.User, AuthenticationTypes.Cookie)); var controller = GetController(); var result = controller.SignIn( @@ -172,7 +172,7 @@ public void WillRedirectToTheReturnUrlIfLocal() .Setup(x => x.Authenticate("confirmed@example.com", "thePassword")) .Returns(authUser); GetMock() - .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); + .Setup(x => x.CreateSession(It.IsAny(), authUser.User, AuthenticationTypes.Cookie)); var controller = GetController(); var result = controller.SignIn( @@ -207,7 +207,7 @@ public void WillCreateAndLogInTheUser() .Setup(x => x.Register("theUsername", "thePassword", "theEmailAddress")) .Returns(authUser); GetMock() - .Setup(x => x.CreateSession(It.IsAny(), authUser)) + .Setup(x => x.CreateSession(It.IsAny(), authUser.User, AuthenticationTypes.Cookie)) .Verifiable(); var controller = GetController(); @@ -251,7 +251,7 @@ public void WillRedirectToTheReturnUrl() .Setup(x => x.Register("theUsername", "thepassword", "unconfirmed@example.com")) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() - .Setup(x => x.CreateSession(It.IsAny(), It.IsAny())); + .Setup(x => x.CreateSession(It.IsAny(), user, AuthenticationTypes.Cookie)); var controller = GetController(); var result = controller.Register(new RegisterRequest From 4cf0eb44060e2be9b6db96de9ee2417d06acb248 Mon Sep 17 00:00:00 2001 From: anurse Date: Tue, 15 Oct 2013 18:00:13 -0700 Subject: [PATCH 10/18] Fix issue with IStatisticsService bindings being null --- .../App_Start/ContainerBindings.cs | 6 ++ src/NuGetGallery/Controllers/ApiController.cs | 53 +++++++-------- .../Controllers/StatisticsController.cs | 10 +-- src/NuGetGallery/Helpers/StatisticsHelper.cs | 2 +- src/NuGetGallery/Services/IReportService.cs | 12 ++++ .../Services/IStatisticsService.cs | 68 +++++++++++++++++++ .../ViewModels/StatisticsPackagesReport.cs | 19 ++++-- 7 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/NuGetGallery/App_Start/ContainerBindings.cs b/src/NuGetGallery/App_Start/ContainerBindings.cs index 089cfd7085..1167f3f0a9 100644 --- a/src/NuGetGallery/App_Start/ContainerBindings.cs +++ b/src/NuGetGallery/App_Start/ContainerBindings.cs @@ -222,6 +222,12 @@ private void ConfigureForLocalFileSystem() Bind() .To() .InSingletonScope(); + + // Ninject is doing some weird things with constructor selection without these. + // Anyone requesting an IReportService or IStatisticsService should be prepared + // to receive null anyway. + Bind().ToConstant(NullReportService.Instance); + Bind().ToConstant(NullStatisticsService.Instance); } private void ConfigureForAzureStorage(ConfigurationService configuration) diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index f9ad685f3f..22f5b8e949 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -382,43 +382,40 @@ public virtual ActionResult GetPackageVersions(string id, bool? includePrereleas [HttpGet] public virtual async Task GetStatsDownloads(int? count) { - if (StatisticsService != null) + var result = await StatisticsService.LoadDownloadPackageVersions(); + + if (result.Loaded) { - var result = await StatisticsService.LoadDownloadPackageVersions(); + int i = 0; - if (result.Loaded) + JArray content = new JArray(); + foreach (StatisticsPackagesItemViewModel row in StatisticsService.DownloadPackageVersionsAll) { - int i = 0; - - JArray content = new JArray(); - foreach (StatisticsPackagesItemViewModel row in StatisticsService.DownloadPackageVersionsAll) - { - JObject item = new JObject(); + JObject item = new JObject(); - item.Add("PackageId", row.PackageId); - item.Add("PackageVersion", row.PackageVersion); - item.Add("Gallery", Url.PackageGallery(row.PackageId, row.PackageVersion)); - item.Add("PackageTitle", row.PackageTitle ?? row.PackageId); - item.Add("PackageDescription", row.PackageDescription); - item.Add("PackageIconUrl", row.PackageIconUrl ?? Url.PackageDefaultIcon()); - item.Add("Downloads", row.Downloads); + item.Add("PackageId", row.PackageId); + item.Add("PackageVersion", row.PackageVersion); + item.Add("Gallery", Url.PackageGallery(row.PackageId, row.PackageVersion)); + item.Add("PackageTitle", row.PackageTitle ?? row.PackageId); + item.Add("PackageDescription", row.PackageDescription); + item.Add("PackageIconUrl", row.PackageIconUrl ?? Url.PackageDefaultIcon()); + item.Add("Downloads", row.Downloads); - content.Add(item); + content.Add(item); - i++; - - if (count.HasValue && count.Value == i) - { - break; - } - } + i++; - return new ContentResult + if (count.HasValue && count.Value == i) { - Content = content.ToString(), - ContentType = "application/json" - }; + break; + } } + + return new ContentResult + { + Content = content.ToString(), + ContentType = "application/json" + }; } return new HttpStatusCodeResult(HttpStatusCode.NotFound); diff --git a/src/NuGetGallery/Controllers/StatisticsController.cs b/src/NuGetGallery/Controllers/StatisticsController.cs index 696c5404bc..06fc2e2a91 100644 --- a/src/NuGetGallery/Controllers/StatisticsController.cs +++ b/src/NuGetGallery/Controllers/StatisticsController.cs @@ -99,7 +99,7 @@ private CultureInfo DetermineClientLocale() public virtual async Task Index() { - if (_statisticsService == null) + if (_statisticsService == NullStatisticsService.Instance) { return new HttpStatusCodeResult(HttpStatusCode.NotFound); } @@ -141,7 +141,7 @@ public virtual async Task Index() public virtual async Task Packages() { - if (_statisticsService == null) + if (_statisticsService == NullStatisticsService.Instance) { return new HttpStatusCodeResult(HttpStatusCode.NotFound); } @@ -164,7 +164,7 @@ public virtual async Task Packages() public virtual async Task PackageVersions() { - if (_statisticsService == null) + if (_statisticsService == NullStatisticsService.Instance) { return new HttpStatusCodeResult(HttpStatusCode.NotFound); } @@ -187,7 +187,7 @@ public virtual async Task PackageVersions() public virtual async Task PackageDownloadsByVersion(string id, string[] groupby) { - if (_statisticsService == null) + if (_statisticsService == NullStatisticsService.Instance) { return new HttpStatusCodeResult(HttpStatusCode.NotFound); } @@ -215,7 +215,7 @@ public virtual async Task PackageDownloadsByVersion(string id, str public virtual async Task PackageDownloadsDetail(string id, string version, string[] groupby) { - if (_statisticsService == null) + if (_statisticsService == NullStatisticsService.Instance) { return new HttpStatusCodeResult(HttpStatusCode.NotFound); } diff --git a/src/NuGetGallery/Helpers/StatisticsHelper.cs b/src/NuGetGallery/Helpers/StatisticsHelper.cs index 754fcf2658..39c9049bb6 100644 --- a/src/NuGetGallery/Helpers/StatisticsHelper.cs +++ b/src/NuGetGallery/Helpers/StatisticsHelper.cs @@ -9,7 +9,7 @@ public static bool IsStatisticsPageAvailable get { var statistics = DependencyResolver.Current.GetService(); - return (statistics != null); + return (statistics != NullStatisticsService.Instance); } } } diff --git a/src/NuGetGallery/Services/IReportService.cs b/src/NuGetGallery/Services/IReportService.cs index f90cc03f49..6f39862ea7 100644 --- a/src/NuGetGallery/Services/IReportService.cs +++ b/src/NuGetGallery/Services/IReportService.cs @@ -8,4 +8,16 @@ public interface IReportService { Task Load(string name); } + + public class NullReportService : IReportService + { + public static readonly NullReportService Instance = new NullReportService(); + + private NullReportService() { } + + public Task Load(string name) + { + return Task.FromResult(null); + } + } } \ No newline at end of file diff --git a/src/NuGetGallery/Services/IStatisticsService.cs b/src/NuGetGallery/Services/IStatisticsService.cs index f1bdef2b7a..1d1da6e9a6 100644 --- a/src/NuGetGallery/Services/IStatisticsService.cs +++ b/src/NuGetGallery/Services/IStatisticsService.cs @@ -1,6 +1,7 @@  using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace NuGetGallery @@ -22,4 +23,71 @@ public interface IStatisticsService Task GetPackageDownloadsByVersion(string packageId); Task GetPackageVersionDownloadsByClient(string packageId, string packageVersion); } + + public class NullStatisticsService : IStatisticsService + { + public static readonly NullStatisticsService Instance = new NullStatisticsService(); + + private NullStatisticsService() { } + + public IEnumerable DownloadPackagesSummary + { + get { return Enumerable.Empty(); } + } + + public IEnumerable DownloadPackageVersionsSummary + { + get { return Enumerable.Empty(); } + } + + public IEnumerable DownloadPackagesAll + { + get { return Enumerable.Empty(); } + } + + public IEnumerable DownloadPackageVersionsAll + { + get { return Enumerable.Empty(); } + } + + public IEnumerable NuGetClientVersion + { + get { return Enumerable.Empty(); } + } + + public IEnumerable Last6Months + { + get { return Enumerable.Empty(); } + } + + public Task LoadDownloadPackages() + { + return Task.FromResult(StatisticsReportResult.Failed); + } + + public Task LoadDownloadPackageVersions() + { + return Task.FromResult(StatisticsReportResult.Failed); + } + + public Task LoadNuGetClientVersion() + { + return Task.FromResult(StatisticsReportResult.Failed); + } + + public Task LoadLast6Months() + { + return Task.FromResult(StatisticsReportResult.Failed); + } + + public Task GetPackageDownloadsByVersion(string packageId) + { + return Task.FromResult(new StatisticsPackagesReport()); + } + + public Task GetPackageVersionDownloadsByClient(string packageId, string packageVersion) + { + return Task.FromResult(new StatisticsPackagesReport()); + } + } } diff --git a/src/NuGetGallery/ViewModels/StatisticsPackagesReport.cs b/src/NuGetGallery/ViewModels/StatisticsPackagesReport.cs index e15485985b..1c8a92878a 100644 --- a/src/NuGetGallery/ViewModels/StatisticsPackagesReport.cs +++ b/src/NuGetGallery/ViewModels/StatisticsPackagesReport.cs @@ -1,18 +1,16 @@  using System; using System.Collections.Generic; +using System.Linq; namespace NuGetGallery { public class StatisticsPackagesReport { - private List _rows = new List(); - private List _dimensions = new List(); - - public IList Rows { get { return _rows; } } + public IList Rows { get; private set; } public string Total { get; set; } - public IList Dimensions { get { return _dimensions; } } + public IList Dimensions { get; private set; } public IList Facts { get; set; } public ICollection Table { get; set; } @@ -21,5 +19,16 @@ public class StatisticsPackagesReport public DateTime? LastUpdatedUtc { get; set; } public string Id { get; set; } + + public StatisticsPackagesReport() + { + Total = String.Empty; + Id = String.Empty; + Columns = Enumerable.Empty(); + Facts = new List(); + Table = new List(); + Rows = new List(); + Dimensions = new List(); + } } } \ No newline at end of file From 583f64482f3835feb8d7358dcf8bfbb9a214a264 Mon Sep 17 00:00:00 2001 From: anurse Date: Wed, 16 Oct 2013 11:34:31 -0700 Subject: [PATCH 11/18] Added a glimpse tab to view auth info. --- .../Diagnostics/AuthenticationGlimpseTab.cs | 22 +++++++++++++++++++ src/NuGetGallery/NuGetGallery.csproj | 1 + 2 files changed, 23 insertions(+) create mode 100644 src/NuGetGallery/Diagnostics/AuthenticationGlimpseTab.cs diff --git a/src/NuGetGallery/Diagnostics/AuthenticationGlimpseTab.cs b/src/NuGetGallery/Diagnostics/AuthenticationGlimpseTab.cs new file mode 100644 index 0000000000..a883ac684e --- /dev/null +++ b/src/NuGetGallery/Diagnostics/AuthenticationGlimpseTab.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Glimpse.AspNet.Extensibility; +using Glimpse.Core.Extensibility; + +namespace NuGetGallery.Diagnostics +{ + public class AuthenticationGlimpseTab : AspNetTab + { + public override object GetData(ITabContext context) + { + return context.GetRequestContext().User; + } + + public override string Name + { + get { return "Auth"; } + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index d6b05e3a51..06e8a9b58d 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -329,6 +329,7 @@ + From 344ddeedbad88a2ecdbcd788f5d4608705aa4449 Mon Sep 17 00:00:00 2001 From: anurse Date: Wed, 16 Oct 2013 15:21:26 -0700 Subject: [PATCH 12/18] Added API Key auth middleware --- src/NuGetGallery/App_Start/OwinStartup.cs | 5 +++ .../ApiKeyAuthenticationExtensions.cs | 24 ++++++++++++ .../ApiKeyAuthenticationHandler.cs | 38 +++++++++++++++++++ .../ApiKeyAuthenticationMiddleware.cs | 27 +++++++++++++ .../ApiKeyAuthenticationOptions.cs | 19 ++++++++++ .../Authentication/AuthenticationService.cs | 8 +--- .../Authentication/AuthenticationTypes.cs | 1 + .../Authentication/NuGetClaims.cs | 12 ++++++ src/NuGetGallery/Constants.cs | 2 + src/NuGetGallery/NuGetGallery.csproj | 5 +++ 10 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs create mode 100644 src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs create mode 100644 src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs create mode 100644 src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs create mode 100644 src/NuGetGallery/Authentication/NuGetClaims.cs diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 8085b362e6..67324ffb0b 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -26,6 +26,11 @@ public static void Configuration(IAppBuilder app) CookieHttpOnly = true, LoginPath = "/users/account/logon" }); + app.UseApiKeyAuthentication(new ApiKeyAuthenticationOptions() + { + AuthenticationType = AuthenticationTypes.ApiKey, + ApiKeyFormName = Constants.ApiKeyParameterName + }); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs new file mode 100644 index 0000000000..14b16bc9a9 --- /dev/null +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using NuGetGallery.Authentication; +using Microsoft.Owin.Extensions; + +namespace Owin +{ + public static class ApiKeyAuthenticationExtensions + { + public static IAppBuilder UseApiKeyAuthentication(this IAppBuilder self, ApiKeyAuthenticationOptions options) + { + if (self == null) + { + throw new ArgumentNullException("self"); + } + + self.Use(typeof(ApiKeyAuthenticationMiddleware), self, options); + self.UseStageMarker(PipelineStage.Authenticate); + return self; + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs new file mode 100644 index 0000000000..a6145195d3 --- /dev/null +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; + +namespace NuGetGallery.Authentication +{ + public class ApiKeyAuthenticationHandler : AuthenticationHandler + { + private readonly ILogger _logger; + + public ApiKeyAuthenticationHandler(ILogger logger) + { + _logger = logger; + } + + protected override async Task AuthenticateCoreAsync() + { + var form = await Request.ReadFormAsync(); + var apiKey = form.Get(Options.ApiKeyFormName); + if (!String.IsNullOrEmpty(apiKey)) + { + var id = new ClaimsIdentity( + claims: new[] { + new Claim(Options.ApiKeyClaim, apiKey) + }, + authenticationType: Options.AuthenticationType); + return new AuthenticationTicket(id, new AuthenticationProperties()); + } + return null; + } + } +} diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs new file mode 100644 index 0000000000..5518d12041 --- /dev/null +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security.Infrastructure; +using Owin; + +namespace NuGetGallery.Authentication +{ + public class ApiKeyAuthenticationMiddleware : AuthenticationMiddleware + { + private ILogger _logger; + + public ApiKeyAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, ApiKeyAuthenticationOptions options) + : base(next, options) + { + _logger = app.CreateLogger(); + } + + protected override AuthenticationHandler CreateHandler() + { + return new ApiKeyAuthenticationHandler(_logger); + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs new file mode 100644 index 0000000000..9e7524bf80 --- /dev/null +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Microsoft.Owin.Security; + +namespace NuGetGallery.Authentication +{ + public class ApiKeyAuthenticationOptions : AuthenticationOptions + { + public string ApiKeyFormName { get; set; } + public string ApiKeyClaim { get; set; } + + public ApiKeyAuthenticationOptions() : base(AuthenticationTypes.ApiKey) { + ApiKeyFormName = "apiKey"; + ApiKeyClaim = NuGetClaims.ApiKey; + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index 799ef0b7ee..8b732ca845 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -92,14 +92,8 @@ public virtual void CreateSession(IOwinContext owinContext, User user, string au // Create a claims identity for the session ClaimsIdentity identity = CreateIdentity(user, authenticationType); - // Create authentication properties - var props = new AuthenticationProperties() - { - IsPersistent = true - }; - // Issue the session token - owinContext.Authentication.SignIn(props, identity); + owinContext.Authentication.SignIn(identity); } public virtual AuthenticatedUser Register(string username, string password, string emailAddress) diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs index e794a186be..68bbaaca16 100644 --- a/src/NuGetGallery/Authentication/AuthenticationTypes.cs +++ b/src/NuGetGallery/Authentication/AuthenticationTypes.cs @@ -8,5 +8,6 @@ namespace NuGetGallery.Authentication public static class AuthenticationTypes { public static readonly string Cookie = "cookie"; + public static readonly string ApiKey = "apikey"; } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/NuGetClaims.cs b/src/NuGetGallery/Authentication/NuGetClaims.cs new file mode 100644 index 0000000000..75bd67339a --- /dev/null +++ b/src/NuGetGallery/Authentication/NuGetClaims.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NuGetGallery.Authentication +{ + public static class NuGetClaims + { + public static readonly string ApiKey = "https://claims.nuget.org/apikey"; + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Constants.cs b/src/NuGetGallery/Constants.cs index 4ed65b3b45..176eabf805 100644 --- a/src/NuGetGallery/Constants.cs +++ b/src/NuGetGallery/Constants.cs @@ -35,6 +35,8 @@ public static class Constants public const string UrlValidationRegEx = @"(https?):\/\/[^ ""]+$"; public const string UrlValidationErrorMessage = "This doesn't appear to be a valid HTTP/HTTPS URL"; + public static readonly string ApiKeyParameterName = "apikey"; + public static class ContentNames { public static readonly string FrontPageAnnouncement = "FrontPage-Announcement"; diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 06e8a9b58d..07c9b5816c 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -317,9 +317,14 @@ + + + + + From 2bfbed3cfbe7bff4e042fdf5af9cac319d86fadb Mon Sep 17 00:00:00 2001 From: anurse Date: Thu, 17 Oct 2013 11:35:55 -0700 Subject: [PATCH 13/18] Updated ApiKey Auth module to be path-specific --- .../App_Start/AuthenticationModule.cs | 46 ---- src/NuGetGallery/App_Start/OwinStartup.cs | 3 +- .../ApiKeyAuthenticationHandler.cs | 75 ++++-- .../ApiKeyAuthenticationMiddleware.cs | 5 +- .../ApiKeyAuthenticationOptions.cs | 5 +- .../Authentication/AuthenticationTypes.cs | 1 + .../Authentication/ResolvedUserIdentity.cs | 27 ++ src/NuGetGallery/Constants.cs | 2 +- src/NuGetGallery/Controllers/ApiController.cs | 38 ++- src/NuGetGallery/ExtensionMethods.cs | 9 + src/NuGetGallery/NuGetGallery.csproj | 2 +- src/NuGetGallery/Strings.Designer.cs | 11 +- src/NuGetGallery/Strings.resx | 5 +- .../ApiKeyAuthenticationHandlerFacts.cs | 238 ++++++++++++++++++ .../Controllers/ApiControllerFacts.cs | 6 +- .../NuGetGallery.Facts.csproj | 1 + 16 files changed, 382 insertions(+), 92 deletions(-) delete mode 100644 src/NuGetGallery/App_Start/AuthenticationModule.cs create mode 100644 src/NuGetGallery/Authentication/ResolvedUserIdentity.cs create mode 100644 tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs diff --git a/src/NuGetGallery/App_Start/AuthenticationModule.cs b/src/NuGetGallery/App_Start/AuthenticationModule.cs deleted file mode 100644 index e79854112d..0000000000 --- a/src/NuGetGallery/App_Start/AuthenticationModule.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading; -using System.Web; -using System.Web.Security; -using Microsoft.Web.Infrastructure.DynamicModuleHelper; -using NuGetGallery; - -[assembly: WebActivator.PreApplicationStartMethod(typeof(AuthenticationModule), "Start")] - -namespace NuGetGallery -{ - public class AuthenticationModule : IHttpModule - { - public void Init(HttpApplication context) - { - context.AuthenticateRequest += OnAuthenticateRequest; - } - - public void Dispose() - { - } - - public static void Start() - { - //DynamicModuleUtility.RegisterModule(typeof(AuthenticationModule)); - } - - private void OnAuthenticateRequest(object sender, EventArgs e) - { - var context = HttpContext.Current; - var request = HttpContext.Current.Request; - if (request.IsAuthenticated) - { - HttpCookie authCookie = request.Cookies[FormsAuthentication.FormsCookieName]; - if (authCookie != null) - { - FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); - var roles = authTicket.UserData.Split('|'); - var user = new GenericPrincipal(context.User.Identity, roles); - context.User = Thread.CurrentPrincipal = user; - } - } - } - } -} \ No newline at end of file diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 67324ffb0b..5d8c8fb204 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -28,8 +28,9 @@ public static void Configuration(IAppBuilder app) }); app.UseApiKeyAuthentication(new ApiKeyAuthenticationOptions() { + RootPath = "/api", AuthenticationType = AuthenticationTypes.ApiKey, - ApiKeyFormName = Constants.ApiKeyParameterName + ApiKeyHeaderName = Constants.ApiKeyHeaderName }); } } diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs index a6145195d3..a80950f3aa 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs @@ -12,27 +12,74 @@ namespace NuGetGallery.Authentication { public class ApiKeyAuthenticationHandler : AuthenticationHandler { - private readonly ILogger _logger; + protected ILogger Logger { get; set; } + protected AuthenticationService Auth { get; set; } - public ApiKeyAuthenticationHandler(ILogger logger) + internal ApiKeyAuthenticationHandler() { } + public ApiKeyAuthenticationHandler(ILogger logger, AuthenticationService auth) { - _logger = logger; + Logger = logger; + Auth = auth; } - protected override async Task AuthenticateCoreAsync() + protected override Task AuthenticateCoreAsync() { - var form = await Request.ReadFormAsync(); - var apiKey = form.Get(Options.ApiKeyFormName); - if (!String.IsNullOrEmpty(apiKey)) + // Check if we should run based on root path + if (IsPathMatch(Context.Request.Path)) { - var id = new ClaimsIdentity( - claims: new[] { - new Claim(Options.ApiKeyClaim, apiKey) - }, - authenticationType: Options.AuthenticationType); - return new AuthenticationTicket(id, new AuthenticationProperties()); + var apiKey = Request.Headers[Options.ApiKeyHeaderName]; + if (!String.IsNullOrEmpty(apiKey)) + { + // Get the user + var user = Auth.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); + if (user != null) + { + return Task.FromResult( + new AuthenticationTicket( + new ResolvedUserIdentity(user, AuthenticationTypes.ApiKey), + new AuthenticationProperties())); + } + else + { + Logger.WriteWarning("No match for API Key!"); + } + } + else + { + Logger.WriteVerbose("No API Key Header found in request."); + } } - return null; + else + { + Logger.WriteVerbose("Skipped API Key authentication. Not under specified root path: " + Request.Path); + } + return Task.FromResult(null); + } + + protected bool IsPathMatch(string path) + { + if (String.IsNullOrEmpty(Options.RootPath)) + { + return true; + } + + var root = NormalizeRootPath(Options.RootPath); + path = String.IsNullOrEmpty(path) ? "/" : path; + + return path.StartsWith(root, StringComparison.OrdinalIgnoreCase); + } + + private string NormalizeRootPath(string path) + { + if (path.StartsWith("~")) + { + path = path.Substring(1); + } + if (path.EndsWith("/")) + { + path = path.Substring(0, path.Length - 1); + } + return path; } } } diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs index 5518d12041..681a72756b 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationMiddleware.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; +using System.Web.Mvc; using Microsoft.Owin; using Microsoft.Owin.Logging; using Microsoft.Owin.Security.Infrastructure; @@ -21,7 +22,9 @@ public ApiKeyAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, ApiK protected override AuthenticationHandler CreateHandler() { - return new ApiKeyAuthenticationHandler(_logger); + return new ApiKeyAuthenticationHandler( + _logger, + DependencyResolver.Current.GetService()); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs index 9e7524bf80..99803f9c27 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationOptions.cs @@ -8,11 +8,12 @@ namespace NuGetGallery.Authentication { public class ApiKeyAuthenticationOptions : AuthenticationOptions { - public string ApiKeyFormName { get; set; } + public string ApiKeyHeaderName { get; set; } public string ApiKeyClaim { get; set; } + public string RootPath { get; set; } public ApiKeyAuthenticationOptions() : base(AuthenticationTypes.ApiKey) { - ApiKeyFormName = "apiKey"; + ApiKeyHeaderName = Constants.ApiKeyHeaderName; ApiKeyClaim = NuGetClaims.ApiKey; } } diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs index 68bbaaca16..361f45f4a5 100644 --- a/src/NuGetGallery/Authentication/AuthenticationTypes.cs +++ b/src/NuGetGallery/Authentication/AuthenticationTypes.cs @@ -9,5 +9,6 @@ public static class AuthenticationTypes { public static readonly string Cookie = "cookie"; public static readonly string ApiKey = "apikey"; + public static readonly string ResolvedUser = "resolveduser"; } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs b/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs new file mode 100644 index 0000000000..a9b064a589 --- /dev/null +++ b/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; + +namespace NuGetGallery.Authentication +{ + public class ResolvedUserIdentity : ClaimsIdentity + { + public User User { get; private set; } + + public ResolvedUserIdentity(AuthenticatedUser authUser, string authenticationType) + : base(authenticationType) + { + User = authUser.User; + + AddClaim(new Claim(ClaimTypes.Name, User.Username)); + AddClaim(new Claim(ClaimTypes.Email, User.EmailAddress)); + + if (User.Roles != null) + { + AddClaims(User.Roles.Select(r => new Claim(ClaimTypes.Role, r.Name))); + } + } + } +} diff --git a/src/NuGetGallery/Constants.cs b/src/NuGetGallery/Constants.cs index 176eabf805..ec791bf193 100644 --- a/src/NuGetGallery/Constants.cs +++ b/src/NuGetGallery/Constants.cs @@ -35,7 +35,7 @@ public static class Constants public const string UrlValidationRegEx = @"(https?):\/\/[^ ""]+$"; public const string UrlValidationErrorMessage = "This doesn't appear to be a valid HTTP/HTTPS URL"; - public static readonly string ApiKeyParameterName = "apikey"; + public static readonly string ApiKeyHeaderName = "X-NuGet-ApiKey"; public static class ContentNames { diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index 22f5b8e949..f32cbcafbd 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -67,8 +67,8 @@ public ApiController( StatisticsService = statisticsService; } - [ActionName("GetPackageApi")] [HttpGet] + [ActionName("GetPackageApi")] public virtual async Task GetPackage(string id, string version) { // some security paranoia about URL hacking somehow creating e.g. open redirects @@ -127,21 +127,21 @@ public virtual async Task GetPackage(string id, string version) catch (SqlException e) { // Log the error and continue - QuietlyLogException(e); + QuietLog.LogHandledException(e); } catch (DataException e) { // Log the error and continue - QuietlyLogException(e); + QuietLog.LogHandledException(e); } } catch (SqlException e) { - QuietlyLogException(e); + QuietLog.LogHandledException(e); } catch (DataException e) { - QuietlyLogException(e); + QuietLog.LogHandledException(e); } // Fall back to constructing the URL based on the package version and ID. @@ -165,6 +165,7 @@ public virtual Task GetNuGetExe() } [HttpGet] + [Authorize] [ActionName("VerifyPackageKeyApi")] public virtual ActionResult VerifyPackageKey(string apiKey, string id, string version) { @@ -181,7 +182,7 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve var user = GetUserByApiKey(apiKey); if (user == null || !package.IsOwner(user)) { - return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "push")); + return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, Strings.ApiKeyNotAuthorized); } } @@ -189,6 +190,7 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve } [HttpPut] + [Authorize] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual Task CreatePackagePut(string apiKey) @@ -198,6 +200,7 @@ public virtual Task CreatePackagePut(string apiKey) } [HttpPost] + [Authorize] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual Task CreatePackagePost(string apiKey) @@ -255,6 +258,7 @@ private async Task CreatePackageInternal(User user) return new HttpStatusCodeResult(HttpStatusCode.Created); } + [Authorize] [HttpDelete] [ActionName("DeletePackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] @@ -286,6 +290,7 @@ public virtual ActionResult DeletePackage(string apiKey, string id, string versi } [HttpPost] + [Authorize] [ActionName("PublishPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual ActionResult PublishPackage(string apiKey, string id, string version) @@ -354,8 +359,9 @@ protected internal virtual INupkg ReadPackageFromRequest() return new Nupkg(stream, leaveOpen: false); } - [ActionName("PackageIDs")] [HttpGet] + [Authorize] + [ActionName("PackageIDs")] public virtual ActionResult GetPackageIds(string partialId, bool? includePrerelease) { var query = GetService(); @@ -366,8 +372,9 @@ public virtual ActionResult GetPackageIds(string partialId, bool? includePrerele }; } - [ActionName("PackageVersions")] [HttpGet] + [Authorize] + [ActionName("PackageVersions")] public virtual ActionResult GetPackageVersions(string id, bool? includePrerelease) { var query = GetService(); @@ -378,8 +385,9 @@ public virtual ActionResult GetPackageVersions(string id, bool? includePrereleas }; } - [ActionName("StatisticsDownloadsApi")] [HttpGet] + [Authorize] + [ActionName("StatisticsDownloadsApi")] public virtual async Task GetStatsDownloads(int? count) { var result = await StatisticsService.LoadDownloadPackageVersions(); @@ -433,17 +441,5 @@ private User GetUserByApiKey(string apiKey) return authUser.User; } } - - private static void QuietlyLogException(Exception e) - { - try - { - Elmah.ErrorSignal.FromCurrentContext().Raise(e); - } - catch - { - // logging failed, don't allow exception to escape - } - } } } diff --git a/src/NuGetGallery/ExtensionMethods.cs b/src/NuGetGallery/ExtensionMethods.cs index b34268c4a0..7965f02ea4 100644 --- a/src/NuGetGallery/ExtensionMethods.cs +++ b/src/NuGetGallery/ExtensionMethods.cs @@ -8,6 +8,7 @@ using System.Net.Mail; using System.Runtime.Versioning; using System.Security; +using System.Security.Claims; using System.Security.Principal; using System.ServiceModel.Activation; using System.Text; @@ -306,5 +307,13 @@ public static string ToFriendlyName(this FrameworkName frameworkName) } return sb.ToString(); } + + public static string GetClaimOrDefault(this ClaimsPrincipal self, string claimType) + { + return self.Claims + .Where(c => String.Equals(c.Type, claimType, StringComparison.OrdinalIgnoreCase)) + .Select(c => c.Value) + .FirstOrDefault(); + } } } \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 07c9b5816c..665e3946c2 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -325,6 +325,7 @@ + @@ -947,7 +948,6 @@ - diff --git a/src/NuGetGallery/Strings.Designer.cs b/src/NuGetGallery/Strings.Designer.cs index 4d45c422cc..69572d5725 100644 --- a/src/NuGetGallery/Strings.Designer.cs +++ b/src/NuGetGallery/Strings.Designer.cs @@ -61,7 +61,7 @@ internal Strings() { } /// - /// Looks up a localized string similar to The specified API key does not provide the authority to {0} packages.. + /// Looks up a localized string similar to The specified API key is invalid or does not have permission to access the specified package.. /// public static string ApiKeyNotAuthorized { get { @@ -69,6 +69,15 @@ public static string ApiKeyNotAuthorized { } } + /// + /// Looks up a localized string similar to An API key must be provided in the 'X-NuGet-ApiKey' header to use this service. + /// + public static string ApiKeyRequired { + get { + return ResourceManager.GetString("ApiKeyRequired", resourceCulture); + } + } + /// /// Looks up a localized string similar to You must confirm the email address for the account in order to use the API key.. /// diff --git a/src/NuGetGallery/Strings.resx b/src/NuGetGallery/Strings.resx index eb3b6851fc..532d0cfde4 100644 --- a/src/NuGetGallery/Strings.resx +++ b/src/NuGetGallery/Strings.resx @@ -136,7 +136,7 @@ A nuget package's {0} property may not be more than {1} characters long. - The specified API key does not provide the authority to {0} packages. + The specified API key is invalid or does not have permission to access the specified package. A package with id '{0}' and version '{1}' already exists and cannot be modified. @@ -189,4 +189,7 @@ Password credentials cannot be used with Authenticate(Credential). Use Authenticate(string, string) instead. + + An API key must be provided in the 'X-NuGet-ApiKey' header to use this service + \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs new file mode 100644 index 0000000000..aadfc26651 --- /dev/null +++ b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Infrastructure; +using Moq; +using Xunit; +using Xunit.Extensions; + +namespace NuGetGallery.Authentication +{ + public class ApiKeyAuthenticationHandlerFacts + { + public class TheIsPathMatchMethod + { + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("/a")] + [InlineData("/a/b")] + [InlineData("/a/b/c")] + [InlineData("/a/b/c/d/e")] + [InlineData("/z")] + public async Task GivenNoRootPath_AllPathsMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(); + Assert.True(handler.InvokeIsPathMatch(path)); + } + + [Theory] + [InlineData("/api")] + [InlineData("/api/v2")] + [InlineData("/api/v2/Packages")] + public async Task GivenARootPath_PathsUnderAndIncludingThatRootMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + Assert.True(handler.InvokeIsPathMatch(path)); + } + + [Theory] + [InlineData("/api")] + [InlineData("/api/v2")] + [InlineData("/api/v2/Packages")] + public async Task GivenARootPathWithTrailingSlash_PathsUnderAndIncludingThatRootMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api/" + }); + Assert.True(handler.InvokeIsPathMatch(path)); + } + + [Theory] + [InlineData("/api")] + [InlineData("/api/v2")] + [InlineData("/api/v2/Packages")] + public async Task GivenARootPathWithTrailingSlashAndTildeSlash_PathsUnderAndIncludingThatRootMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "~/api/" + }); + Assert.True(handler.InvokeIsPathMatch(path)); + } + + [Theory] + [InlineData("/api")] + [InlineData("/api/v2")] + [InlineData("/api/v2/Packages")] + public async Task GivenARootPathWithTildeSlash_PathsUnderAndIncludingThatRootMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "~/api" + }); + Assert.True(handler.InvokeIsPathMatch(path)); + } + + [Theory] + [InlineData("/packages")] + [InlineData("/")] + [InlineData("/flarglebargle")] + public async Task GivenARootPath_PathsNotUnderAndIncludingThatRootMatch(string path) + { + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + Assert.False(handler.InvokeIsPathMatch(path)); + } + } + + public class TheAuthenticateCoreAsyncMethod + { + [Fact] + public async Task GivenANonMatchingPath_ItReturnsNull() + { + // Arrange + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + handler.MockContext.Setup(c => c.Request.Path).Returns("/packages"); + + // Act + var ticket = await handler.InvokeAuthenticateCoreAsync(); + + // Assert + Assert.Null(ticket); + } + + [Fact] + public async Task GivenNoApiKeyHeader_ItReturnsNull() + { + // Arrange + TestableApiKeyAuthenticationHandler handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); + + // Act + var ticket = await handler.InvokeAuthenticateCoreAsync(); + + // Assert + Assert.Null(ticket); + } + + [Fact] + public async Task GivenNoUserMatchingApiKey_ItReturnsNull() + { + // Arrange + Guid apiKey = Guid.NewGuid(); + TestableApiKeyAuthenticationHandler handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); + handler.MockContext.Object.Request.Headers.Set( + Constants.ApiKeyHeaderName, + apiKey.ToString().ToLowerInvariant()); + + // Act + var ticket = await handler.InvokeAuthenticateCoreAsync(); + + // Assert + Assert.Null(ticket); + } + + [Fact] + public async Task GivenMatchingApiKey_ItReturnsTicketForResolvedUser() + { + // Arrange + Guid apiKey = Guid.NewGuid(); + var user = new User() { Username = "theUser", EmailAddress = "confirmed@example.com" }; + TestableApiKeyAuthenticationHandler handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); + handler.MockContext.Object.Request.Headers.Set( + Constants.ApiKeyHeaderName, + apiKey.ToString().ToLowerInvariant()); + handler.MockAuth.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); + + // Act + var ticket = await handler.InvokeAuthenticateCoreAsync(); + + // Assert + Assert.NotNull(ticket); + var id = Assert.IsType(ticket.Identity); + Assert.Same(user, id.User); + } + } + + // Why a TestableNNN class? Because we need to access protected members. + public class TestableApiKeyAuthenticationHandler : ApiKeyAuthenticationHandler + { + public Mock MockContext { get; private set; } + public Mock MockAuth { get; private set; } + public Mock MockLogger { get; private set; } + + private TestableApiKeyAuthenticationHandler() + { + Logger = (MockLogger = new Mock()).Object; + Auth = (MockAuth = new Mock()).Object; + } + + public static Task CreateAsync() + { + return CreateAsync(new ApiKeyAuthenticationOptions()); + } + + public static async Task CreateAsync(ApiKeyAuthenticationOptions options) + { + // Always use passive mode for tests + options.AuthenticationMode = AuthenticationMode.Passive; + + var handler = new TestableApiKeyAuthenticationHandler(); + + var ctxt = (handler.MockContext = new Mock()).Object; + + handler.MockContext.Setup(c => c.Request).Returns(new Mock().Object); + handler.MockContext.Setup(c => c.Response).Returns(new Mock().Object); + handler.MockContext.Setup(c => c.Request.PathBase).Returns("/testroot"); + handler.MockContext.Setup(c => c.Request.Headers).Returns(new HeaderDictionary(new Dictionary())); + + // Grr, have to make an internal call to initialize... + await (Task)(typeof(AuthenticationHandler) + .InvokeMember( + "Initialize", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, + Type.DefaultBinder, + handler, + new object[] { options, ctxt })); + return handler; + } + + public Task InvokeAuthenticateCoreAsync() + { + return AuthenticateCoreAsync(); + } + + public bool InvokeIsPathMatch(string path) + { + return IsPathMatch(path); + } + } + } +} diff --git a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs index ef9b40d66c..c7f2017ec2 100644 --- a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs @@ -609,8 +609,8 @@ public void VerifyPackageKeyReturns403IfUserDoesNotExist() // Assert ResultAssert.IsStatusCode( result, - HttpStatusCode.Forbidden, - "The specified API key does not provide the authority to push packages."); + HttpStatusCode.Forbidden, + Strings.ApiKeyNotAuthorized); } [Fact] @@ -670,7 +670,7 @@ public void VerifyPackageKeyReturns403IfUserIsNotAnOwner() ResultAssert.IsStatusCode( result, HttpStatusCode.Forbidden, - "The specified API key does not provide the authority to push packages."); + Strings.ApiKeyNotAuthorized); } [Fact] diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index 2dd35c91f8..c69dd4b0b2 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -214,6 +214,7 @@ Properties\CommonAssemblyInfo.cs + From 6627cfedbd7e5baf5107b1184411f5f3ec946e3d Mon Sep 17 00:00:00 2001 From: anurse Date: Thu, 17 Oct 2013 14:58:29 -0700 Subject: [PATCH 14/18] Configured cookie and API key auth to play nicely --- build/Enable-LocalTestMe.ps1 | 96 +++++++++++++++---- src/NuGetGallery/App_Start/OwinStartup.cs | 16 ++-- .../ApiKeyAuthenticationExtensions.cs | 5 + .../Authentication/UserSession.cs | 44 --------- src/NuGetGallery/Constants.cs | 1 + src/NuGetGallery/Controllers/ApiController.cs | 20 ++-- src/NuGetGallery/Controllers/AppController.cs | 32 ++++--- .../Filters/ApiAuthorizeAttribute.cs | 19 ++++ src/NuGetGallery/NuGetGallery.csproj | 9 +- 9 files changed, 147 insertions(+), 95 deletions(-) delete mode 100644 src/NuGetGallery/Authentication/UserSession.cs create mode 100644 src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs diff --git a/build/Enable-LocalTestMe.ps1 b/build/Enable-LocalTestMe.ps1 index f99598adda..92f890b37b 100644 --- a/build/Enable-LocalTestMe.ps1 +++ b/build/Enable-LocalTestMe.ps1 @@ -1,31 +1,90 @@ -param([switch]$Force, [string]$Subdomain="nuget") +param([string]$Subdomain="nuget", [string]$SiteName = "NuGet Gallery", [string]$SitePhysicalPath, [string]$MakeCertPath, [string]$AppCmdPath) if(!(([Security.Principal.WindowsPrincipal]([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator"))) { throw "This script must be run as an admin." } -$WebSite = Resolve-Path (Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\Website") +if(!$SitePhysicalPath) { + $ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path; + $SitePhysicalPath = Join-Path $ScriptRoot "..\src\NuGetGallery" +} +if(!(Test-Path $SitePhysicalPath)) { + throw "Could not find site at $SitePhysicalPath. Use -SitePhysicalPath argument to specify the path." +} +$SitePhysicalPath = Convert-Path $SitePhysicalPath -# Enable access to the necessary URLs -netsh http add urlacl url=http://nuget.localtest.me:80/ user=Everyone -netsh http add urlacl url=https://nuget.localtest.me:443/ user=Everyone +# Find Windows SDK +if(!$MakeCertPath) { + $SDKVersion = dir 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows' | + where { $_.PSChildName -match "v(?\d+\.\d+)" } | + foreach { New-Object System.Version $($matches["ver"]) } | + sort -desc | + select -first 1 + if(!$SDKVersion) { + throw "Could not find Windows SDK. Please install the Windows SDK before running this script, or use -MakeCertPath to specify the path to makecert.exe" + } + $SDKRegKey = (Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v$SDKVersion") + $WinSDKDir = $SDKRegKey.InstallationFolder + $xArch = "x86" + if($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { + $xArch = "x64" + } + $MakeCertPath = Join-Path $WinSDKDir "bin\$xArch\makecert.exe" +} -$IISExpressDir = "$env:ProgramFiles\IIS Express" -if(!(Test-Path $IISExpressDir)) { - throw "Can't find IIS Express in $IISExpressDir" +if(!(Test-Path $MakeCertPath)) { + throw "Could not find makecert.exe in $MakeCertPath!" } -$AppCmd = "$IISExpressDir\appcmd.exe" -$sites = @(&$AppCmd list site "NuGet Gallery ($Subdomain.localtest.me)") -if($sites.Length -gt 0) { - if($Force) { - &$AppCmd delete site "NuGet Gallery ($Subdomain.localtest.me)" +# Find IIS Express +if(!$AppCmdPath) { + $IISXVersion = dir 'HKLM:\Software\Microsoft\IISExpress' | + foreach { New-Object System.Version ($_.PSChildName) } | + sort -desc | + select -first 1 + if(!$IISXVersion) { + throw "Could not find IIS Express. Please install IIS Express before running this script, or use -AppCmdPath to specify the path to appcmd.exe for your IIS environment" + } + $IISRegKey = (Get-ItemProperty "HKLM:\Software\Microsoft\IISExpress\$IISXVersion") + $IISExpressDir = $IISRegKey.InstallPath + if(!(Test-Path $IISExpressDir)) { + throw "Can't find IIS Express in $IISExpressDir. Please install IIS Express" + } + $AppCmdPath = "$IISExpressDir\appcmd.exe" +} + +if(!(Test-Path $AppCmdPath)) { + throw "Could not find appcmd.exe in $AppCmdPath!" +} + +function Invoke-Netsh() { + $argStr = $([String]::Join(" ", $args)) + Write-Verbose "netsh $argStr" + $result = netsh @args + $parsed = [Regex]::Match($result, ".*Error: (\d+).*") + if($parsed.Success) { + $err = $parsed.Groups[1].Value + if($err -ne "183") { + throw $result + } } else { - throw "You already have a site named `"NuGet Gallery ($Subdomain.localtest.me)`". Remove it manually or use -Force to have this command auto-remove it" + Write-Host $result } } -&$AppCmd add site /name:"NuGet Gallery ($Subdomain.localtest.me)" /bindings:"http://$Subdomain.localtest.me:80,https://$Subdomain.localtest.me:443" /physicalPath:$WebSite +# Enable access to the necessary URLs +Invoke-Netsh http add urlacl "url=http://$Subdomain.localtest.me:80/" user=Everyone +Invoke-Netsh http add urlacl "url=https://$Subdomain.localtest.me:443/" user=Everyone + + +$SiteFullName = "$SiteName ($Subdomain.localtest.me)" +$sites = @(&$AppCmdPath list site $SiteFullName) +if($sites.Length -gt 0) { + Write-Warning "Site '$SiteFullName' already exists. Deleting and recreating." + &$AppCmdPath delete site "$SiteFullName" +} + +&$AppCmdPath add site /name:"$SiteFullName" /bindings:"http://$Subdomain.localtest.me:80,https://$Subdomain.localtest.me:443" /physicalPath:$SitePhysicalPath # Check for a cert $cert = @(dir -l "Cert:\CurrentUser\Root" | where {$_.Subject -eq "CN=$Subdomain.localtest.me"}) @@ -36,7 +95,7 @@ if($cert.Length -eq 0) { if($cert.Length -eq 0) { Write-Host "Generating a Self-Signed SSL Certificate for $Subdomain.localtest.me" # Generate one - & makecert -r -pe -n "CN=$Subdomain.localtest.me" -b `"$([DateTime]::Now.ToString("MM/dd/yyy"))`" -e `"$([DateTime]::Now.AddYears(10).ToString("MM/dd/yyy"))`" -eku 1.3.6.1.5.5.7.3.1 -ss root -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 + & $MakeCertPath -r -pe -n "CN=$Subdomain.localtest.me" -b `"$([DateTime]::Now.ToString("MM/dd/yyy"))`" -e `"$([DateTime]::Now.AddYears(10).ToString("MM/dd/yyy"))`" -eku 1.3.6.1.5.5.7.3.1 -ss root -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 $cert = @(dir -l "Cert:\LocalMachine\Root" | where {$_.Subject -eq "CN=$Subdomain.localtest.me"}) } @@ -47,7 +106,6 @@ if($cert.Length -eq 0) { Write-Host "Using SSL Certificate: $($cert.Thumbprint)" # Set the Certificate -netsh http add sslcert hostnameport="$Subdomain.localtest.me:443" certhash="$($cert.Thumbprint)" certstorename=Root appid="{$([Guid]::NewGuid().ToString())}" +Invoke-Netsh http add sslcert hostnameport="$Subdomain.localtest.me:443" certhash="$($cert.Thumbprint)" certstorename=Root appid="{$([Guid]::NewGuid().ToString())}" -Write-Host "Ready! All you have to do now is go to your Website project properties and set 'http://$Subdomain.localtest.me' as your Project URL" -Write-Host "To use SSL, set the IISExpressSSLPort MSBuild property in your Website.csproj.user to 443" \ No newline at end of file +Write-Host "Ready! All you have to do now is go to your website project properties and set 'http://$Subdomain.localtest.me' as your Project URL!" \ No newline at end of file diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 5d8c8fb204..b9ef03c525 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -3,12 +3,14 @@ using System.Linq; using System.Web; using Owin; +using Ninject; using Microsoft.Owin; using Microsoft.Owin.Extensions; using Microsoft.Owin.Diagnostics; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using NuGetGallery.Authentication; +using NuGetGallery.Configuration; [assembly: OwinStartup(typeof(NuGetGallery.OwinStartup))] @@ -19,19 +21,19 @@ public class OwinStartup // This method is auto-detected by the OWIN pipeline. DO NOT RENAME IT! public static void Configuration(IAppBuilder app) { + + var config = Container.Kernel.Get(); + var cookieSecurity = config.Current.RequireSSL ? CookieSecureOption.Always : CookieSecureOption.Never; + app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationType = AuthenticationTypes.Cookie, AuthenticationMode = AuthenticationMode.Active, CookieHttpOnly = true, - LoginPath = "/users/account/logon" - }); - app.UseApiKeyAuthentication(new ApiKeyAuthenticationOptions() - { - RootPath = "/api", - AuthenticationType = AuthenticationTypes.ApiKey, - ApiKeyHeaderName = Constants.ApiKeyHeaderName + CookieSecure = cookieSecurity, + LoginPath = "/users/account/LogOn" }); + app.UseApiKeyAuthentication(); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs index 14b16bc9a9..33d6167136 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs @@ -9,6 +9,11 @@ namespace Owin { public static class ApiKeyAuthenticationExtensions { + public static IAppBuilder UseApiKeyAuthentication(this IAppBuilder self) + { + UseApiKeyAuthentication(self, new ApiKeyAuthenticationOptions()); + } + public static IAppBuilder UseApiKeyAuthentication(this IAppBuilder self, ApiKeyAuthenticationOptions options) { if (self == null) diff --git a/src/NuGetGallery/Authentication/UserSession.cs b/src/NuGetGallery/Authentication/UserSession.cs deleted file mode 100644 index e3b3381e8e..0000000000 --- a/src/NuGetGallery/Authentication/UserSession.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Web; - -namespace NuGetGallery.Authentication -{ - public class UserSession : IPrincipal, IIdentity - { - public virtual ClaimsPrincipal Principal { get; private set; } - - public virtual string AuthenticationType - { - get { return Principal.Identity.AuthenticationType; } - } - - public virtual bool IsAuthenticated - { - get { return Principal.Identity.IsAuthenticated; } - } - - public virtual string Name - { - get { return Principal.Identity.Name; } - } - - public virtual IIdentity Identity - { - get { return Principal.Identity; } - } - - public UserSession(ClaimsPrincipal principal) - { - Principal = principal; - } - - public virtual bool IsInRole(string role) - { - return Principal.IsInRole(role); - } - } -} \ No newline at end of file diff --git a/src/NuGetGallery/Constants.cs b/src/NuGetGallery/Constants.cs index ec791bf193..01647780c2 100644 --- a/src/NuGetGallery/Constants.cs +++ b/src/NuGetGallery/Constants.cs @@ -36,6 +36,7 @@ public static class Constants public const string UrlValidationErrorMessage = "This doesn't appear to be a valid HTTP/HTTPS URL"; public static readonly string ApiKeyHeaderName = "X-NuGet-ApiKey"; + public static readonly string ReturnUrlParameterName = "ReturnUrl"; public static class ContentNames { diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index f32cbcafbd..0befaaac55 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using System.Web; using System.Web.Mvc; using System.Web.UI; using Newtonsoft.Json.Linq; @@ -190,27 +191,28 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve } [HttpPut] - [Authorize] + [ApiAuthorize] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - public virtual Task CreatePackagePut(string apiKey) + public virtual Task CreatePackagePut() { - var user = GetUserByApiKey(apiKey); - return CreatePackageInternal(user); + return CreatePackageInternal(); } [HttpPost] - [Authorize] + [ApiAuthorize] [ActionName("PushPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - public virtual Task CreatePackagePost(string apiKey) + public virtual Task CreatePackagePost() { - var user = GetUserByApiKey(apiKey); - return CreatePackageInternal(user); + return CreatePackageInternal(); } - private async Task CreatePackageInternal(User user) + private async Task CreatePackageInternal() { + // Get the user + + using (var packageToPush = ReadPackageFromRequest()) { // Ensure that the user can push packages for this partialId. diff --git a/src/NuGetGallery/Controllers/AppController.cs b/src/NuGetGallery/Controllers/AppController.cs index 42fca132ac..cb5d33afb6 100644 --- a/src/NuGetGallery/Controllers/AppController.cs +++ b/src/NuGetGallery/Controllers/AppController.cs @@ -1,6 +1,7 @@ using System; using System.Security.Claims; using System.Security.Principal; +using System.Linq; using System.Web; using System.Web.Mvc; using Microsoft.Owin; @@ -10,7 +11,7 @@ namespace NuGetGallery { public abstract partial class AppController : Controller { - private Lazy _session; + private Lazy _id; private IOwinContext _overrideContext; [Obsolete("Use UserSession instead!")] @@ -25,11 +26,6 @@ public virtual IIdentity Identity get { return base.User; } } - public UserSession UserSession - { - get { return _session.Value; } - } - public IOwinContext OwinContext { get { return _overrideContext ?? HttpContext.GetOwinContext(); } @@ -38,7 +34,7 @@ public IOwinContext OwinContext public AppController() { - _session = new Lazy(LoadSession); + _session = new Lazy(ResolveUser); } protected internal virtual T GetService() @@ -46,22 +42,36 @@ protected internal virtual T GetService() return DependencyResolver.Current.GetService(); } - private UserSession LoadSession() + private ResolvedUserIdentity ResolveUser() { - if (!HttpContext.Request.IsAuthenticated) + if (OwinContext.Authentication.User == null) { return null; } - ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal; + ClaimsPrincipal principal = OwinContext.Authentication.User as ClaimsPrincipal; if (principal == null) { return null; } else { - return new UserSession(principal); + var id = principal.Identities.OfType().SingleOrDefault(); + if (id != null) + { + return id; + } + else + { + id = LoadUser(); + principal.AddIdentity(id); + } } } + + private ResolvedUserIdentity LoadUser() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs new file mode 100644 index 0000000000..de6c58d4b4 --- /dev/null +++ b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using NuGetGallery.Authentication; + +namespace NuGetGallery.Filters +{ + public class ApiAuthorizeAttribute : AuthorizeAttribute + { + protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) + { + var owinContext = filterContext.HttpContext.GetOwinContext(); + owinContext.Authentication.Challenge(AuthenticationTypes.ApiKey); + filterContext.Result = new HttpUnauthorizedResult(); + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 665e3946c2..be2a3db560 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -28,8 +28,7 @@ 4.0 - - + 443 true enabled disabled @@ -326,7 +325,6 @@ - @@ -620,6 +618,7 @@ + @@ -1399,9 +1398,9 @@ - False + True True - 8085 + 80 / http://nuget.localtest.me False From 00079db8ea06c296a6b4a3004fa78ee1d7c6d960 Mon Sep 17 00:00:00 2001 From: anurse Date: Thu, 17 Oct 2013 16:55:45 -0700 Subject: [PATCH 15/18] Got it building with the new GetCurrentUser() helper --- src/NuGetGallery/ApiController.generated.cs | 9 +- .../ApiKeyAuthenticationExtensions.cs | 2 +- .../ApiKeyAuthenticationHandler.cs | 83 ++-- .../Authentication/ResolvedUserIdentity.cs | 27 -- src/NuGetGallery/Constants.cs | 1 + src/NuGetGallery/Controllers/ApiController.cs | 65 +-- src/NuGetGallery/Controllers/AppController.cs | 83 ++-- .../Controllers/CuratedFeedsController.cs | 2 +- .../Controllers/CuratedPackagesController.cs | 8 +- .../Controllers/PackagesController.cs | 64 ++- .../Controllers/UsersController.cs | 34 +- src/NuGetGallery/ExtensionMethods.cs | 12 +- .../Filters/ApiAuthorizeAttribute.cs | 1 + .../RequiresAccountConfirmationAttribute.cs | 13 +- src/NuGetGallery/NuGetGallery.csproj | 1 - src/NuGetGallery/Strings.Designer.cs | 9 + src/NuGetGallery/Strings.resx | 3 + .../ApiKeyAuthenticationHandlerFacts.cs | 191 +++++--- .../Controllers/ApiControllerFacts.cs | 191 ++------ .../Controllers/AppControllerFacts.cs | 42 ++ .../CuratedFeedsControllerFacts.cs | 14 +- .../CuratedPackagesControllerFacts.cs | 34 +- .../Controllers/PackagesControllerFacts.cs | 446 ++++++------------ .../Controllers/UsersControllerFacts.cs | 194 +++----- tests/NuGetGallery.Facts/Framework/Fakes.cs | 15 + .../Framework/TestExtensionMethods.cs | 24 +- .../NuGetGallery.Facts.csproj | 1 + .../TestUtils/TestUtility.cs | 2 +- 28 files changed, 672 insertions(+), 899 deletions(-) delete mode 100644 src/NuGetGallery/Authentication/ResolvedUserIdentity.cs create mode 100644 tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs diff --git a/src/NuGetGallery/ApiController.generated.cs b/src/NuGetGallery/ApiController.generated.cs index e1eff9b642..df9ced635b 100644 --- a/src/NuGetGallery/ApiController.generated.cs +++ b/src/NuGetGallery/ApiController.generated.cs @@ -89,25 +89,22 @@ public class ViewNames { public class T4MVC_ApiController: NuGetGallery.ApiController { public T4MVC_ApiController() : base(Dummy.Instance) { } - public override System.Web.Mvc.ActionResult VerifyPackageKey(string apiKey, string id, string version) { + public override System.Web.Mvc.ActionResult VerifyPackageKey(string id, string version) { var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.VerifyPackageKey); - callInfo.RouteValueDictionary.Add("apiKey", apiKey); callInfo.RouteValueDictionary.Add("id", id); callInfo.RouteValueDictionary.Add("version", version); return callInfo; } - public override System.Web.Mvc.ActionResult DeletePackage(string apiKey, string id, string version) { + public override System.Web.Mvc.ActionResult DeletePackage(string id, string version) { var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.DeletePackage); - callInfo.RouteValueDictionary.Add("apiKey", apiKey); callInfo.RouteValueDictionary.Add("id", id); callInfo.RouteValueDictionary.Add("version", version); return callInfo; } - public override System.Web.Mvc.ActionResult PublishPackage(string apiKey, string id, string version) { + public override System.Web.Mvc.ActionResult PublishPackage(string id, string version) { var callInfo = new T4MVC_ActionResult(Area, Name, ActionNames.PublishPackage); - callInfo.RouteValueDictionary.Add("apiKey", apiKey); callInfo.RouteValueDictionary.Add("id", id); callInfo.RouteValueDictionary.Add("version", version); return callInfo; diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs index 33d6167136..02ebd41e41 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationExtensions.cs @@ -11,7 +11,7 @@ public static class ApiKeyAuthenticationExtensions { public static IAppBuilder UseApiKeyAuthentication(this IAppBuilder self) { - UseApiKeyAuthentication(self, new ApiKeyAuthenticationOptions()); + return UseApiKeyAuthentication(self, new ApiKeyAuthenticationOptions()); } public static IAppBuilder UseApiKeyAuthentication(this IAppBuilder self, ApiKeyAuthenticationOptions options) diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs index a80950f3aa..ae490a47d4 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs @@ -22,64 +22,67 @@ public ApiKeyAuthenticationHandler(ILogger logger, AuthenticationService auth) Auth = auth; } - protected override Task AuthenticateCoreAsync() + protected override async Task ApplyResponseChallengeAsync() { - // Check if we should run based on root path - if (IsPathMatch(Context.Request.Path)) + string message = GetChallengeMessage(); + + if (message != null) { - var apiKey = Request.Headers[Options.ApiKeyHeaderName]; - if (!String.IsNullOrEmpty(apiKey)) - { - // Get the user - var user = Auth.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); - if (user != null) - { - return Task.FromResult( - new AuthenticationTicket( - new ResolvedUserIdentity(user, AuthenticationTypes.ApiKey), - new AuthenticationProperties())); - } - else - { - Logger.WriteWarning("No match for API Key!"); - } - } - else - { - Logger.WriteVerbose("No API Key Header found in request."); - } + Response.ReasonPhrase = message; + Response.Write(message); } else { - Logger.WriteVerbose("Skipped API Key authentication. Not under specified root path: " + Request.Path); + await base.ApplyResponseChallengeAsync(); } - return Task.FromResult(null); } - protected bool IsPathMatch(string path) + internal string GetChallengeMessage() { - if (String.IsNullOrEmpty(Options.RootPath)) + string message = null; + if (Response.StatusCode == 401 && (Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode) != null)) { - return true; - } - - var root = NormalizeRootPath(Options.RootPath); - path = String.IsNullOrEmpty(path) ? "/" : path; + var apiKey = Request.Headers[Options.ApiKeyHeaderName]; + message = Strings.ApiKeyRequired; + if (!String.IsNullOrEmpty(apiKey)) + { + // Had an API key, but it wasn't valid + message = Strings.ApiKeyNotAuthorized; + } - return path.StartsWith(root, StringComparison.OrdinalIgnoreCase); + } + return message; } - private string NormalizeRootPath(string path) + protected override Task AuthenticateCoreAsync() { - if (path.StartsWith("~")) + var apiKey = Request.Headers[Options.ApiKeyHeaderName]; + if (!String.IsNullOrEmpty(apiKey)) { - path = path.Substring(1); + // Get the user + var user = Auth.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); + if (user != null) + { + // Set the current user + Context.Set(Constants.CurrentUserOwinEnvironmentKey, user); + + return Task.FromResult( + new AuthenticationTicket( + new ClaimsIdentity(new[] { + new Claim(NuGetClaims.ApiKey, apiKey) + }), + new AuthenticationProperties())); + } + else + { + Logger.WriteWarning("No match for API Key!"); + } } - if (path.EndsWith("/")) + else { - path = path.Substring(0, path.Length - 1); + Logger.WriteVerbose("No API Key Header found in request."); } - return path; + return Task.FromResult(null); } } } diff --git a/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs b/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs deleted file mode 100644 index a9b064a589..0000000000 --- a/src/NuGetGallery/Authentication/ResolvedUserIdentity.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text; - -namespace NuGetGallery.Authentication -{ - public class ResolvedUserIdentity : ClaimsIdentity - { - public User User { get; private set; } - - public ResolvedUserIdentity(AuthenticatedUser authUser, string authenticationType) - : base(authenticationType) - { - User = authUser.User; - - AddClaim(new Claim(ClaimTypes.Name, User.Username)); - AddClaim(new Claim(ClaimTypes.Email, User.EmailAddress)); - - if (User.Roles != null) - { - AddClaims(User.Roles.Select(r => new Claim(ClaimTypes.Role, r.Name))); - } - } - } -} diff --git a/src/NuGetGallery/Constants.cs b/src/NuGetGallery/Constants.cs index 01647780c2..2700cfbf96 100644 --- a/src/NuGetGallery/Constants.cs +++ b/src/NuGetGallery/Constants.cs @@ -37,6 +37,7 @@ public static class Constants public static readonly string ApiKeyHeaderName = "X-NuGet-ApiKey"; public static readonly string ReturnUrlParameterName = "ReturnUrl"; + public static readonly string CurrentUserOwinEnvironmentKey = "nuget.user"; public static class ContentNames { diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index 0befaaac55..7b8f893f51 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -28,8 +28,7 @@ public partial class ApiController : AppController public IStatisticsService StatisticsService { get; set; } public IContentService ContentService { get; set; } public IIndexingService IndexingService { get; set; } - public AuthenticationService AuthService { get; set; } - + protected ApiController() { } public ApiController( @@ -39,8 +38,7 @@ public ApiController( IUserService userService, INuGetExeDownloaderService nugetExeDownloaderService, IContentService contentService, - IIndexingService indexingService, - AuthenticationService authService) + IIndexingService indexingService) { EntitiesContext = entitiesContext; PackageService = packageService; @@ -50,7 +48,6 @@ public ApiController( ContentService = contentService; StatisticsService = null; IndexingService = indexingService; - AuthService = authService; } public ApiController( @@ -61,9 +58,8 @@ public ApiController( INuGetExeDownloaderService nugetExeDownloaderService, IContentService contentService, IIndexingService indexingService, - IStatisticsService statisticsService, - AuthenticationService authService) - : this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService, indexingService, authService) + IStatisticsService statisticsService) + : this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService, indexingService) { StatisticsService = statisticsService; } @@ -166,9 +162,9 @@ public virtual Task GetNuGetExe() } [HttpGet] - [Authorize] + [ApiAuthorize] [ActionName("VerifyPackageKeyApi")] - public virtual ActionResult VerifyPackageKey(string apiKey, string id, string version) + public virtual ActionResult VerifyPackageKey(string id, string version) { if (!String.IsNullOrEmpty(id)) { @@ -180,8 +176,8 @@ public virtual ActionResult VerifyPackageKey(string apiKey, string id, string ve HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version)); } - var user = GetUserByApiKey(apiKey); - if (user == null || !package.IsOwner(user)) + var user = GetCurrentUser(); + if (!package.IsOwner(user)) { return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, Strings.ApiKeyNotAuthorized); } @@ -211,7 +207,7 @@ public virtual Task CreatePackagePost() private async Task CreatePackageInternal() { // Get the user - + var user = GetCurrentUser(); using (var packageToPush = ReadPackageFromRequest()) { @@ -260,11 +256,11 @@ private async Task CreatePackageInternal() return new HttpStatusCodeResult(HttpStatusCode.Created); } - [Authorize] [HttpDelete] + [ApiAuthorize] [ActionName("DeletePackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - public virtual ActionResult DeletePackage(string apiKey, string id, string version) + public virtual ActionResult DeletePackage(string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); if (package == null) @@ -273,13 +269,7 @@ public virtual ActionResult DeletePackage(string apiKey, string id, string versi HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version)); } - User user = GetUserByApiKey(apiKey); - if (user == null) - { - return new HttpStatusCodeWithBodyResult( - HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "delete")); - } - + var user = GetCurrentUser(); if (!package.IsOwner(user)) { return new HttpStatusCodeWithBodyResult( @@ -292,10 +282,10 @@ public virtual ActionResult DeletePackage(string apiKey, string id, string versi } [HttpPost] - [Authorize] + [ApiAuthorize] [ActionName("PublishPackageApi")] [RequireRemoteHttps(OnlyWhenAuthenticated = false)] - public virtual ActionResult PublishPackage(string apiKey, string id, string version) + public virtual ActionResult PublishPackage(string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); if (package == null) @@ -304,13 +294,7 @@ public virtual ActionResult PublishPackage(string apiKey, string id, string vers HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version)); } - User user = GetUserByApiKey(apiKey); - if (user == null) - { - return new HttpStatusCodeWithBodyResult( - HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "publish")); - } - + User user = GetCurrentUser(); if (!package.IsOwner(user)) { return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "publish")); @@ -362,7 +346,7 @@ protected internal virtual INupkg ReadPackageFromRequest() } [HttpGet] - [Authorize] + [ApiAuthorize] [ActionName("PackageIDs")] public virtual ActionResult GetPackageIds(string partialId, bool? includePrerelease) { @@ -375,7 +359,7 @@ public virtual ActionResult GetPackageIds(string partialId, bool? includePrerele } [HttpGet] - [Authorize] + [ApiAuthorize] [ActionName("PackageVersions")] public virtual ActionResult GetPackageVersions(string id, bool? includePrerelease) { @@ -388,7 +372,7 @@ public virtual ActionResult GetPackageVersions(string id, bool? includePrereleas } [HttpGet] - [Authorize] + [ApiAuthorize] [ActionName("StatisticsDownloadsApi")] public virtual async Task GetStatsDownloads(int? count) { @@ -430,18 +414,5 @@ public virtual async Task GetStatsDownloads(int? count) return new HttpStatusCodeResult(HttpStatusCode.NotFound); } - - private User GetUserByApiKey(string apiKey) - { - var authUser = AuthService.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); - if (authUser == null) - { - return null; - } - else - { - return authUser.User; - } - } } } diff --git a/src/NuGetGallery/Controllers/AppController.cs b/src/NuGetGallery/Controllers/AppController.cs index cb5d33afb6..7aab6f2cc4 100644 --- a/src/NuGetGallery/Controllers/AppController.cs +++ b/src/NuGetGallery/Controllers/AppController.cs @@ -6,35 +6,23 @@ using System.Web.Mvc; using Microsoft.Owin; using NuGetGallery.Authentication; +using System.Net; namespace NuGetGallery { public abstract partial class AppController : Controller { - private Lazy _id; private IOwinContext _overrideContext; - - [Obsolete("Use UserSession instead!")] - public virtual IIdentity Identity - { - get { return base.User.Identity; } - } - - [Obsolete("Use UserSession instead!")] - public new IPrincipal User - { - get { return base.User; } - } - + public IOwinContext OwinContext { get { return _overrideContext ?? HttpContext.GetOwinContext(); } set { _overrideContext = value; } } - public AppController() + public new ClaimsPrincipal User { - _session = new Lazy(ResolveUser); + get { return base.User as ClaimsPrincipal; } } protected internal virtual T GetService() @@ -42,36 +30,61 @@ protected internal virtual T GetService() return DependencyResolver.Current.GetService(); } - private ResolvedUserIdentity ResolveUser() + // This is a method because the first call will perform a database call + /// + /// Get the current user, from the database, or if someone in this request has already + /// retrieved it, from memory. This will NEVER return null. It will throw an exception + /// that will yield an HTTP 401 if it would return null. As a result, it should only + /// be called in actions with the Authorize attribute or a Request.IsAuthenticated check + /// + /// The current user + protected internal User GetCurrentUser() { - if (OwinContext.Authentication.User == null) + User user = null; + object obj; + if (OwinContext.Environment.TryGetValue(Constants.CurrentUserOwinEnvironmentKey, out obj)) { - return null; + user = obj as User; } - ClaimsPrincipal principal = OwinContext.Authentication.User as ClaimsPrincipal; - if (principal == null) + if (user == null) { - return null; + user = LoadUser(); + OwinContext.Environment[Constants.CurrentUserOwinEnvironmentKey] = user; } - else + + if (user == null) { - var id = principal.Identities.OfType().SingleOrDefault(); - if (id != null) - { - return id; - } - else - { - id = LoadUser(); - principal.AddIdentity(id); - } + // Unauthorized! If we get here it's because a valid session token was presented, but the + // user doesn't exist any more. So we just have a generic error. + throw new HttpException(401, Strings.Unauthorized); } + + return user; + } + + protected internal void SetCurrentUser(User user) + { + OwinContext.Environment[Constants.CurrentUserOwinEnvironmentKey] = user; } - private ResolvedUserIdentity LoadUser() + private User LoadUser() { - throw new NotImplementedException(); + var principal = OwinContext.Authentication.User; + if(principal != null) + { + // Try to authenticate with the user name + string userName = principal.GetClaimOrDefault(ClaimTypes.Name); + + if (!String.IsNullOrEmpty(userName)) + { + return DependencyResolver + .Current + .GetService() + .FindByUsername(userName); + } + } + return null; // No user logged in, or credentials could not be resolved } } } \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/CuratedFeedsController.cs b/src/NuGetGallery/Controllers/CuratedFeedsController.cs index 9c80ca4e49..0134c90c30 100644 --- a/src/NuGetGallery/Controllers/CuratedFeedsController.cs +++ b/src/NuGetGallery/Controllers/CuratedFeedsController.cs @@ -33,7 +33,7 @@ public virtual ActionResult CuratedFeed(string name) return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != User.Identity.Name)) { return new HttpStatusCodeResult(403); } diff --git a/src/NuGetGallery/Controllers/CuratedPackagesController.cs b/src/NuGetGallery/Controllers/CuratedPackagesController.cs index 0a5376aec0..4d9d647a69 100644 --- a/src/NuGetGallery/Controllers/CuratedPackagesController.cs +++ b/src/NuGetGallery/Controllers/CuratedPackagesController.cs @@ -31,7 +31,7 @@ public virtual ActionResult GetCreateCuratedPackageForm(string curatedFeedName) return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != User.Identity.Name)) { return new HttpStatusCodeResult(403); } @@ -58,7 +58,7 @@ public virtual ActionResult DeleteCuratedPackage( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != User.Identity.Name)) { return new HttpStatusCodeResult(403); } @@ -89,7 +89,7 @@ public virtual ActionResult PatchCuratedPackage( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != User.Identity.Name)) { return new HttpStatusCodeResult(403); } @@ -119,7 +119,7 @@ public virtual ActionResult PostCuratedPackages( return HttpNotFound(); } - if (curatedFeed.Managers.All(manager => manager.Username != UserSession.Name)) + if (curatedFeed.Managers.All(manager => manager.Username != User.Identity.Name)) { return new HttpStatusCodeResult(403); } diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs index 6571a362f0..15ddee9edc 100644 --- a/src/NuGetGallery/Controllers/PackagesController.cs +++ b/src/NuGetGallery/Controllers/PackagesController.cs @@ -74,7 +74,7 @@ public PackagesController( [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] public virtual ActionResult UploadPackageProgress() { - string username = UserSession.Name; + string username = User.Identity.Name; AsyncFileUploadProgress progress = _cacheService.GetProgress(username); if (progress == null) @@ -96,7 +96,7 @@ public virtual ActionResult UndoPendingEdits(string id, string version) return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(403, "Forbidden"); } @@ -144,7 +144,7 @@ public virtual ActionResult UndoPendingEdits(string id, string version) [RequiresAccountConfirmation("upload a package")] public async virtual Task UploadPackage() { - var currentUser = _userService.FindByUsername(UserSession.Name); + var currentUser = GetCurrentUser(); using (var existingUploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { @@ -159,11 +159,11 @@ public async virtual Task UploadPackage() [HttpPost] [Authorize] - [RequiresAccountConfirmation("upload a package")] [ValidateAntiForgeryToken] + [RequiresAccountConfirmation("upload a package")] public virtual async Task UploadPackage(HttpPostedFileBase uploadFile) { - var currentUser = _userService.FindByUsername(UserSession.Name); + var currentUser = GetCurrentUser(); using (var existingUploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) { @@ -243,7 +243,7 @@ public virtual ActionResult DisplayPackage(string id, string version) } var model = new DisplayPackageViewModel(package); - if (package.IsOwner(UserSession)) + if (package.IsOwner(User)) { // Tell logged-in package owners not to cache the package page, so they won't be confused about the state of pending edits. Response.Cache.SetCacheability(HttpCacheability.NoCache); @@ -329,7 +329,7 @@ public virtual ActionResult ReportAbuse(string id, string version) if (Request.IsAuthenticated) { - var user = _userService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); // If user logged on in as owner a different tab, then clicked the link, we can redirect them to ReportMyPackage if (package.IsOwner(user)) @@ -359,7 +359,7 @@ public virtual ActionResult ReportAbuse(string id, string version) [RequiresAccountConfirmation("contact support about your package")] public virtual ActionResult ReportMyPackage(string id, string version) { - var user = _userService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); var package = _packageService.FindPackageByIdAndVersion(id, version); @@ -369,7 +369,7 @@ public virtual ActionResult ReportMyPackage(string id, string version) } // If user hit this url by constructing it manually but is not the owner, redirect them to ReportAbuse - if (!(HttpContext.User.IsInRole(Constants.AdminRoleName) || package.IsOwner(user))) + if (!(User.IsInRole(Constants.AdminRoleName) || package.IsOwner(user))) { return RedirectToAction(ActionNames.ReportAbuse, new { id, version }); } @@ -408,7 +408,7 @@ public virtual ActionResult ReportAbuse(string id, string version, ReportAbuseVi MailAddress from; if (Request.IsAuthenticated) { - user = _userService.FindByUsername(UserSession.Name); + user = GetCurrentUser(); from = user.ToMailAddress(); } else @@ -454,7 +454,7 @@ public virtual ActionResult ReportMyPackage(string id, string version, ReportAbu return HttpNotFound(); } - var user = _userService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); MailAddress from = user.ToMailAddress(); _messageService.ReportMyPackage( @@ -512,7 +512,7 @@ public virtual ActionResult ContactOwners(string id, ContactOwnersViewModel cont return HttpNotFound(); } - var user = _userService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); var fromAddress = new MailAddress(user.EmailAddress, user.Username); _messageService.SendContactOwnersMessage( fromAddress, package, contactForm.Message, Url.Action(MVC.Users.Edit(), protocol: Request.Url.Scheme)); @@ -536,12 +536,12 @@ public virtual ActionResult ManagePackageOwners(string id, string version) { return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(401, "Unauthorized"); } - var model = new ManagePackageOwnersViewModel(package, HttpContext.User); + var model = new ManagePackageOwnersViewModel(package, User); return View(model); } @@ -573,7 +573,7 @@ public virtual ActionResult Edit(string id, string version) return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(403, "Forbidden"); } @@ -596,9 +596,9 @@ public virtual ActionResult Edit(string id, string version) [Authorize] [HttpPost] - [RequiresAccountConfirmation("edit a package")] - [ValidateAntiForgeryToken] [ValidateInput(false)] // Security note: Disabling ASP.Net input validation which does things like disallow angle brackets in submissions. See http://go.microsoft.com/fwlink/?LinkID=212874 + [ValidateAntiForgeryToken] + [RequiresAccountConfirmation("edit a package")] public virtual ActionResult Edit(string id, string version, EditPackageRequest formData, string returnUrl) { var package = _packageService.FindPackageByIdAndVersion(id, version); @@ -607,12 +607,12 @@ public virtual ActionResult Edit(string id, string version, EditPackageRequest f return HttpNotFound(); } - var user = _userService.FindByUsername(UserSession.Name); - if (user == null || !package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(403, "Forbidden"); } + var user = GetCurrentUser(); if (!ModelState.IsValid) { formData.PackageId = package.PackageRegistration.Id; @@ -646,7 +646,7 @@ public virtual ActionResult ConfirmOwner(string id, string username, string toke return HttpNotFound(); } - if (!String.Equals(username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(username, User.Identity.Name, StringComparison.OrdinalIgnoreCase)) { return View(new PackageOwnerConfirmationModel() { @@ -661,17 +661,7 @@ public virtual ActionResult ConfirmOwner(string id, string username, string toke return HttpNotFound(); } - var user = _userService.FindByUsername(username); - if (user == null) - { - return HttpNotFound(); - } - - if (!String.Equals(user.Username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) - { - return new HttpStatusCodeResult(403); - } - + var user = GetCurrentUser(); ConfirmOwnershipResult result = _packageService.ConfirmPackageOwner(package, user, token); var model = new PackageOwnerConfirmationModel @@ -690,7 +680,7 @@ internal virtual ActionResult Edit(string id, string version, bool? listed, Func { return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(401, "Unauthorized"); } @@ -723,7 +713,7 @@ private ActionResult GetPackageOwnerActionFormResult(string id, string version) { return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(401, "Unauthorized"); } @@ -736,7 +726,7 @@ private ActionResult GetPackageOwnerActionFormResult(string id, string version) [RequiresAccountConfirmation("upload a package")] public virtual async Task VerifyPackage() { - var currentUser = _userService.FindByUsername(UserSession.Name); + var currentUser = GetCurrentUser(); IPackageMetadata packageMetadata; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) @@ -792,7 +782,7 @@ public virtual async Task VerifyPackage() [ValidateInput(false)] // Security note: Disabling ASP.Net input validation which does things like disallow angle brackets in submissions. See http://go.microsoft.com/fwlink/?LinkID=212874 public virtual async Task VerifyPackage(VerifyPackageRequest formData) { - var currentUser = _userService.FindByUsername(UserSession.Name); + var currentUser = GetCurrentUser(); Package package; using (Stream uploadFile = await _uploadFileService.GetUploadFileAsync(currentUser.Key)) @@ -885,7 +875,7 @@ public virtual async Task VerifyPackage(VerifyPackageRequest formD [ValidateAntiForgeryToken] public virtual async Task CancelUpload() { - var currentUser = _userService.FindByUsername(UserSession.Name); + var currentUser = GetCurrentUser(); await _uploadFileService.DeleteUploadFileAsync(currentUser.Key); return RedirectToAction("UploadPackage"); @@ -906,7 +896,7 @@ internal virtual ActionResult SetLicenseReportVisibility(string id, string versi { return HttpNotFound(); } - if (!package.IsOwner(HttpContext.User)) + if (!package.IsOwner(User)) { return new HttpStatusCodeResult(401, "Unauthorized"); } diff --git a/src/NuGetGallery/Controllers/UsersController.cs b/src/NuGetGallery/Controllers/UsersController.cs index 78f60eb567..dbedf6d208 100644 --- a/src/NuGetGallery/Controllers/UsersController.cs +++ b/src/NuGetGallery/Controllers/UsersController.cs @@ -12,10 +12,10 @@ namespace NuGetGallery public partial class UsersController : AppController { public ICuratedFeedService CuratedFeedService { get; protected set; } + public IUserService UserService { get; protected set; } public IMessageService MessageService { get; protected set; } public IPackageService PackageService { get; protected set; } public IAppConfiguration Config { get; protected set; } - public IUserService UserService { get; protected set; } public AuthenticationService AuthService { get; protected set; } protected UsersController() { } @@ -39,7 +39,7 @@ public UsersController( [Authorize] public virtual ActionResult Account() { - var user = UserService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); var curatedFeeds = CuratedFeedService.GetFeedsForManager(user.Key); var apiCredential = user .Credentials @@ -55,11 +55,11 @@ public virtual ActionResult Account() }); } - [Authorize] [HttpGet] + [Authorize] public virtual ActionResult ConfirmationRequired() { - User user = UserService.FindByUsername(UserSession.Name); + User user = GetCurrentUser(); var model = new ConfirmationViewModel { ConfirmingNewAccount = !(user.Confirmed), @@ -73,7 +73,7 @@ public virtual ActionResult ConfirmationRequired() [ActionName("ConfirmationRequired")] public virtual ActionResult ConfirmationRequiredPost() { - User user = UserService.FindByUsername(UserSession.Name); + User user = GetCurrentUser(); var confirmationUrl = Url.ConfirmationUrl( MVC.Users.Confirm(), user.Username, user.EmailConfirmationToken, protocol: Request.Url.Scheme); @@ -91,7 +91,7 @@ public virtual ActionResult ConfirmationRequiredPost() [Authorize] public virtual ActionResult Edit() { - var user = UserService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); var model = new EditProfileViewModel { Username = user.Username, @@ -107,7 +107,7 @@ public virtual ActionResult Edit() [ValidateAntiForgeryToken] public virtual ActionResult Edit(EditProfileViewModel profile) { - var user = UserService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); if (user == null) { return HttpNotFound(); @@ -131,7 +131,7 @@ public virtual ActionResult Thanks() [Authorize] public virtual ActionResult Packages() { - var user = UserService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); var packages = PackageService.FindPackagesByOwner(user, includeUnlisted: true) .Select(p => new PackageViewModel(p) { @@ -152,7 +152,7 @@ public virtual ActionResult Packages() public virtual ActionResult GenerateApiKey() { // Get the user - var user = UserService.FindByUsername(UserSession.Name); + var user = GetCurrentUser(); // Generate an API Key var apiKey = Guid.NewGuid(); @@ -247,7 +247,7 @@ public virtual ActionResult Confirm(string username, string token) // By having this value present in the dictionary BUT null, we don't put "returnUrl" on the Login link at all ViewData[Constants.ReturnUrlViewDataKey] = null; - if (!String.Equals(username, UserSession.Name, StringComparison.OrdinalIgnoreCase)) + if (!String.Equals(username, User.Identity.Name, StringComparison.OrdinalIgnoreCase)) { return View(new ConfirmationViewModel { @@ -256,12 +256,8 @@ public virtual ActionResult Confirm(string username, string token) }); } - var user = UserService.FindByUsername(username); - if (user == null) - { - return HttpNotFound(); - } - + var user = GetCurrentUser(); + string existingEmail = user.EmailAddress; var model = new ConfirmationViewModel { @@ -338,7 +334,7 @@ public virtual ActionResult ChangeEmail(ChangeEmailRequestModel model) return View(model); } - var authUser = AuthService.Authenticate(UserSession.Name, model.Password); + var authUser = AuthService.Authenticate(User.Identity.Name, model.Password); if (authUser == null) { ModelState.AddModelError("Password", Strings.CurrentPasswordIncorrect); @@ -385,8 +381,8 @@ public virtual ActionResult ChangePassword() } [HttpPost] - [ValidateAntiForgeryToken] [Authorize] + [ValidateAntiForgeryToken] public virtual ActionResult ChangePassword(PasswordChangeViewModel model) { if (!ModelState.IsValid) @@ -394,7 +390,7 @@ public virtual ActionResult ChangePassword(PasswordChangeViewModel model) return View(model); } - if (!AuthService.ChangePassword(UserSession.Name, model.OldPassword, model.NewPassword)) + if (!AuthService.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword)) { ModelState.AddModelError( "OldPassword", diff --git a/src/NuGetGallery/ExtensionMethods.cs b/src/NuGetGallery/ExtensionMethods.cs index 7965f02ea4..7d5b7ce726 100644 --- a/src/NuGetGallery/ExtensionMethods.cs +++ b/src/NuGetGallery/ExtensionMethods.cs @@ -310,7 +310,17 @@ public static string ToFriendlyName(this FrameworkName frameworkName) public static string GetClaimOrDefault(this ClaimsPrincipal self, string claimType) { - return self.Claims + return self.Claims.GetClaimOrDefault(claimType); + } + + public static string GetClaimOrDefault(this ClaimsIdentity self, string claimType) + { + return self.Claims.GetClaimOrDefault(claimType); + } + + public static string GetClaimOrDefault(this IEnumerable self, string claimType) + { + return self .Where(c => String.Equals(c.Type, claimType, StringComparison.OrdinalIgnoreCase)) .Select(c => c.Value) .FirstOrDefault(); diff --git a/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs index de6c58d4b4..134f3021ac 100644 --- a/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs +++ b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs @@ -13,6 +13,7 @@ protected override void HandleUnauthorizedRequest(AuthorizationContext filterCon { var owinContext = filterContext.HttpContext.GetOwinContext(); owinContext.Authentication.Challenge(AuthenticationTypes.ApiKey); + owinContext.Response.StatusCode = 401; filterContext.Result = new HttpUnauthorizedResult(); } } diff --git a/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs b/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs index 58c17d408a..b71496af5b 100644 --- a/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs +++ b/src/NuGetGallery/Filters/RequiresAccountConfirmationAttribute.cs @@ -23,16 +23,15 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) throw new ArgumentNullException("filterContext"); } - var controller = ((AppController)filterContext.Controller); - UserSession user = controller.UserSession; - if (!user.IsAuthenticated) + if (!filterContext.HttpContext.Request.IsAuthenticated) { throw new InvalidOperationException("Requires account confirmation attribute is only valid on authenticated actions."); } - - var userService = controller.GetService(); - var currentUser = userService.FindByUsername(user.Name); - if (!currentUser.Confirmed) + + var controller = ((AppController)filterContext.Controller); + var user = controller.GetCurrentUser(); + + if (!user.Confirmed) { controller.TempData["ConfirmationRequiredMessage"] = String.Format( CultureInfo.CurrentCulture, diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index be2a3db560..41a8789683 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -324,7 +324,6 @@ - diff --git a/src/NuGetGallery/Strings.Designer.cs b/src/NuGetGallery/Strings.Designer.cs index 69572d5725..7e42a1e890 100644 --- a/src/NuGetGallery/Strings.Designer.cs +++ b/src/NuGetGallery/Strings.Designer.cs @@ -231,6 +231,15 @@ public static string SuccessfullyUploadedPackage { } } + /// + /// Looks up a localized string similar to User is not authorized. + /// + public static string Unauthorized { + get { + return ResourceManager.GetString("Unauthorized", resourceCulture); + } + } + /// /// Looks up a localized string similar to A package file is required.. /// diff --git a/src/NuGetGallery/Strings.resx b/src/NuGetGallery/Strings.resx index 532d0cfde4..0cb76301fd 100644 --- a/src/NuGetGallery/Strings.resx +++ b/src/NuGetGallery/Strings.resx @@ -192,4 +192,7 @@ An API key must be provided in the 'X-NuGet-ApiKey' header to use this service + + User is not authorized + \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs index aadfc26651..914e4d9508 100644 --- a/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Microsoft.Owin; @@ -9,6 +10,7 @@ using Microsoft.Owin.Security; using Microsoft.Owin.Security.Infrastructure; using Moq; +using NuGetGallery.Framework; using Xunit; using Xunit.Extensions; @@ -16,85 +18,140 @@ namespace NuGetGallery.Authentication { public class ApiKeyAuthenticationHandlerFacts { - public class TheIsPathMatchMethod + public class TheGetChallengeMessageMethod { - [Theory] - [InlineData("")] - [InlineData(null)] - [InlineData("/a")] - [InlineData("/a/b")] - [InlineData("/a/b/c")] - [InlineData("/a/b/c/d/e")] - [InlineData("/z")] - public async Task GivenNoRootPath_AllPathsMatch(string path) + [Fact] + public async Task GivenANon401ResponseInActiveMode_ItReturnsNull() { - var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(); - Assert.True(handler.InvokeIsPathMatch(path)); + // Arrange + var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + AuthenticationMode = AuthenticationMode.Active + }); + handler.MockContext.Setup(c => c.Response.StatusCode).Returns(200); + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Null(message); } - [Theory] - [InlineData("/api")] - [InlineData("/api/v2")] - [InlineData("/api/v2/Packages")] - public async Task GivenARootPath_PathsUnderAndIncludingThatRootMatch(string path) + [Fact] + public async Task GivenA401ResponseInPassiveModeWithoutMatchingAuthenticationType_ItReturnsNull() { + // Arrange var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() { - RootPath = "/api" + AuthenticationMode = AuthenticationMode.Passive, + AuthenticationType = "blarg" }); - Assert.True(handler.InvokeIsPathMatch(path)); + handler.MockContext + .Setup(c => c.Response.StatusCode) + .Returns(401); + handler.MockContext + .Setup(c => c.Authentication.AuthenticationResponseChallenge) + .Returns(new AuthenticationResponseChallenge(new [] { "flarg" }, new AuthenticationProperties())); + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Null(message); } - [Theory] - [InlineData("/api")] - [InlineData("/api/v2")] - [InlineData("/api/v2/Packages")] - public async Task GivenARootPathWithTrailingSlash_PathsUnderAndIncludingThatRootMatch(string path) + [Fact] + public async Task GivenA401ResponseInPassiveModeWithMatchingAuthenticationTypeAndNoHeader_ItReturnsApiKeyRequired() { + // Arrange var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() { - RootPath = "/api/" + AuthenticationMode = AuthenticationMode.Passive, + AuthenticationType = "blarg" }); - Assert.True(handler.InvokeIsPathMatch(path)); + handler.MockContext + .Setup(c => c.Response.StatusCode) + .Returns(401); + handler.MockContext + .Setup(c => c.Authentication.AuthenticationResponseChallenge) + .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Equal(Strings.ApiKeyRequired, message); } - [Theory] - [InlineData("/api")] - [InlineData("/api/v2")] - [InlineData("/api/v2/Packages")] - public async Task GivenARootPathWithTrailingSlashAndTildeSlash_PathsUnderAndIncludingThatRootMatch(string path) + [Fact] + public async Task GivenA401ResponseInPassiveModeWithMatchingAuthenticationTypeAndHeader_ItReturnsApiKeyNotAuthorized() { + // Arrange var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() { - RootPath = "~/api/" + AuthenticationMode = AuthenticationMode.Passive, + AuthenticationType = "blarg" }); - Assert.True(handler.InvokeIsPathMatch(path)); + handler.MockContext + .Setup(c => c.Response.StatusCode) + .Returns(401); + handler.MockContext + .Setup(c => c.Authentication.AuthenticationResponseChallenge) + .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); + handler.MockContext.Object.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Equal(Strings.ApiKeyNotAuthorized, message); } - [Theory] - [InlineData("/api")] - [InlineData("/api/v2")] - [InlineData("/api/v2/Packages")] - public async Task GivenARootPathWithTildeSlash_PathsUnderAndIncludingThatRootMatch(string path) + [Fact] + public async Task GivenA401ResponseInActiveModeAndNoHeader_ItReturnsApiKeyRequired() { + // Arrange var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() { - RootPath = "~/api" + AuthenticationMode = AuthenticationMode.Active, + AuthenticationType = "blarg" }); - Assert.True(handler.InvokeIsPathMatch(path)); + handler.MockContext + .Setup(c => c.Response.StatusCode) + .Returns(401); + handler.MockContext + .Setup(c => c.Authentication.AuthenticationResponseChallenge) + .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Equal(Strings.ApiKeyRequired, message); } - [Theory] - [InlineData("/packages")] - [InlineData("/")] - [InlineData("/flarglebargle")] - public async Task GivenARootPath_PathsNotUnderAndIncludingThatRootMatch(string path) + [Fact] + public async Task GivenA401ResponseInActiveModeAndHeader_ItReturnsApiKeyNotAuthorized() { + // Arrange var handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() { - RootPath = "/api" + AuthenticationMode = AuthenticationMode.Active, + AuthenticationType = "blarg" }); - Assert.False(handler.InvokeIsPathMatch(path)); + handler.MockContext + .Setup(c => c.Response.StatusCode) + .Returns(401); + handler.MockContext + .Setup(c => c.Authentication.AuthenticationResponseChallenge) + .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); + handler.MockContext.Object.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; + + // Act + var message = handler.GetChallengeMessage(); + + // Assert + Assert.Equal(Strings.ApiKeyNotAuthorized, message); } } @@ -156,7 +213,7 @@ public async Task GivenNoUserMatchingApiKey_ItReturnsNull() } [Fact] - public async Task GivenMatchingApiKey_ItReturnsTicketForResolvedUser() + public async Task GivenMatchingApiKey_ItReturnsTicketWithUserNameAndRoles() { // Arrange Guid apiKey = Guid.NewGuid(); @@ -176,8 +233,30 @@ public async Task GivenMatchingApiKey_ItReturnsTicketForResolvedUser() // Assert Assert.NotNull(ticket); - var id = Assert.IsType(ticket.Identity); - Assert.Same(user, id.User); + Assert.Equal("theUser", ticket.Identity.GetClaimOrDefault(ClaimTypes.Name)); + } + + [Fact] + public async Task GivenMatchingApiKey_ItSetsUserInOwinEnvironment() + { + // Arrange + Guid apiKey = Guid.NewGuid(); + var user = new User() { Username = "theUser", EmailAddress = "confirmed@example.com" }; + TestableApiKeyAuthenticationHandler handler = await TestableApiKeyAuthenticationHandler.CreateAsync(new ApiKeyAuthenticationOptions() + { + RootPath = "/api" + }); + handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); + handler.MockContext.Object.Request.Headers.Set( + Constants.ApiKeyHeaderName, + apiKey.ToString().ToLowerInvariant()); + handler.MockAuth.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); + + // Act + await handler.InvokeAuthenticateCoreAsync(); + + // Assert + Assert.Same(user, handler.MockContext.Object.Environment[Constants.CurrentUserOwinEnvironmentKey]); } } @@ -206,12 +285,7 @@ public static async Task CreateAsync(ApiKey var handler = new TestableApiKeyAuthenticationHandler(); - var ctxt = (handler.MockContext = new Mock()).Object; - - handler.MockContext.Setup(c => c.Request).Returns(new Mock().Object); - handler.MockContext.Setup(c => c.Response).Returns(new Mock().Object); - handler.MockContext.Setup(c => c.Request.PathBase).Returns("/testroot"); - handler.MockContext.Setup(c => c.Request.Headers).Returns(new HeaderDictionary(new Dictionary())); + var ctxt = (handler.MockContext = Fakes.CreateOwinContext()).Object; // Grr, have to make an internal call to initialize... await (Task)(typeof(AuthenticationHandler) @@ -228,11 +302,6 @@ public Task InvokeAuthenticateCoreAsync() { return AuthenticateCoreAsync(); } - - public bool InvokeIsPathMatch(string path) - { - return IsPathMatch(path); - } } } } diff --git a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs index c7f2017ec2..8b9d2fcc49 100644 --- a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs @@ -9,6 +9,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Owin; using Moq; using Newtonsoft.Json.Linq; using NuGet; @@ -30,12 +31,13 @@ class TestableApiController : ApiController public Mock MockContentService { get; private set; } public Mock MockStatisticsService { get; private set; } public Mock MockIndexingService { get; private set; } - public Mock MockAuthService { get; set; } + public Mock MockOwinContext { get; set; } private INupkg PackageFromInputStream { get; set; } public TestableApiController(MockBehavior behavior = MockBehavior.Default) { + OwinContext = (MockOwinContext = Fakes.CreateOwinContext()).Object; EntitiesContext = (MockEntitiesContext = new Mock()).Object; PackageService = (MockPackageService = new Mock(behavior)).Object; UserService = (MockUserService = new Mock(behavior)).Object; @@ -43,8 +45,7 @@ public TestableApiController(MockBehavior behavior = MockBehavior.Default) ContentService = (MockContentService = new Mock()).Object; StatisticsService = (MockStatisticsService = new Mock()).Object; IndexingService = (MockIndexingService = new Mock()).Object; - AuthService = (MockAuthService = new Mock()).Object; - + MockPackageFileService = new Mock(MockBehavior.Strict); MockPackageFileService.Setup(p => p.SavePackageFileAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); PackageFileService = MockPackageFileService.Object; @@ -72,15 +73,14 @@ public class TheCreatePackageAction public async Task CreatePackageWillSavePackageFileToFileStorage() { // Arrange - var guid = Guid.NewGuid().ToString(); var user = new User() { EmailAddress = "confirmed@email.com" }; var userService = new Mock(); var packageRegistration = new PackageRegistration(); packageRegistration.Owners.Add(user); var controller = new TestableApiController(); + controller.SetCurrentUser(user); controller.MockPackageFileService.Setup(p => p.SavePackageFileAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)).Verifiable(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(guid), user); controller.MockPackageService.Setup(p => p.FindPackageRegistrationById(It.IsAny())).Returns(packageRegistration); var nuGetPackage = new Mock(); @@ -89,31 +89,12 @@ public async Task CreatePackageWillSavePackageFileToFileStorage() controller.SetupPackageFromInputStream(nuGetPackage); // Act - await controller.CreatePackagePut(guid); + await controller.CreatePackagePut(); // Assert controller.MockPackageFileService.Verify(); } - [Fact] - public void WillFindTheUserThatMatchesTheApiKey() - { - var nuGetPackage = new Mock(); - nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); - nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - - var apiKey = Guid.NewGuid(); - var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth( - CredentialBuilder.CreateV1ApiKey(apiKey), new User()).Verifiable(); - controller.SetupPackageFromInputStream(nuGetPackage); - - - controller.CreatePackagePut(apiKey.ToString()); - - controller.MockAuthService.VerifyAll(); - } - [Fact] public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionAlreadyExists() { @@ -130,14 +111,13 @@ public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionA Owners = new List { user } }; - var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); + controller.SetCurrentUser(new User()); controller.MockPackageService.Setup(x => x.FindPackageRegistrationById("theId")).Returns(packageRegistration); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); controller.SetupPackageFromInputStream(nuGetPackage); // Act - var result = await controller.CreatePackagePut(apiKey.ToString()); + var result = await controller.CreatePackagePut(); // Assert ResultAssert.IsStatusCode( @@ -146,41 +126,19 @@ public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionA String.Format(Strings.PackageExistsAndCannotBeModified, "theId", "1.0.42")); } - [Fact] - public async Task WillFindUserUsingAuthenticate() - { - var nuGetPackage = new Mock(); - nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); - nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - - var user = new User(); - var apiKey = Guid.NewGuid(); - - var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); - controller.SetupPackageFromInputStream(nuGetPackage); - - ResultAssert.IsStatusCode( - await controller.CreatePackagePut(apiKey.ToString()), - HttpStatusCode.Created); - - controller.MockPackageService.Verify(p => - p.CreatePackage(nuGetPackage.Object, user, true)); - } - [Fact] public void WillCreateAPackageFromTheNuGetPackage() { var nuGetPackage = new Mock(); nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; + var user = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); + controller.SetCurrentUser(user); controller.SetupPackageFromInputStream(nuGetPackage); - controller.CreatePackagePut(apiKey.ToString()); + controller.CreatePackagePut(); controller.MockPackageService.Verify(x => x.CreatePackage(nuGetPackage.Object, It.IsAny(), true)); } @@ -191,15 +149,15 @@ public void WillCreateAPackageWithTheUserMatchingTheApiKey() var nuGetPackage = new Mock(); nuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; + var user = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); + controller.SetCurrentUser(user); controller.SetupPackageFromInputStream(nuGetPackage); - controller.CreatePackagePut(apiKey.ToString()); + controller.CreatePackagePut(); - controller.MockPackageService.Verify(x => x.CreatePackage(It.IsAny(), matchingUser, true)); + controller.MockPackageService.Verify(x => x.CreatePackage(It.IsAny(), user, true)); } [Fact] @@ -209,17 +167,17 @@ public void CreatePackageRefreshesNuGetExeIfCommandLinePackageIsUploaded() var nuGetPackage = new Mock(); nuGetPackage.Setup(x => x.Metadata.Id).Returns("NuGet.CommandLine"); nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); - var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; + var user = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny(), true)) .Returns(new Package { IsLatestStable = true }); controller.MockNuGetExeDownloaderService.Setup(s => s.UpdateExecutableAsync(nuGetPackage.Object)).Verifiable(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); + controller.SetCurrentUser(user); controller.SetupPackageFromInputStream(nuGetPackage); // Act - controller.CreatePackagePut(apiKey.ToString()); + controller.CreatePackagePut(); // Assert controller.MockNuGetExeDownloaderService.Verify(); @@ -232,17 +190,17 @@ public void CreatePackageDoesNotRefreshNuGetExeIfItIsNotLatestStable() var nuGetPackage = new Mock(); nuGetPackage.Setup(x => x.Metadata.Id).Returns("NuGet.CommandLine"); nuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("2.0.0-alpha")); - var matchingUser = new User() { EmailAddress = "confirmed@email.com" }; + var user = new User() { EmailAddress = "confirmed@email.com" }; var controller = new TestableApiController(); var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(p => p.CreatePackage(nuGetPackage.Object, It.IsAny(), true)) .Returns(new Package { IsLatest = true, IsLatestStable = false }); controller.MockNuGetExeDownloaderService.Setup(s => s.UpdateExecutableAsync(nuGetPackage.Object)).Verifiable(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), matchingUser); + controller.SetCurrentUser(user); controller.SetupPackageFromInputStream(nuGetPackage); // Act - controller.CreatePackagePut(apiKey.ToString()); + controller.CreatePackagePut(); // Assert controller.MockNuGetExeDownloaderService.Verify(s => s.UpdateExecutableAsync(It.IsAny()), Times.Never()); @@ -251,32 +209,14 @@ public void CreatePackageDoesNotRefreshNuGetExeIfItIsNotLatestStable() public class TheDeletePackageAction { - [Fact] - public void WillThrowIfTheApiKeyDoesNotExist() - { - var controller = new TestableApiController(); - controller.MockPackageService - .Setup(p => p.FindPackageByIdAndVersion("theId", "1.0.42", true)) - .Returns(new Package()); - - var result = controller.DeletePackage(Guid.NewGuid().ToString(), "theId", "1.0.42"); - - Assert.IsType(result); - var statusCodeResult = (HttpStatusCodeWithBodyResult)result; - Assert.Equal(403, statusCodeResult.StatusCode); - Assert.Equal(String.Format(Strings.ApiKeyNotAuthorized, "delete"), statusCodeResult.StatusDescription); - controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(It.IsAny(), true), Times.Never()); - } - [Fact] public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() { var controller = new TestableApiController(); - var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns((Package)null); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); + controller.SetCurrentUser(new User()); - var result = controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42"); + var result = controller.DeletePackage("theId", "1.0.42"); Assert.IsType(result); var statusCodeResult = (HttpStatusCodeWithBodyResult)result; @@ -295,10 +235,10 @@ public void WillNotDeleteThePackageIfApiKeyDoesNotBelongToAnOwner() }; var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), notOwner); + controller.SetCurrentUser(notOwner); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns(package); - var result = controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42"); + var result = controller.DeletePackage("theId", "1.0.42"); Assert.IsType(result); var statusCodeResult = (HttpStatusCodeWithBodyResult)result; @@ -318,9 +258,9 @@ public void WillUnlistThePackageIfApiKeyBelongsToAnOwner() }; var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); + controller.SetCurrentUser(owner); - ResultAssert.IsEmpty(controller.DeletePackage(apiKey.ToString(), "theId", "1.0.42")); + ResultAssert.IsEmpty(controller.DeletePackage("theId", "1.0.42")); controller.MockPackageService.Verify(x => x.MarkPackageUnlisted(package, true)); controller.MockIndexingService.Verify(i => i.UpdatePackage(package)); @@ -351,10 +291,8 @@ public async Task GetPackageReturns400ForEvilPackageVersion() public async Task GetPackageReturns404IfPackageIsNotFound() { // Arrange - var apiKey = Guid.NewGuid(); var controller = new TestableApiController(MockBehavior.Strict); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("Baz", "1.0.1", false)).Returns((Package)null).Verifiable(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); // Act var result = await controller.GetPackage("Baz", "1.0.1"); @@ -371,7 +309,6 @@ public async Task GetPackageReturnsPackageIfItExists() { // Arrange const string PackageId = "Baz"; - var apiKey = Guid.NewGuid(); var package = new Package() { Version = "1.0.01", NormalizedVersion = "1.0.1" }; var actionResult = new EmptyResult(); var controller = new TestableApiController(MockBehavior.Strict); @@ -380,8 +317,7 @@ public async Task GetPackageReturnsPackageIfItExists() controller.MockPackageFileService.Setup(s => s.CreateDownloadPackageActionResultAsync(HttpRequestUrl, PackageId, package.NormalizedVersion)) .Returns(Task.FromResult(actionResult)) .Verifiable(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); - + NameValueCollection headers = new NameValueCollection(); headers.Add("NuGet-Operation", "Install"); @@ -410,7 +346,6 @@ public async Task GetPackageReturnsPackageIfItExists() public async Task GetPackageReturnsSpecificPackageEvenIfDatabaseIsOffline() { // Arrange - var apiKey = Guid.NewGuid(); var package = new Package(); var actionResult = new EmptyResult(); @@ -523,12 +458,11 @@ public void WillThrowIfAPackageWithTheIdAndSemanticVersionDoesNotExist() { // Arrange var controller = new TestableApiController(); - var apiKey = Guid.NewGuid(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns((Package)null); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); + controller.SetCurrentUser(new User()); // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); + var result = controller.PublishPackage("theId", "1.0.42"); // Assert ResultAssert.IsStatusCode( @@ -547,14 +481,13 @@ public void WillNotListThePackageIfApiKeyDoesNotBelongToAnOwner() { PackageRegistration = new PackageRegistration { Owners = new[] { new User() } } }; - var apiKey = Guid.NewGuid(); - + var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion("theId", "1.0.42", true)).Returns(package); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); - + controller.SetCurrentUser(owner); + // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); + var result = controller.PublishPackage("theId", "1.0.42"); // Assert ResultAssert.IsStatusCode( @@ -566,11 +499,10 @@ public void WillNotListThePackageIfApiKeyDoesNotBelongToAnOwner() } [Fact] - public void WillListThePackageIfApiKeyBelongsToAnOwner() + public void WillListThePackageIfUserIsAnOwner() { // Arrange - var apiKey = Guid.NewGuid(); - var owner = new User { Key = 1, ApiKey = apiKey }; + var owner = new User { Key = 1 }; var package = new Package { PackageRegistration = new PackageRegistration { Owners = new[] { new User(), owner } } @@ -578,10 +510,10 @@ public void WillListThePackageIfApiKeyBelongsToAnOwner() var controller = new TestableApiController(); controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), true)).Returns(package); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), owner); - + controller.SetCurrentUser(owner); + // Act - var result = controller.PublishPackage(apiKey.ToString(), "theId", "1.0.42"); + var result = controller.PublishPackage("theId", "1.0.42"); // Assert ResultAssert.IsEmpty(result); @@ -592,37 +524,15 @@ public void WillListThePackageIfApiKeyBelongsToAnOwner() public class TheVerifyPackageKeyAction : TestContainer { - [Fact] - public void VerifyPackageKeyReturns403IfUserDoesNotExist() - { - // Arrange - var apiKey = Guid.NewGuid(); - var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), null); - controller.MockPackageService - .Setup(p => p.FindPackageByIdAndVersion("foo", "1.0.0", true)) - .Returns(new Package()); - - // Act - var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); - - // Assert - ResultAssert.IsStatusCode( - result, - HttpStatusCode.Forbidden, - Strings.ApiKeyNotAuthorized); - } - [Fact] public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsButIdAndVersionAreEmpty() { // Arrange - var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), new User()); - + controller.SetCurrentUser(new User()); + // Act - var result = controller.VerifyPackageKey(apiKey.ToString(), null, null); + var result = controller.VerifyPackageKey(null, null); // Assert ResultAssert.IsEmpty(result); @@ -632,17 +542,15 @@ public void VerifyPackageKeyReturnsEmptyResultIfApiKeyExistsButIdAndVersionAreEm public void VerifyPackageKeyReturns404IfPackageDoesNotExist() { // Arrange - var apiKey = Guid.NewGuid(); var user = new User { EmailAddress = "confirmed@email.com" }; - GetMock() - .SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); GetMock() .Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)) .ReturnsNull(); var controller = GetController(); - + controller.SetCurrentUser(user); + // Act - var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey("foo", "1.0.0"); // Assert ResultAssert.IsStatusCode( @@ -655,16 +563,15 @@ public void VerifyPackageKeyReturns404IfPackageDoesNotExist() public void VerifyPackageKeyReturns403IfUserIsNotAnOwner() { // Arrange - var apiKey = Guid.NewGuid(); var controller = new TestableApiController(); var owner = new User(); var nonOwner = new User(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), nonOwner); + controller.SetCurrentUser(nonOwner); controller.MockPackageService.Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)).Returns( new Package { PackageRegistration = new PackageRegistration() }); // Act - var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey("foo", "1.0.0"); // Assert ResultAssert.IsStatusCode( @@ -682,11 +589,11 @@ public void VerifyPackageKeyReturns200IfUserIsAnOwner() var package = new Package { PackageRegistration = new PackageRegistration() }; package.PackageRegistration.Owners.Add(user); var controller = new TestableApiController(); - controller.MockAuthService.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); + controller.SetCurrentUser(user); controller.MockPackageService.Setup(s => s.FindPackageByIdAndVersion("foo", "1.0.0", true)).Returns(package); // Act - var result = controller.VerifyPackageKey(apiKey.ToString(), "foo", "1.0.0"); + var result = controller.VerifyPackageKey("foo", "1.0.0"); // Assert ResultAssert.IsEmpty(result); diff --git a/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs new file mode 100644 index 0000000000..b447eef1bb --- /dev/null +++ b/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin; +using Moq; +using Xunit; + +namespace NuGetGallery.Controllers +{ + public class AppControllerFacts + { + public class TheGetCurrentUserMethod + { + [Fact] + public void GivenNoActiveUserPrincipal_ItReturnsNull() + { + // Arrange + var context = new Mock(); + var ctrl = new TestableAppController(); + ctrl.OwinContext = context.Object; + + // Act + var user = ctrl.InvokeGetCurrentUser(); + + // Assert + Assert.Null(user); + } + } + + public class TestableAppController : AppController + { + // Nothing but a concrete class to test an abstract class :) + + public User InvokeGetCurrentUser() + { + return GetCurrentUser(); + } + } + } +} diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs index 7f278af2a5..12869c457d 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs @@ -7,6 +7,7 @@ using System.Web.Mvc; using Moq; using NuGetGallery.Authentication; +using NuGetGallery.Framework; using Xunit; namespace NuGetGallery @@ -18,7 +19,7 @@ public class TestableCuratedFeedsController : CuratedFeedsController public TestableCuratedFeedsController() { StubCuratedFeed = new CuratedFeed - { Key = 0, Name = "aName", Managers = new HashSet(new[] { new User { Username = "aUsername" } }) }; + { Key = 0, Name = "aName", Managers = new HashSet(new[] { Fakes.User }) }; StubCuratedFeedService = new Mock(); StubCuratedFeedService @@ -32,7 +33,9 @@ public TestableCuratedFeedsController() var httpContext = new Mock(); TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, this); - this.SetUser("aUsername"); + + this.SetCurrentUser(Fakes.User); + } public CuratedFeed StubCuratedFeed { get; set; } @@ -67,7 +70,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedFeedsController(); - controller.SetUser("notAManager"); + controller.SetCurrentUser(Fakes.Owner); var result = controller.CuratedFeed("aFeedName") as HttpStatusCodeResult; @@ -90,10 +93,11 @@ public void WillPassTheCuratedFeedNameToTheView() [Fact] public void WillPassTheCuratedFeedManagersToTheView() { + var user = new User { Username = "theManager" }; var controller = new TestableCuratedFeedsController(); - controller.SetUser("theManager"); + controller.SetCurrentUser(user); controller.StubCuratedFeed.Name = "aFeedName"; - controller.StubCuratedFeed.Managers = new HashSet(new[] { new User { Username = "theManager" } }); + controller.StubCuratedFeed.Managers = new HashSet(new[] { user }); var viewModel = (controller.CuratedFeed("aFeedName") as ViewResult).Model as CuratedFeedViewModel; diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs index cd77042877..b490503007 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs @@ -6,6 +6,7 @@ using System.Web; using System.Web.Mvc; using Moq; +using NuGetGallery.Framework; using Xunit; namespace NuGetGallery @@ -17,7 +18,7 @@ public class TestableCuratedPackagesController : CuratedPackagesController public TestableCuratedPackagesController() { StubCuratedFeed = new CuratedFeed - { Key = 0, Name = "aFeedName", Managers = new HashSet(new[] { new User { Username = "aUsername" } }) }; + { Key = 0, Name = "aFeedName", Managers = new HashSet(new[] { Fakes.User }) }; StubPackageRegistration = new PackageRegistration { Key = 0, Id = "anId" }; EntitiesContext = new FakeEntitiesContext(); @@ -36,8 +37,6 @@ public TestableCuratedPackagesController() var httpContext = new Mock(); TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, this); - - this.SetUser("aUsername"); } public CuratedFeed StubCuratedFeed { get; set; } @@ -50,6 +49,7 @@ public class TheDeleteCuratedPackageAction public void WillReturn404IfTheCuratedFeedDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.DeleteCuratedPackage("aStrangeCuratedFeedName", "anId"); @@ -60,6 +60,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn404IfTheCuratedPackageDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages = new[] { new CuratedPackage { PackageRegistration = new PackageRegistration() } }; var result = controller.DeleteCuratedPackage("aFeedName", "aStrangeCuratedPackageId"); @@ -71,7 +72,7 @@ public void WillReturn404IfTheCuratedPackageDoesNotExist() public void WillReturn403IfTheUserNotAManager() { var controller = new TestableCuratedPackagesController(); - controller.SetUser("notAManager"); + controller.SetCurrentUser(Fakes.Owner); controller.StubCuratedFeed.Packages.Add( new CuratedPackage @@ -92,6 +93,7 @@ public void WillReturn403IfTheUserNotAManager() public void WillDeleteTheCuratedPackageWhenRequestIsValid() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage @@ -115,6 +117,7 @@ public void WillDeleteTheCuratedPackageWhenRequestIsValid() public void WillReturn204AfterDeletingTheCuratedPackage() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage @@ -138,6 +141,7 @@ public class TheGetCreateCuratedPackageFormAction public void WillReturn404IfTheCuratedFeedDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.GetCreateCuratedPackageForm("aWrongFeedName"); @@ -148,8 +152,8 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedPackagesController(); - controller.SetUser("notAManager"); - + controller.SetCurrentUser(Fakes.Owner); + var result = controller.GetCreateCuratedPackageForm("aFeedName") as HttpStatusCodeResult; Assert.NotNull(result); @@ -160,6 +164,7 @@ public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() public void WillPushTheCuratedFeedNameIntoTheViewBag() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Name = "theCuratedFeedName"; var result = controller.GetCreateCuratedPackageForm("theCuratedFeedName") as ViewResult; @@ -175,7 +180,8 @@ public class ThePatchCuratedPackageAction public void WillReturn404IfTheCuratedFeedDoesNotExist() { var controller = new TestableCuratedPackagesController(); - + controller.SetCurrentUser(Fakes.User); + var result = controller.PatchCuratedPackage("aWrongFeedName", "anId", new ModifyCuratedPackageRequest()); @@ -186,6 +192,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn404IfTheCuratedPackageDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.PatchCuratedPackage("aFeedName", "aWrongId", new ModifyCuratedPackageRequest()); @@ -196,7 +203,7 @@ public void WillReturn404IfTheCuratedPackageDoesNotExist() public void WillReturn403IfNotAFeedManager() { var controller = new TestableCuratedPackagesController(); - controller.SetUser("notAManager"); + controller.SetCurrentUser(Fakes.Owner); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { @@ -217,6 +224,7 @@ public void WillReturn403IfNotAFeedManager() public void WillReturn400IfTheModelStateIsInvalid() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { @@ -240,6 +248,7 @@ public void WillReturn400IfTheModelStateIsInvalid() public void WillModifyTheCuratedPackageWhenRequestIsValid() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { @@ -266,6 +275,7 @@ public void WillModifyTheCuratedPackageWhenRequestIsValid() public void WillReturn204AfterModifyingTheCuratedPackage() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { @@ -290,6 +300,7 @@ public class ThePostCuratedPackagesAction public void WillReturn404IfTheCuratedFeedDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.PostCuratedPackages( "aWrongFeedName", @@ -302,7 +313,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedPackagesController(); - controller.SetUser("notAManager"); + controller.SetCurrentUser(Fakes.Owner); var result = controller.PostCuratedPackages( "aFeedName", @@ -317,6 +328,7 @@ public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() public void WillPushTheCuratedFeedNameIntoTheViewBagAndShowTheCreateCuratedPackageFormWithErrorsWhenModelStateIsInvalid() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Name = "theCuratedFeedName"; controller.ModelState.AddModelError("", "anError"); @@ -332,6 +344,7 @@ public void WillPushTheCuratedFeedNameIntoTheViewBagAndShowTheCreateCuratedPacka public void WillPushTheCuratedFeedNameIntoTheViewBagAndShowTheCreateCuratedPackageFormWithErrorsWhenThePackageIdDoesNotExist() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.PostCuratedPackages("aFeedName", new CreateCuratedPackageRequest { PackageId = "aWrongId" }) as ViewResult; @@ -346,6 +359,7 @@ public void WillPushTheCuratedFeedNameIntoTheViewBagAndShowTheCreateCuratedPacka public void WillCreateTheCuratedPackage() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.PostCuratedPackages( "aFeedName", @@ -366,6 +380,7 @@ public void WillCreateTheCuratedPackage() public void WillRedirectToTheCuratedFeedRouteAfterCreatingTheCuratedPackage() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); var result = controller.PostCuratedPackages( "aFeedName", new CreateCuratedPackageRequest { PackageId = "anId" }) @@ -379,6 +394,7 @@ public void WillRedirectToTheCuratedFeedRouteAfterCreatingTheCuratedPackage() public void WillShowAnErrorWhenThePackageHasAlreadyBeenCurated() { var controller = new TestableCuratedPackagesController(); + controller.SetCurrentUser(Fakes.User); controller.StubCuratedFeed.Packages.Add( new CuratedPackage { CuratedFeed = controller.StubCuratedFeed, diff --git a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs index ec84eb75bf..aea3bfe210 100644 --- a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs @@ -128,7 +128,7 @@ public async Task DeletesTheInProgressPackageUpload() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.CancelUpload(); @@ -145,7 +145,7 @@ public async Task RedirectsToUploadPageAfterDelete() var controller = CreateController( uploadFileService: fakeUploadFileService, userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.CancelUpload() as RedirectToRouteResult; @@ -200,7 +200,7 @@ public void GivenAValidPackageThatTheCurrentUserDoesNotOwnItDisplaysCurrentMetad var packageService = new Mock(); var controller = CreateController( packageService: packageService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); packageService.Setup(p => p.FindPackageByIdAndVersion("Foo", "1.1.1", true)) .Returns(new Package() @@ -237,7 +237,7 @@ public void GivenAValidPackageThatTheCurrentUserOwnsItDisablesResponseCaching() packageService: packageService, editPackageService: editPackageService, httpContext: httpContext); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); httpCachePolicy.Setup(c => c.SetCacheability(HttpCacheability.NoCache)).Verifiable(); @@ -280,7 +280,7 @@ public void GivenAValidPackageThatTheCurrentUserOwnsWithNoEditsItDisplaysCurrent packageService: packageService, editPackageService: editPackageService, httpContext: httpContext); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); var package = new Package() @@ -324,7 +324,7 @@ public void GivenAValidPackageThatTheCurrentUserOwnsWithEditsItDisplaysEditedMet packageService: packageService, editPackageService: editPackageService, httpContext: httpContext); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); httpContext.Setup(c => c.Response.Cache).Returns(httpCachePolicy.Object); var package = new Package() { @@ -366,9 +366,9 @@ public void WithEmptyTokenReturnsHttpNotFound() { var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(new PackageRegistration()); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("username")).Returns(new User { Username = "username" }); - var controller = CreateController(packageService: packageService, userService: userService); + var controller = CreateController(packageService: packageService); + controller.SetCurrentUser(new User { Username = "username" }); + controller.SetPrincipal("username"); var result = controller.ConfirmOwner("foo", "username", ""); @@ -378,25 +378,11 @@ public void WithEmptyTokenReturnsHttpNotFound() [Fact] public void WithNonExistentPackageIdReturnsHttpNotFound() { - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("username")).Returns(new User { Username = "username" }); - var controller = CreateController(userService: userService); - controller.SetUser("username"); + var controller = CreateController(); var result = controller.ConfirmOwner("foo", "username", "token"); - - Assert.IsType(result); - } - - [Fact] - public void WithNonExistentUserReturnsHttpNotFound() - { - var packageService = new Mock(); - packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(new PackageRegistration()); - var controller = CreateController(packageService: packageService); - controller.SetUser("username"); + controller.SetCurrentUser(new User { Username = "username" }); + controller.SetPrincipal("username"); - var result = controller.ConfirmOwner("foo", "username", "token"); - Assert.IsType(result); } @@ -404,34 +390,14 @@ public void WithNonExistentUserReturnsHttpNotFound() public void WithIdentityNotMatchingUserInRequestReturnsViewWithMessage() { var controller = CreateController(); - controller.SetUser("userA"); var result = controller.ConfirmOwner("foo", "userB", "token"); - + controller.SetPrincipal("userA"); + var model = ResultAssert.IsView(result); Assert.Equal(ConfirmOwnershipResult.NotYourRequest, model.Result); Assert.Equal("userB", model.Username); } - [Fact] - public void RequiresUserBeLoggedInToConfirm() - { - var package = new PackageRegistration { Id = "foo" }; - var user = new User { Username = "username" }; - var packageService = new Mock(); - packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(package); - packageService.Setup(p => p.ConfirmPackageOwner(package, user, "token")).Returns(ConfirmOwnershipResult.Success); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("username")).Returns(user); - var controller = CreateController(packageService: packageService, userService: userService); - controller.SetUser("not-username"); - - var result = controller.ConfirmOwner("foo", "username", "token"); - - var viewModel = ResultAssert.IsView(result); - Assert.Equal("username", viewModel.Username); - Assert.Equal(ConfirmOwnershipResult.NotYourRequest, viewModel.Result); - } - [Theory] [InlineData(ConfirmOwnershipResult.Success)] [InlineData(ConfirmOwnershipResult.AlreadyOwner)] @@ -443,10 +409,9 @@ public void AcceptsResultOfPackageServiceIfOtherwiseValid(ConfirmOwnershipResult var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(package); packageService.Setup(p => p.ConfirmPackageOwner(package, user, "token")).Returns(confirmationResult); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("username")).Returns(user); - var controller = CreateController(packageService: packageService, userService: userService); - controller.SetUser("username"); + var controller = CreateController(packageService: packageService); + controller.SetCurrentUser(user); + controller.SetPrincipal(user.Username); var result = controller.ConfirmOwner("foo", "username", "token"); @@ -498,13 +463,10 @@ public void HtmlEncodesMessageContent() var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("factory")).Returns(package); var userService = new Mock(); - userService.Setup(u => u.FindByUsername("Montgomery")).Returns( - new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var controller = CreateController( packageService: packageService, - messageService: messageService, - userService: userService); - controller.SetUser("Montgomery"); + messageService: messageService); + controller.SetCurrentUser(new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var model = new ContactOwnersViewModel { Message = "I like the cut of your jib. It's bold.", @@ -530,13 +492,10 @@ public void CallsSendContactOwnersMessageWithUserInfo() var packageService = new Mock(); packageService.Setup(p => p.FindPackageRegistrationById("factory")).Returns(package); var userService = new Mock(); - userService.Setup(u => u.FindByUsername("Montgomery")).Returns( - new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var controller = CreateController( packageService: packageService, - messageService: messageService, - userService: userService); - controller.SetUser("Montgomery"); + messageService: messageService); + controller.SetCurrentUser(new User { EmailAddress = "montgomery@burns.example.com", Username = "Montgomery" }); var model = new ContactOwnersViewModel { Message = "I like the cut of your jib", @@ -635,7 +594,7 @@ public void TrimsSearchTerm() var controller = CreateController( httpContext: httpContext, searchService: searchService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var result = controller.ListPackages(" test ") as ViewResult; @@ -699,15 +658,12 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("mordor", It.IsAny(), true)).Returns(package); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("Frodo")).Returns(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 1 }); var httpContext = new Mock(); var controller = CreateController( packageService: packageService, messageService: messageService, - userService: userService, httpContext: httpContext); - controller.SetUser("Frodo"); + controller.SetCurrentUser(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 1 }); var model = new ReportAbuseViewModel { Message = "Mordor took my finger", @@ -718,7 +674,6 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() ActionResult result = controller.ReportAbuse("mordor", "2.0.1", model) as RedirectResult; Assert.NotNull(result); - userService.VerifyAll(); messageService.Verify( s => s.ReportAbuse( It.Is( @@ -738,14 +693,11 @@ public void FormRedirectsPackageOwnerToReportMyPackage() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("Mordor", It.IsAny(), true)).Returns(package); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("Sauron")).Returns(new User { EmailAddress = "darklord@mordor.com", Username = "Sauron" }); var httpContext = new Mock(); var controller = CreateController( packageService: packageService, - userService: userService, httpContext: httpContext); - controller.SetUser("Sauron"); + controller.SetCurrentUser(new User { EmailAddress = "darklord@mordor.com", Username = "Sauron" }); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportAbuse("Mordor", "2.0.1"); @@ -806,14 +758,11 @@ public void FormRedirectsNonOwnersToReportAbuse() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("Mordor", It.IsAny(), true)).Returns(package); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername("Frodo")).Returns(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 2 }); var httpContext = new Mock(); var controller = CreateController( packageService: packageService, - userService: userService, httpContext: httpContext); - controller.SetUser("Frodo"); + controller.SetCurrentUser(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 2 }); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportMyPackage("Mordor", "2.0.1"); @@ -832,9 +781,7 @@ public void HtmlEncodesMessageContent() }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("mordor", "2.0.1", true)).Returns(package); - var userService = new Mock(); - userService.Setup(u => u.FindByUsername(user.Username)).Returns(user); - + ReportPackageRequest reportRequest = null; var messageService = new Mock(); messageService @@ -844,9 +791,8 @@ public void HtmlEncodesMessageContent() var controller = CreateController( packageService: packageService, messageService: messageService, - userService: userService, httpContext: httpContext); - controller.SetUser("Sauron"); + controller.SetCurrentUser(user); var model = new ReportAbuseViewModel { Email = "frodo@hobbiton.example.com", @@ -871,15 +817,12 @@ public class TheUploadFileActionForGetRequests [Fact] public async Task WillRedirectToVerifyPackageActionWhenThereIsAlreadyAnUploadInProgress() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeFileStream = new MemoryStream(); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + uploadFileService: fakeUploadFileService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage() as RedirectToRouteResult; @@ -891,14 +834,11 @@ public async Task WillRedirectToVerifyPackageActionWhenThereIsAlreadyAnUploadInP [Fact] public async Task WillShowTheViewWhenThereIsNoUploadInProgress() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(null)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + uploadFileService: fakeUploadFileService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage() as ViewResult; @@ -911,15 +851,12 @@ public class TheUploadFileActionForPostRequests [Fact] public async Task WillReturn409WhenThereIsAlreadyAnUploadInProgress() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeFileStream = new MemoryStream(); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + uploadFileService: fakeUploadFileService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(null) as HttpStatusCodeResult; @@ -931,11 +868,8 @@ public async Task WillReturn409WhenThereIsAlreadyAnUploadInProgress() [Fact] public async Task WillShowViewWithErrorsIfPackageFileIsNull() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); - var controller = CreateController( - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + var controller = CreateController(); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(null) as ViewResult; @@ -949,11 +883,8 @@ public async Task WillShowViewWithErrorsIfFileIsNotANuGetPackage() { var fakeUploadedFile = new Mock(); fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.notNuPkg"); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); - var controller = CreateController( - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + var controller = CreateController(); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -967,14 +898,11 @@ public async Task WillShowViewWithErrorsIfNuGetPackageIsInvalid() { var fakeUploadedFile = new Mock(); fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.nupkg"); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var readPackageException = new Exception(); var controller = CreateController( - userService: fakeUserService, readPackageException: readPackageException); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -988,15 +916,12 @@ public async Task WillShowTheViewWithErrorsWhenThePackageIdIsAlreadyBeingUsed() { var fakeUploadedFile = new Mock(); fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.nupkg"); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakePackageRegistration = new PackageRegistration { Id = "theId", Owners = new[] { new User { Key = 1 /* not the current user */ } } }; var fakePackageService = new Mock(); fakePackageService.Setup(x => x.FindPackageRegistrationById(It.IsAny())).Returns(fakePackageRegistration); var controller = CreateController( - packageService: fakePackageService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + packageService: fakePackageService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -1010,15 +935,12 @@ public async Task WillShowTheViewWithErrorsWhenThePackageAlreadyExists() { var fakeUploadedFile = new Mock(); fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.nupkg"); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.FindPackageByIdAndVersion(It.IsAny(), It.IsAny(), It.IsAny())).Returns( new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); var controller = CreateController( - packageService: fakePackageService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + packageService: fakePackageService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as ViewResult; @@ -1036,24 +958,21 @@ public async Task WillSaveTheUploadFile() fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.nupkg"); var fakeFileStream = new MemoryStream(); fakeUploadedFile.Setup(x => x.InputStream).Returns(fakeFileStream); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(p => p.Metadata.Id).Returns("thePackageId"); fakeNuGetPackage.Setup(x => x.GetStream()).Returns(fakeFileStream); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); - fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(42, It.IsAny())).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(null)); + fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(TestUtility.FakeUser.Key, It.IsAny())).Returns(Task.FromResult(0)); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.UploadPackage(fakeUploadedFile.Object); - fakeUploadFileService.Verify(x => x.SaveUploadFileAsync(42, fakeFileStream)); + fakeUploadFileService.Verify(x => x.SaveUploadFileAsync(TestUtility.FakeUser.Key, fakeFileStream)); fakeFileStream.Dispose(); } @@ -1064,16 +983,13 @@ public async Task WillRedirectToVerifyPackageActionAfterSaving() fakeUploadedFile.Setup(x => x.FileName).Returns("theFile.nupkg"); var fakeFileStream = new MemoryStream(); fakeUploadedFile.Setup(x => x.InputStream).Returns(fakeFileStream); - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); - fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(42, It.IsAny())).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(null)); + fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(TestUtility.FakeUser.Key, It.IsAny())).Returns(Task.FromResult(0)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + uploadFileService: fakeUploadFileService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.UploadPackage(fakeUploadedFile.Object) as RedirectToRouteResult; @@ -1087,16 +1003,13 @@ public class TheVerifyPackageActionForGetRequests [Fact] public async Task WillRedirectToUploadPackagePageWhenThereIsNoUploadInProgress() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(null); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(null); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(null)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); - controller.SetUser(TestUtility.FakeUser); + uploadFileService: fakeUploadFileService); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.VerifyPackage() as RedirectToRouteResult; @@ -1107,18 +1020,15 @@ public async Task WillRedirectToUploadPackagePageWhenThereIsNoUploadInProgress() [Fact] public async Task WillPassThePackageIdToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Id).Returns("theId"); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1129,18 +1039,15 @@ public async Task WillPassThePackageIdToTheView() [Fact] public async Task WillPassThePackageVersionToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Version).Returns(new SemanticVersion("1.0.42")); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1151,18 +1058,15 @@ public async Task WillPassThePackageVersionToTheView() [Fact] public async Task WillPassThePackageTitleToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Title).Returns("theTitle"); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1173,18 +1077,15 @@ public async Task WillPassThePackageTitleToTheView() [Fact] public async Task WillPassThePackageSummaryToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Summary).Returns("theSummary"); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1195,18 +1096,15 @@ public async Task WillPassThePackageSummaryToTheView() [Fact] public async Task WillPassThePackageDescriptionToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Description).Returns("theDescription"); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1217,18 +1115,15 @@ public async Task WillPassThePackageDescriptionToTheView() [Fact] public async Task WillPassThePackageLicenseAcceptanceRequirementToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.RequireLicenseAcceptance).Returns(true); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1239,18 +1134,15 @@ public async Task WillPassThePackageLicenseAcceptanceRequirementToTheView() [Fact] public async Task WillPassThePackageLicenseUrlToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.LicenseUrl).Returns(new Uri("http://theLicenseUri")); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1261,18 +1153,15 @@ public async Task WillPassThePackageLicenseUrlToTheView() [Fact] public async Task WillPassThePackageTagsToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Tags).Returns("theTags"); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1283,18 +1172,15 @@ public async Task WillPassThePackageTagsToTheView() [Fact] public async Task WillPassThePackageProjectUrlToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.ProjectUrl).Returns(new Uri("http://theProjectUri")); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1305,18 +1191,15 @@ public async Task WillPassThePackageProjectUrlToTheView() [Fact] public async Task WillPassThePackagAuthorsToTheView() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); var fakeUploadFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeUploadFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeUploadFileStream)); var fakeNuGetPackage = new Mock(); fakeNuGetPackage.Setup(x => x.Metadata.Authors).Returns(new[] { "firstAuthor", "secondAuthor" }); var controller = CreateController( uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var model = ((ViewResult)await controller.VerifyPackage()).Model as VerifyPackageRequest; @@ -1330,15 +1213,12 @@ public class TheVerifyPackageActionForPostRequests [Fact] public async Task WillRedirectToUploadPageWhenThereIsNoUploadInProgress() { - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(new User { Key = 42 }); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(null)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(null)); var controller = CreateController( - uploadFileService: fakeUploadFileService, - userService: fakeUserService); + uploadFileService: fakeUploadFileService); TestUtility.SetupUrlHelperForUrlGeneration(controller, new Uri("http://uploadpackage.xyz")); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }) as RedirectResult; @@ -1348,13 +1228,10 @@ public async Task WillRedirectToUploadPageWhenThereIsNoUploadInProgress() [Fact] public async Task WillCreateThePackage() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); @@ -1363,13 +1240,12 @@ public async Task WillCreateThePackage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); - fakePackageService.Verify(x => x.CreatePackage(fakeNuGetPackage.Object, fakeCurrentUser, false)); + fakePackageService.Verify(x => x.CreatePackage(fakeNuGetPackage.Object, TestUtility.FakeUser, false)); fakeFileStream.Dispose(); } @@ -1377,13 +1253,10 @@ public async Task WillCreateThePackage() public async Task WillSavePackageToFileStorage() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(fakePackage); @@ -1394,16 +1267,15 @@ public async Task WillSavePackageToFileStorage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage, packageFileService: fakePackageFileService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); // Assert - fakePackageService.Verify(x => x.CreatePackage(fakeNuGetPackage.Object, fakeCurrentUser, false)); + fakePackageService.Verify(x => x.CreatePackage(fakeNuGetPackage.Object, TestUtility.FakeUser, false)); fakePackageFileService.Verify(); fakeFileStream.Dispose(); } @@ -1412,13 +1284,10 @@ public async Task WillSavePackageToFileStorage() public async Task WillUpdateIndexingService() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(fakePackage); @@ -1432,11 +1301,10 @@ public async Task WillUpdateIndexingService() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage, packageFileService: fakePackageFileService, indexingService: fakeIndexingService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1450,13 +1318,10 @@ public async Task WillUpdateIndexingService() public async Task WillSaveChangesToEntitiesContext() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(fakePackage); @@ -1468,10 +1333,9 @@ public async Task WillSaveChangesToEntitiesContext() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage, entitiesContext: entitiesContext); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1485,13 +1349,10 @@ public async Task WillSaveChangesToEntitiesContext() public async Task WillNotCommitChangesToPackageService() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(MockBehavior.Strict); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), false)).Returns(fakePackage); @@ -1502,9 +1363,8 @@ public async Task WillNotCommitChangesToPackageService() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1518,13 +1378,10 @@ public async Task WillNotCommitChangesToPackageService() [Fact] public async Task WillPublishThePackage() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(fakePackage); @@ -1532,9 +1389,8 @@ public async Task WillPublishThePackage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = true, Edit = null }); @@ -1545,13 +1401,10 @@ public async Task WillPublishThePackage() [Fact] public async Task WillMarkThePackageUnlistedWhenListedArgumentIsFalse() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); @@ -1559,9 +1412,8 @@ public async Task WillMarkThePackageUnlistedWhenListedArgumentIsFalse() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1575,13 +1427,10 @@ public async Task WillMarkThePackageUnlistedWhenListedArgumentIsFalse() [InlineData(new object[] { true })] public async Task WillNotMarkThePackageUnlistedWhenListedArgumentIsNullorTrue(bool? listed) { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); @@ -1589,9 +1438,8 @@ public async Task WillNotMarkThePackageUnlistedWhenListedArgumentIsNullorTrue(bo var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = listed.GetValueOrDefault(true), Edit = null }); @@ -1602,13 +1450,10 @@ public async Task WillNotMarkThePackageUnlistedWhenListedArgumentIsNullorTrue(bo [Fact] public async Task WillDeleteTheUploadFile() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)).Verifiable(); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)).Verifiable(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }); @@ -1616,9 +1461,8 @@ public async Task WillDeleteTheUploadFile() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1629,14 +1473,11 @@ public async Task WillDeleteTheUploadFile() [Fact] public async Task WillSetAFlashMessage() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(42, It.IsAny())).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.SaveUploadFileAsync(TestUtility.FakeUser.Key, It.IsAny())).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( (new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" })); @@ -1644,9 +1485,8 @@ public async Task WillSetAFlashMessage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1657,13 +1497,10 @@ public async Task WillSetAFlashMessage() [Fact] public async Task WillRedirectToPackagePage() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns( (new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" })); @@ -1671,9 +1508,8 @@ public async Task WillRedirectToPackagePage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); var result = await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }) as RedirectToRouteResult; @@ -1685,13 +1521,10 @@ public async Task WillRedirectToPackagePage() [Fact] public async Task WillCurateThePackage() { - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakeFileStream = new MemoryStream(); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(fakeFileStream)); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(fakeFileStream)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var fakePackageService = new Mock(); var fakePackage = new Package { PackageRegistration = new PackageRegistration { Id = "theId" }, Version = "theVersion" }; fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(fakePackage); @@ -1700,10 +1533,9 @@ public async Task WillCurateThePackage() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, fakeNuGetPackage: fakeNuGetPackage, autoCuratePackageCmd: fakeAutoCuratePackageCmd); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1714,12 +1546,9 @@ public async Task WillCurateThePackage() public async Task WillExtractNuGetExe() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult(Stream.Null)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(Stream.Null)); var fakePackageService = new Mock(); var commandLinePackage = new Package { @@ -1733,9 +1562,8 @@ public async Task WillExtractNuGetExe() var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, downloaderService: nugetExeDownloader); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1748,9 +1576,6 @@ public async Task WillExtractNuGetExe() public async Task WillNotExtractNuGetExeIfIsNotLatestStable() { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakePackageService = new Mock(); @@ -1763,17 +1588,16 @@ public async Task WillNotExtractNuGetExeIfIsNotLatestStable() fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(commandLinePackage); - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult( + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult( CreateTestPackageStream(commandLinePackage))); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); var nugetExeDownloader = new Mock(MockBehavior.Strict); var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, downloaderService: nugetExeDownloader); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1789,27 +1613,23 @@ public async Task WillNotExtractNuGetExeIfIsNotLatestStable() public async Task WillNotExtractNuGetExeIfIsItDoesNotMatchId(string id) { // Arrange - var fakeCurrentUser = new User { Key = 42 }; - var fakeUserService = new Mock(); - fakeUserService.Setup(x => x.FindByUsername(It.IsAny())).Returns(fakeCurrentUser); var fakeUploadFileService = new Mock(); var fakePackageService = new Mock(); var commandLinePackage = new Package { PackageRegistration = new PackageRegistration { Id = id }, Version = "2.0.0", IsLatestStable = true }; - fakeUploadFileService.Setup(x => x.GetUploadFileAsync(42)).Returns(Task.FromResult( + fakeUploadFileService.Setup(x => x.GetUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult( CreateTestPackageStream(commandLinePackage))); - fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(42)).Returns(Task.FromResult(0)); + fakeUploadFileService.Setup(x => x.DeleteUploadFileAsync(TestUtility.FakeUser.Key)).Returns(Task.FromResult(0)); fakePackageService.Setup(x => x.CreatePackage(It.IsAny(), It.IsAny(), It.IsAny())).Returns(commandLinePackage); var nugetExeDownloader = new Mock(MockBehavior.Strict); var controller = CreateController( packageService: fakePackageService, uploadFileService: fakeUploadFileService, - userService: fakeUserService, downloaderService: nugetExeDownloader); TestUtility.SetupUrlHelperForUrlGeneration(controller, new Uri("http://1.1.1.1")); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act await controller.VerifyPackage(new VerifyPackageRequest() { Listed = false, Edit = null }); @@ -1855,7 +1675,7 @@ public void WillReturnHttpNotFoundForUnknownUser() cacheService.Setup(c => c.GetItem(FakeUploadName)).Returns(null); var controller = CreateController(cacheService: cacheService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act var result = controller.UploadPackageProgress(); @@ -1872,7 +1692,7 @@ public void WillReturnCorrectResultForKnownUser() .Returns(new AsyncFileUploadProgress(100) { FileName = "haha", TotalBytesRead = 80 }); var controller = CreateController(cacheService: cacheService); - controller.SetUser(TestUtility.FakeUser); + controller.SetCurrentUser(TestUtility.FakeUser); // Act var result = controller.UploadPackageProgress() as JsonResult; diff --git a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs index 7f99e180ea..759133acf3 100644 --- a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs @@ -17,32 +17,11 @@ public class UsersControllerFacts { public class TheAccountAction : TestContainer { - [Fact] - public void WillGetTheCurrentUserUsingTheRequestIdentityName() - { - var controller = GetController(); - var user = new User { Username = "theUsername" }; - controller.SetUser(user); - GetMock() - .Setup(s => s.FindByUsername(It.IsAny())) - .Returns(user); - - //act - controller.Account(); - - // verify - GetMock() - .Verify(stub => stub.FindByUsername("theUsername")); - } - [Fact] public void WillGetCuratedFeedsManagedByTheCurrentUser() { var controller = GetController(); - controller.SetUser("user"); - GetMock() - .Setup(s => s.FindByUsername("user")) - .Returns(new User { Key = 42 }); + controller.SetCurrentUser(new User { Key = 42 }); // act controller.Account(); @@ -56,11 +35,8 @@ public void WillGetCuratedFeedsManagedByTheCurrentUser() public void WillReturnTheAccountViewModelWithTheUserApiKey() { var controller = GetController(); - controller.SetUser("user"); var stubApiKey = Guid.NewGuid(); - GetMock() - .Setup(s => s.FindByUsername("user")) - .Returns(new User { Key = 42, ApiKey = stubApiKey }); + controller.SetCurrentUser(new User { Key = 42, ApiKey = stubApiKey }); // act var model = ResultAssert.IsView(controller.Account()); @@ -73,10 +49,7 @@ public void WillReturnTheAccountViewModelWithTheUserApiKey() public void WillReturnTheAccountViewModelWithTheCuratedFeeds() { var controller = GetController(); - controller.SetUser("user"); - GetMock() - .Setup(s => s.FindByUsername("user")) - .Returns(new User { Key = 42 }); + controller.SetCurrentUser(new User { Key = 42 }); GetMock() .Setup(stub => stub.GetFeedsForManager(42)) .Returns(new[] { new CuratedFeed { Name = "theCuratedFeed" } }); @@ -93,17 +66,14 @@ public void WillUseApiKeyInColumnIfNoCredentialPresent() { var apiKey = Guid.NewGuid(); var controller = GetController(); - controller.SetUser("user"); - GetMock() - .Setup(s => s.FindByUsername("user")) - .Returns(new User { Key = 42, ApiKey = apiKey }); + controller.SetCurrentUser(new User { Key = 42, ApiKey = apiKey }); GetMock() .Setup(stub => stub.GetFeedsForManager(42)) .Returns(new[] { new CuratedFeed { Name = "theCuratedFeed" } }); // act var result = controller.Account(); - + // verify var model = ResultAssert.IsView(result); Assert.Equal(apiKey.ToString().ToLowerInvariant(), model.ApiKey); @@ -114,24 +84,21 @@ public void WillUseApiKeyInCredentialIfPresent() { var apiKey = Guid.NewGuid(); var controller = GetController(); - controller.SetUser("user"); - GetMock() - .Setup(s => s.FindByUsername("user")) - .Returns(new User - { - Key = 42, - ApiKey = Guid.NewGuid(), - Credentials = new List() { - CredentialBuilder.CreateV1ApiKey(apiKey) - } - }); + controller.SetCurrentUser(new User + { + Key = 42, + ApiKey = Guid.NewGuid(), + Credentials = new List() { + CredentialBuilder.CreateV1ApiKey(apiKey) + } + }); GetMock() .Setup(stub => stub.GetFeedsForManager(42)) .Returns(new[] { new CuratedFeed { Name = "theCuratedFeed" } }); // act var result = controller.Account(); - + // verify var model = ResultAssert.IsView(result); Assert.Equal(apiKey.ToString().ToLowerInvariant(), model.ApiKey); @@ -150,13 +117,9 @@ public void UpdatesEmailAllowedSetting() }; var controller = GetController(); - controller.SetUser(user); - GetMock() - .Setup(u => u.FindByUsername(It.IsAny())) - .Returns(user); + controller.SetCurrentUser(user); GetMock() .Setup(u => u.UpdateProfile(user, false)); - controller.SetUser(user); var model = new EditProfileViewModel { EmailAddress = "test@example.com", EmailAllowed = false }; var result = controller.Edit(model); @@ -174,15 +137,15 @@ public void SendsEmailWithPasswordResetUrl() { const string resetUrl = "https://nuget.local/account/ResetPassword/somebody/confirmation"; var user = new User("somebody") - { - EmailAddress = "some@example.com", - PasswordResetToken = "confirmation", - PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) - }; + { + EmailAddress = "some@example.com", + PasswordResetToken = "confirmation", + PasswordResetTokenExpirationDate = DateTime.UtcNow.AddDays(1) + }; GetMock() .Setup(s => s.SendPasswordResetInstructions(user, resetUrl)); GetMock() - .Setup(s => s.FindByEmailAddress(It.IsAny())) + .Setup(s => s.FindByEmailAddress("user")) .Returns(user); GetMock() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) @@ -205,7 +168,7 @@ public void RedirectsAfterGeneratingToken() .Returns(user) .Verifiable(); var controller = GetController(); - + var model = new ForgotPasswordViewModel { Email = "user" }; var result = controller.ForgotPassword(model) as RedirectToRouteResult; @@ -222,7 +185,7 @@ public void ReturnsSameViewIfTokenGenerationFails() .Setup(s => s.GeneratePasswordResetToken("user", 1440)) .Returns((User)null); var controller = GetController(); - + var model = new ForgotPasswordViewModel { Email = "user" }; var result = controller.ForgotPassword(model) as ViewResult; @@ -238,11 +201,8 @@ public class TheGenerateApiKeyMethod : TestContainer public void RedirectsToAccountPage() { var user = new User { Username = "the-username" }; - GetMock() - .Setup(u => u.FindByUsername(It.IsAny())) - .Returns(user); var controller = GetController(); - controller.SetUser(user); + controller.SetCurrentUser(user); var result = controller.GenerateApiKey(); @@ -253,16 +213,13 @@ public void RedirectsToAccountPage() public void PutsNewCredentialInOldField() { var user = new User("the-username") { ApiKey = Guid.NewGuid() }; - GetMock() - .Setup(u => u.FindByUsername("the-username")) - .Returns(user); Credential created = null; GetMock() .Setup(u => u.ReplaceCredential(user, It.IsAny())) .Callback((_, c) => created = c); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + GetMock() .Setup(u => u.FindByUsername(It.IsAny())) .Returns(user); @@ -276,17 +233,14 @@ public void PutsNewCredentialInOldField() public void ReplacesTheApiKeyCredential() { var user = new User("the-username") { ApiKey = Guid.NewGuid() }; - GetMock() - .Setup(u => u.FindByUsername("the-username")) - .Returns(user); GetMock() .Setup(u => u.ReplaceCredential( user, It.Is(c => c.Type == CredentialTypes.ApiKeyV1))) .Verifiable(); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + controller.GenerateApiKey(); GetMock().VerifyAll(); @@ -312,8 +266,8 @@ public void DoesNotLetYouUseSomeoneElsesConfirmedEmailAddress() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Throws(new EntityException("msg")); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + var result = controller.ChangeEmail(new ChangeEmailRequestModel { NewEmail = "new@example.com" }); Assert.False(controller.ModelState.IsValid); Assert.Equal("msg", controller.ModelState["NewEmail"].Errors[0].ErrorMessage); @@ -330,15 +284,15 @@ public void SendsEmailChangeConfirmationNoticeWhenChangingAConfirmedEmailAddress }; GetMock() - .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Setup(u => u.Authenticate("theUsername", "password")) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(user, "new@example.com")) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "token")); var controller = GetController(); - controller.SetUser(user); - - var model = new ChangeEmailRequestModel { NewEmail = "new@example.com" }; + controller.SetCurrentUser(user); + + var model = new ChangeEmailRequestModel { NewEmail = "new@example.com", Password = "password" }; var result = controller.ChangeEmail(model); @@ -356,15 +310,15 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenAddressDoesntChange() }; GetMock() - .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Setup(u => u.Authenticate("aUsername", "password")) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("old@example.com", () => "new-token")); var controller = GetController(); - controller.SetUser(user); - - var model = new ChangeEmailRequestModel { NewEmail = "old@example.com" }; + controller.SetCurrentUser(user); + + var model = new ChangeEmailRequestModel { NewEmail = "old@example.com", Password = "password" }; controller.ChangeEmail(model); @@ -384,15 +338,15 @@ public void DoesNotSendEmailChangeConfirmationNoticeWhenUserWasNotConfirmed() }; GetMock() - .Setup(u => u.Authenticate(It.IsAny(), It.IsAny())) + .Setup(u => u.Authenticate("aUsername", "password")) .Returns(new AuthenticatedUser(user, new Credential())); GetMock() .Setup(u => u.ChangeEmailAddress(It.IsAny(), It.IsAny())) .Callback(() => user.UpdateEmailAddress("new@example.com", () => "new-token")); var controller = GetController(); - controller.SetUser(user); - - var model = new ChangeEmailRequestModel{ NewEmail = "new@example.com" }; + controller.SetCurrentUser(user); + + var model = new ChangeEmailRequestModel { NewEmail = "new@example.com", Password = "password" }; controller.ChangeEmail(model); @@ -416,10 +370,9 @@ public void ConfirmsTheUser() EmailConfirmationToken = "aToken", }; - GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + var result = controller.Confirm("aUsername", "aToken"); GetMock().Verify(u => u.ConfirmEmailAddress(user, "aToken")); @@ -435,10 +388,9 @@ public void ShowsAnErrorForWrongUsername() EmailConfirmationToken = "aToken", }; - GetMock().Setup(u => u.FindByUsername("aUsername")).Returns(user); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + var result = controller.Confirm("wrongUsername", "aToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -456,15 +408,12 @@ public void ShowsAnErrorForWrongToken() EmailConfirmationToken = "aToken", }; - GetMock() - .Setup(u => u.FindByUsername("aUsername")) - .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, It.IsAny())) .Returns(false); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + var result = controller.Confirm("aUsername", "wrongToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -481,15 +430,12 @@ public void ShowsAnErrorForConflictingEmailAddress() EmailConfirmationToken = "aToken", }; - GetMock() - .Setup(u => u.FindByUsername("aUsername")) - .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, It.IsAny())) .Throws(new EntityException("msg")); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + var result = controller.Confirm("aUsername", "aToken"); var model = (ConfirmationViewModel)((ViewResult)result).Model; @@ -508,17 +454,14 @@ public void SendsAccountChangedNoticeWhenConfirmingChangedEmail() EmailConfirmationToken = "the-token" }; - GetMock() - .Setup(u => u.FindByUsername("username")) - .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, "the-token")) .Returns(true); var controller = GetController(); - controller.SetUser(user); + controller.SetCurrentUser(user); var result = controller.Confirm("username", "the-token"); - var model = (ConfirmationViewModel)((ViewResult)result).Model; + var model = (ConfirmationViewModel)((ViewResult)result).Model; Assert.True(model.SuccessfulConfirmation); Assert.False(model.ConfirmingNewAccount); @@ -536,15 +479,12 @@ public void DoesntSendAccountChangedEmailsWhenNoOldConfirmedAddress() UnconfirmedEmailAddress = "new@example.com", EmailConfirmationToken = "the-token" }; - - GetMock() - .Setup(u => u.FindByUsername("username")) - .Returns(user); + GetMock() .Setup(u => u.ConfirmEmailAddress(user, "the-token")) .Returns(true); var controller = GetController(); - controller.SetUser(user); + controller.SetCurrentUser(user); // act: var result = controller.Confirm("username", "the-token"); @@ -570,15 +510,12 @@ public void DoesntSendAccountChangedEmailsIfConfirmationTokenDoesntMatch() EmailConfirmationToken = "the-token" }; - GetMock() - .Setup(u => u.FindByUsername(It.IsAny())) - .Returns(user); GetMock() .Setup(u => u.ConfirmEmailAddress(user, "faketoken")) .Returns(false); var controller = GetController(); - controller.SetUser(user); - + controller.SetCurrentUser(user); + // act: var model = (controller.Confirm("username", "faketoken") as ViewResult).Model as ConfirmationViewModel; @@ -607,9 +544,6 @@ public void WillSendNewUserEmailWhenPosted() string sentConfirmationUrl = null; MailAddress sentToAddress = null; - GetMock() - .Setup(x => x.FindByUsername(It.IsAny())) - .Returns(user); GetMock() .Setup(m => m.SendNewAccountEmail(It.IsAny(), It.IsAny())) .Callback((to, url) => @@ -619,7 +553,7 @@ public void WillSendNewUserEmailWhenPosted() }); var controller = GetController(); - controller.SetUser(user); + controller.SetCurrentUser(user); controller.ConfirmationRequiredPost(); @@ -649,16 +583,16 @@ public void ReturnsViewIfModelStateInvalid() } [Fact] - public void AddsModelErrorIfUserServiceFails() + public void AddsModelErrorIfAuthServiceFails() { // Arrange GetMock() .Setup(u => u.ChangePassword("user", "old", "new")) .Returns(false); - + var controller = GetController(); - controller.SetUser("user"); - + controller.SetPrincipal("user"); + var inputModel = new PasswordChangeViewModel() { OldPassword = "old", @@ -689,7 +623,7 @@ public void RedirectsToPasswordChangedIfUserServiceSucceeds() .Setup(u => u.ChangePassword("user", "old", "new")) .Returns(true); var controller = GetController(); - controller.SetUser("user"); + controller.SetPrincipal("user"); var inputModel = new PasswordChangeViewModel() { OldPassword = "old", @@ -761,7 +695,7 @@ public void ShowsDefaultThanksView() .Setup(x => x.ConfirmEmailAddresses) .Returns(true); var controller = GetController(); - + var result = controller.Thanks() as ViewResult; Assert.Empty(result.ViewName); @@ -775,7 +709,7 @@ public void ShowsConfirmViewWithModelWhenConfirmingEmailAddressIsNotRequired() .Setup(x => x.ConfirmEmailAddresses) .Returns(false); var controller = GetController(); - + ResultAssert.IsView(controller.Thanks()); } } diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 8779f45049..8ec27dfb20 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -4,6 +4,9 @@ using System.Reflection; using System.Security.Claims; using System.Security.Principal; +using Microsoft.Owin; +using Microsoft.Owin.Security; +using Moq; using NuGetGallery.Authentication; namespace NuGetGallery.Framework @@ -99,5 +102,17 @@ internal static void ConfigureEntitiesContext(FakeEntitiesContext ctxt) } } } + + public static Mock CreateOwinContext() + { + var context = new Mock(); + context.Setup(c => c.Environment).Returns(new Dictionary()); + context.Setup(c => c.Request).Returns(new Mock().Object); + context.Setup(c => c.Response).Returns(new Mock().Object); + context.Setup(c => c.Authentication).Returns(new Mock().Object); + context.Setup(c => c.Request.PathBase).Returns("/testroot"); + context.Setup(c => c.Request.Headers).Returns(new HeaderDictionary(new Dictionary())); + return context; + } } } diff --git a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs index ea543ea8c8..79ec12bd6b 100644 --- a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs +++ b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using System.Security.Principal; using System.Text; using System.Threading.Tasks; -using System.Web.Mvc; using Moq; using NuGetGallery.Authentication; @@ -13,17 +11,19 @@ namespace NuGetGallery { public static class TestExtensionMethods { - public static void SetUser(this AppController self, string userName) + /// + /// Should only be used in the rare cases where you are testing an action that + /// does NOT use AppController.GetCurrentUser()! In those cases, use + /// AppController.SetCurrentUser instead. + /// + /// + public static void SetPrincipal(this AppController self, string name) { - SetUser(self, new User(userName)); - } - - public static void SetUser(this AppController self, User user) - { - Mock.Get(self.HttpContext).Setup(c => c.Request.IsAuthenticated).Returns(true); - Mock.Get(self.HttpContext).Setup(c => c.User).Returns( - new ClaimsPrincipal( - AuthenticationService.CreateIdentity(user, "Test"))); + var mock = Mock.Get(self.HttpContext); + mock.Setup(c => c.Request.IsAuthenticated).Returns(true); + mock.Setup(c => c.User).Returns(new ClaimsPrincipal( + new ClaimsIdentity( + new [] { new Claim(ClaimTypes.Name, name) }))); } } } diff --git a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj index c69dd4b0b2..4e1b992067 100644 --- a/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj +++ b/tests/NuGetGallery.Facts/NuGetGallery.Facts.csproj @@ -218,6 +218,7 @@ + diff --git a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs index 3a038a17c2..cbdfd87b30 100644 --- a/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs +++ b/tests/NuGetGallery.Facts/TestUtils/TestUtility.cs @@ -18,7 +18,7 @@ public static class TestUtility public static readonly string FakeUserName = "theUsername"; public static readonly string FakeAdminName = "theAdmin"; - public static readonly User FakeUser = new User() { Username = FakeUserName }; + public static readonly User FakeUser = new User() { Username = FakeUserName, Key = 42 }; public static readonly User FakeAdminUser = new User() { Username = FakeAdminName, Roles = new List() { new Role() { Name = Constants.AdminRoleName } } }; // We only need this method because testing URL generation is a pain. From 0bbc3745331c91af84a3c029c176ee56d8eebd4c Mon Sep 17 00:00:00 2001 From: anurse Date: Thu, 17 Oct 2013 17:05:40 -0700 Subject: [PATCH 16/18] Most unit tests passing --- .../Controllers/CuratedFeedsControllerFacts.cs | 4 +++- .../Controllers/CuratedPackagesControllerFacts.cs | 2 ++ .../Controllers/PackagesControllerFacts.cs | 1 + tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs index 12869c457d..3058f746fb 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs @@ -21,7 +21,9 @@ public TestableCuratedFeedsController() StubCuratedFeed = new CuratedFeed { Key = 0, Name = "aName", Managers = new HashSet(new[] { Fakes.User }) }; StubCuratedFeedService = new Mock(); - + + OwinContext = Fakes.CreateOwinContext().Object; + StubCuratedFeedService .Setup(stub => stub.GetFeedByName(It.IsAny(), It.IsAny())) .Returns(StubCuratedFeed); diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs index b490503007..4096f2a625 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs @@ -21,6 +21,8 @@ public TestableCuratedPackagesController() { Key = 0, Name = "aFeedName", Managers = new HashSet(new[] { Fakes.User }) }; StubPackageRegistration = new PackageRegistration { Key = 0, Id = "anId" }; + OwinContext = Fakes.CreateOwinContext().Object; + EntitiesContext = new FakeEntitiesContext(); EntitiesContext.CuratedFeeds.Add(StubCuratedFeed); EntitiesContext.PackageRegistrations.Add(StubPackageRegistration); diff --git a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs index aea3bfe210..e8fca87e38 100644 --- a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs @@ -84,6 +84,7 @@ private static PackagesController CreateController( cacheService.Object, editPackageService.Object); controller.CallBase = true; + controller.Object.OwinContext = Fakes.CreateOwinContext().Object; httpContext = httpContext ?? new Mock(); TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, controller.Object); diff --git a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs index 9fa7103919..881969a42d 100644 --- a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs +++ b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs @@ -7,6 +7,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; +using Microsoft.Owin; using Moq; using Ninject; using Ninject.Activation; @@ -68,6 +69,10 @@ public override void Load() return ctxt; }) .InSingletonScope(); + + Bind() + .ToMethod(_ => Fakes.CreateOwinContext().Object) + .InSingletonScope(); } private class TestKernel : MoqMockingKernel From a4bdacb322a757dbb1ab2809ae4476dda4971f4d Mon Sep 17 00:00:00 2001 From: anurse Date: Mon, 21 Oct 2013 11:57:34 -0700 Subject: [PATCH 17/18] Updated Facts. --- src/NuGetGallery/Controllers/AppController.cs | 16 ++-- .../ApiKeyAuthenticationHandlerFacts.cs | 79 ++++++++----------- .../AuthenticationServiceFacts.cs | 12 ++- .../Controllers/ApiControllerFacts.cs | 7 +- .../Controllers/AppControllerFacts.cs | 4 +- .../AuthenticationControllerFacts.cs | 8 +- .../CuratedFeedsControllerFacts.cs | 29 +++---- .../CuratedPackagesControllerFacts.cs | 2 +- .../Controllers/PackagesControllerFacts.cs | 52 ++++++------ .../Controllers/UsersControllerFacts.cs | 4 +- tests/NuGetGallery.Facts/Framework/Fakes.cs | 13 +-- .../Framework/TestExtensionMethods.cs | 20 +++-- .../Framework/UnitTestBindings.cs | 2 +- 13 files changed, 113 insertions(+), 135 deletions(-) diff --git a/src/NuGetGallery/Controllers/AppController.cs b/src/NuGetGallery/Controllers/AppController.cs index 7aab6f2cc4..6e83e3f31a 100644 --- a/src/NuGetGallery/Controllers/AppController.cs +++ b/src/NuGetGallery/Controllers/AppController.cs @@ -13,7 +13,7 @@ namespace NuGetGallery public abstract partial class AppController : Controller { private IOwinContext _overrideContext; - + public IOwinContext OwinContext { get { return _overrideContext ?? HttpContext.GetOwinContext(); } @@ -40,6 +40,11 @@ protected internal virtual T GetService() /// The current user protected internal User GetCurrentUser() { + if (OwinContext.Request.User == null) + { + return null; + } + User user = null; object obj; if (OwinContext.Environment.TryGetValue(Constants.CurrentUserOwinEnvironmentKey, out obj)) @@ -63,19 +68,14 @@ protected internal User GetCurrentUser() return user; } - protected internal void SetCurrentUser(User user) - { - OwinContext.Environment[Constants.CurrentUserOwinEnvironmentKey] = user; - } - private User LoadUser() { var principal = OwinContext.Authentication.User; - if(principal != null) + if (principal != null) { // Try to authenticate with the user name string userName = principal.GetClaimOrDefault(ClaimTypes.Name); - + if (!String.IsNullOrEmpty(userName)) { return DependencyResolver diff --git a/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs index 914e4d9508..f9f6886394 100644 --- a/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/ApiKeyAuthenticationHandlerFacts.cs @@ -28,7 +28,7 @@ public async Task GivenANon401ResponseInActiveMode_ItReturnsNull() { AuthenticationMode = AuthenticationMode.Active }); - handler.MockContext.Setup(c => c.Response.StatusCode).Returns(200); + handler.OwinContext.Response.StatusCode = 200; // Act var message = handler.GetChallengeMessage(); @@ -46,12 +46,9 @@ public async Task GivenA401ResponseInPassiveModeWithoutMatchingAuthenticationTyp AuthenticationMode = AuthenticationMode.Passive, AuthenticationType = "blarg" }); - handler.MockContext - .Setup(c => c.Response.StatusCode) - .Returns(401); - handler.MockContext - .Setup(c => c.Authentication.AuthenticationResponseChallenge) - .Returns(new AuthenticationResponseChallenge(new [] { "flarg" }, new AuthenticationProperties())); + handler.OwinContext.Response.StatusCode = 401; + handler.OwinContext.Authentication.AuthenticationResponseChallenge = + new AuthenticationResponseChallenge(new [] { "flarg" }, new AuthenticationProperties()); // Act var message = handler.GetChallengeMessage(); @@ -69,13 +66,10 @@ public async Task GivenA401ResponseInPassiveModeWithMatchingAuthenticationTypeAn AuthenticationMode = AuthenticationMode.Passive, AuthenticationType = "blarg" }); - handler.MockContext - .Setup(c => c.Response.StatusCode) - .Returns(401); - handler.MockContext - .Setup(c => c.Authentication.AuthenticationResponseChallenge) - .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); - + handler.OwinContext.Response.StatusCode = 401; + handler.OwinContext.Authentication.AuthenticationResponseChallenge = + new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties()); + // Act var message = handler.GetChallengeMessage(); @@ -92,13 +86,10 @@ public async Task GivenA401ResponseInPassiveModeWithMatchingAuthenticationTypeAn AuthenticationMode = AuthenticationMode.Passive, AuthenticationType = "blarg" }); - handler.MockContext - .Setup(c => c.Response.StatusCode) - .Returns(401); - handler.MockContext - .Setup(c => c.Authentication.AuthenticationResponseChallenge) - .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); - handler.MockContext.Object.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; + handler.OwinContext.Response.StatusCode = 401; + handler.OwinContext.Authentication.AuthenticationResponseChallenge = + new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties()); + handler.OwinContext.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; // Act var message = handler.GetChallengeMessage(); @@ -116,12 +107,9 @@ public async Task GivenA401ResponseInActiveModeAndNoHeader_ItReturnsApiKeyRequir AuthenticationMode = AuthenticationMode.Active, AuthenticationType = "blarg" }); - handler.MockContext - .Setup(c => c.Response.StatusCode) - .Returns(401); - handler.MockContext - .Setup(c => c.Authentication.AuthenticationResponseChallenge) - .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); + handler.OwinContext.Response.StatusCode = 401; + handler.OwinContext.Authentication.AuthenticationResponseChallenge = + new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties()); // Act var message = handler.GetChallengeMessage(); @@ -139,13 +127,10 @@ public async Task GivenA401ResponseInActiveModeAndHeader_ItReturnsApiKeyNotAutho AuthenticationMode = AuthenticationMode.Active, AuthenticationType = "blarg" }); - handler.MockContext - .Setup(c => c.Response.StatusCode) - .Returns(401); - handler.MockContext - .Setup(c => c.Authentication.AuthenticationResponseChallenge) - .Returns(new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties())); - handler.MockContext.Object.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; + handler.OwinContext.Response.StatusCode = 401; + handler.OwinContext.Authentication.AuthenticationResponseChallenge = + new AuthenticationResponseChallenge(new [] { "blarg" }, new AuthenticationProperties()); + handler.OwinContext.Request.Headers[Constants.ApiKeyHeaderName] = "woozle wuzzle"; // Act var message = handler.GetChallengeMessage(); @@ -165,7 +150,7 @@ public async Task GivenANonMatchingPath_ItReturnsNull() { RootPath = "/api" }); - handler.MockContext.Setup(c => c.Request.Path).Returns("/packages"); + handler.OwinContext.Request.Path = "/packages"; // Act var ticket = await handler.InvokeAuthenticateCoreAsync(); @@ -182,7 +167,7 @@ public async Task GivenNoApiKeyHeader_ItReturnsNull() { RootPath = "/api" }); - handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); + handler.OwinContext.Request.Path = "/api/v2/packages"; // Act var ticket = await handler.InvokeAuthenticateCoreAsync(); @@ -200,8 +185,8 @@ public async Task GivenNoUserMatchingApiKey_ItReturnsNull() { RootPath = "/api" }); - handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); - handler.MockContext.Object.Request.Headers.Set( + handler.OwinContext.Request.Path = "/api/v2/packages"; + handler.OwinContext.Request.Headers.Set( Constants.ApiKeyHeaderName, apiKey.ToString().ToLowerInvariant()); @@ -222,8 +207,8 @@ public async Task GivenMatchingApiKey_ItReturnsTicketWithUserNameAndRoles() { RootPath = "/api" }); - handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); - handler.MockContext.Object.Request.Headers.Set( + handler.OwinContext.Request.Path = "/api/v2/packages"; + handler.OwinContext.Request.Headers.Set( Constants.ApiKeyHeaderName, apiKey.ToString().ToLowerInvariant()); handler.MockAuth.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); @@ -233,7 +218,7 @@ public async Task GivenMatchingApiKey_ItReturnsTicketWithUserNameAndRoles() // Assert Assert.NotNull(ticket); - Assert.Equal("theUser", ticket.Identity.GetClaimOrDefault(ClaimTypes.Name)); + Assert.Equal(apiKey.ToString().ToLower(), ticket.Identity.GetClaimOrDefault(NuGetClaims.ApiKey)); } [Fact] @@ -246,8 +231,8 @@ public async Task GivenMatchingApiKey_ItSetsUserInOwinEnvironment() { RootPath = "/api" }); - handler.MockContext.Setup(c => c.Request.Path).Returns("/api/v2/packages"); - handler.MockContext.Object.Request.Headers.Set( + handler.OwinContext.Request.Path = "/api/v2/packages"; + handler.OwinContext.Request.Headers.Set( Constants.ApiKeyHeaderName, apiKey.ToString().ToLowerInvariant()); handler.MockAuth.SetupAuth(CredentialBuilder.CreateV1ApiKey(apiKey), user); @@ -256,16 +241,18 @@ public async Task GivenMatchingApiKey_ItSetsUserInOwinEnvironment() await handler.InvokeAuthenticateCoreAsync(); // Assert - Assert.Same(user, handler.MockContext.Object.Environment[Constants.CurrentUserOwinEnvironmentKey]); + var authUser = Assert.IsType( + handler.OwinContext.Environment[Constants.CurrentUserOwinEnvironmentKey]); + Assert.Same(user, authUser.User); } } // Why a TestableNNN class? Because we need to access protected members. public class TestableApiKeyAuthenticationHandler : ApiKeyAuthenticationHandler { - public Mock MockContext { get; private set; } public Mock MockAuth { get; private set; } public Mock MockLogger { get; private set; } + public IOwinContext OwinContext { get { return base.Context; } } private TestableApiKeyAuthenticationHandler() { @@ -285,7 +272,7 @@ public static async Task CreateAsync(ApiKey var handler = new TestableApiKeyAuthenticationHandler(); - var ctxt = (handler.MockContext = Fakes.CreateOwinContext()).Object; + var ctxt = Fakes.CreateOwinContext(); // Grr, have to make an internal call to initialize... await (Task)(typeof(AuthenticationHandler) diff --git a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs index 0e5b155c02..20f35b248a 100644 --- a/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Authentication/AuthenticationServiceFacts.cs @@ -192,11 +192,7 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() { // Arrange var service = Get(); - ClaimsIdentity id = null; - var context = GetMock(); - context - .Setup(c => c.Authentication.SignIn(It.IsAny())) - .Callback(ids => id = ids.SingleOrDefault()); + var context = Fakes.CreateOwinContext(); var passwordCred = Fakes.Admin.Credentials.SingleOrDefault( c => String.Equals(c.Type, CredentialTypes.Password.Pbkdf2, StringComparison.OrdinalIgnoreCase)); @@ -204,11 +200,13 @@ public void GivenAUser_ItCreatesAnOwinAuthenticationTicketForTheUser() var authUser = new AuthenticatedUser(Fakes.Admin, passwordCred); // Act - service.CreateSession(context.Object, authUser.User, AuthenticationTypes.Cookie); + service.CreateSession(context, authUser.User, AuthenticationTypes.Cookie); // Assert + var principal = context.Authentication.AuthenticationResponseGrant.Principal; + var id = principal.Identity; + Assert.NotNull(principal); Assert.NotNull(id); - var principal = new ClaimsPrincipal(id); Assert.Equal(Fakes.Admin.Username, id.Name); Assert.Equal(AuthenticationTypes.Cookie, id.AuthenticationType); Assert.True(principal.IsInRole(Constants.AdminRoleName)); diff --git a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs index 8b9d2fcc49..950c2b526c 100644 --- a/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs @@ -31,13 +31,12 @@ class TestableApiController : ApiController public Mock MockContentService { get; private set; } public Mock MockStatisticsService { get; private set; } public Mock MockIndexingService { get; private set; } - public Mock MockOwinContext { get; set; } - + private INupkg PackageFromInputStream { get; set; } public TestableApiController(MockBehavior behavior = MockBehavior.Default) { - OwinContext = (MockOwinContext = Fakes.CreateOwinContext()).Object; + OwinContext = Fakes.CreateOwinContext(); EntitiesContext = (MockEntitiesContext = new Mock()).Object; PackageService = (MockPackageService = new Mock(behavior)).Object; UserService = (MockUserService = new Mock(behavior)).Object; @@ -49,6 +48,8 @@ public TestableApiController(MockBehavior behavior = MockBehavior.Default) MockPackageFileService = new Mock(MockBehavior.Strict); MockPackageFileService.Setup(p => p.SavePackageFileAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); PackageFileService = MockPackageFileService.Object; + + TestUtility.SetupHttpContextMockForUrlGeneration(new Mock(), this); } internal void SetupPackageFromInputStream(Mock nuGetPackage) diff --git a/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs index b447eef1bb..5ebdebea9c 100644 --- a/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AppControllerFacts.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.Owin; using Moq; +using NuGetGallery.Framework; using Xunit; namespace NuGetGallery.Controllers @@ -17,9 +18,8 @@ public class TheGetCurrentUserMethod public void GivenNoActiveUserPrincipal_ItReturnsNull() { // Arrange - var context = new Mock(); var ctrl = new TestableAppController(); - ctrl.OwinContext = context.Object; + ctrl.OwinContext = Fakes.CreateOwinContext(); // Act var user = ctrl.InvokeGetCurrentUser(); diff --git a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs index 9c1d56369d..e66d15f81c 100644 --- a/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/AuthenticationControllerFacts.cs @@ -17,20 +17,18 @@ public class TheLogOffAction : TestContainer [Fact] public void WillLogTheUserOff() { - GetMock() - .Setup(c => c.Authentication.SignOut()); var controller = GetController(); controller.LogOff("theReturnUrl"); - GetMock() - .Verify(c => c.Authentication.SignOut()); + var revoke = controller.OwinContext.Authentication.AuthenticationResponseRevoke; + Assert.NotNull(revoke); + Assert.Empty(revoke.AuthenticationTypes); } [Fact] public void WillRedirectToTheReturnUrl() { - GetMock().Setup(c => c.Authentication.SignOut()); var controller = GetController(); var result = controller.LogOff("theReturnUrl"); diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs index 3058f746fb..add06b9758 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedFeedsControllerFacts.cs @@ -22,7 +22,7 @@ public TestableCuratedFeedsController() { Key = 0, Name = "aName", Managers = new HashSet(new[] { Fakes.User }) }; StubCuratedFeedService = new Mock(); - OwinContext = Fakes.CreateOwinContext().Object; + OwinContext = Fakes.CreateOwinContext(); StubCuratedFeedService .Setup(stub => stub.GetFeedByName(It.IsAny(), It.IsAny())) @@ -63,7 +63,7 @@ public void WillReturn404IfTheCuratedFeedDoesNotExist() var controller = new TestableCuratedFeedsController(); controller.StubCuratedFeedService.Setup(stub => stub.GetFeedByName(It.IsAny(), It.IsAny())).Returns((CuratedFeed)null); - var result = controller.CuratedFeed("aFeedName"); + var result = controller.CuratedFeed("aName"); Assert.IsType(result); } @@ -73,8 +73,8 @@ public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() { var controller = new TestableCuratedFeedsController(); controller.SetCurrentUser(Fakes.Owner); - - var result = controller.CuratedFeed("aFeedName") as HttpStatusCodeResult; + + var result = controller.CuratedFeed("aName") as HttpStatusCodeResult; Assert.NotNull(result); Assert.Equal(403, result.StatusCode); @@ -84,27 +84,22 @@ public void WillReturn403IfTheCurrentUsersIsNotAManagerOfTheCuratedFeed() public void WillPassTheCuratedFeedNameToTheView() { var controller = new TestableCuratedFeedsController(); - controller.StubCuratedFeed.Name = "theCuratedFeedName"; - - var viewModel = (controller.CuratedFeed("aFeedName") as ViewResult).Model as CuratedFeedViewModel; + + var viewModel = (controller.CuratedFeed("aName") as ViewResult).Model as CuratedFeedViewModel; Assert.NotNull(viewModel); - Assert.Equal("theCuratedFeedName", viewModel.Name); + Assert.Equal("aName", viewModel.Name); } [Fact] public void WillPassTheCuratedFeedManagersToTheView() { - var user = new User { Username = "theManager" }; var controller = new TestableCuratedFeedsController(); - controller.SetCurrentUser(user); - controller.StubCuratedFeed.Name = "aFeedName"; - controller.StubCuratedFeed.Managers = new HashSet(new[] { user }); - - var viewModel = (controller.CuratedFeed("aFeedName") as ViewResult).Model as CuratedFeedViewModel; + + var viewModel = (controller.CuratedFeed("aName") as ViewResult).Model as CuratedFeedViewModel; Assert.NotNull(viewModel); - Assert.Equal("theManager", viewModel.Managers.First()); + Assert.Equal(Fakes.User.Username, viewModel.Managers.First()); } [Fact] @@ -134,7 +129,7 @@ public void WillPassTheIncludedPackagesToTheView() } }); - var viewModel = (controller.CuratedFeed("aFeedName") as ViewResult).Model as CuratedFeedViewModel; + var viewModel = (controller.CuratedFeed("aName") as ViewResult).Model as CuratedFeedViewModel; Assert.NotNull(viewModel); Assert.Equal(2, viewModel.IncludedPackages.Count()); @@ -169,7 +164,7 @@ public void WillPassTheExcludedPackagesToTheView() } }); - var viewModel = (controller.CuratedFeed("aFeedName") as ViewResult).Model as CuratedFeedViewModel; + var viewModel = (controller.CuratedFeed("aName") as ViewResult).Model as CuratedFeedViewModel; Assert.NotNull(viewModel); Assert.Equal(1, viewModel.ExcludedPackages.Count()); diff --git a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs index 4096f2a625..5b30a0ca21 100644 --- a/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/CuratedPackagesControllerFacts.cs @@ -21,7 +21,7 @@ public TestableCuratedPackagesController() { Key = 0, Name = "aFeedName", Managers = new HashSet(new[] { Fakes.User }) }; StubPackageRegistration = new PackageRegistration { Key = 0, Id = "anId" }; - OwinContext = Fakes.CreateOwinContext().Object; + OwinContext = Fakes.CreateOwinContext(); EntitiesContext = new FakeEntitiesContext(); EntitiesContext.CuratedFeeds.Add(StubCuratedFeed); diff --git a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs index e8fca87e38..7e554dbee0 100644 --- a/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/PackagesControllerFacts.cs @@ -84,7 +84,7 @@ private static PackagesController CreateController( cacheService.Object, editPackageService.Object); controller.CallBase = true; - controller.Object.OwinContext = Fakes.CreateOwinContext().Object; + controller.Object.OwinContext = Fakes.CreateOwinContext(); httpContext = httpContext ?? new Mock(); TestUtility.SetupHttpContextMockForUrlGeneration(httpContext, controller.Object); @@ -369,8 +369,7 @@ public void WithEmptyTokenReturnsHttpNotFound() packageService.Setup(p => p.FindPackageRegistrationById("foo")).Returns(new PackageRegistration()); var controller = CreateController(packageService: packageService); controller.SetCurrentUser(new User { Username = "username" }); - controller.SetPrincipal("username"); - + var result = controller.ConfirmOwner("foo", "username", ""); Assert.IsType(result); @@ -379,11 +378,14 @@ public void WithEmptyTokenReturnsHttpNotFound() [Fact] public void WithNonExistentPackageIdReturnsHttpNotFound() { + // Arrange var controller = CreateController(); - var result = controller.ConfirmOwner("foo", "username", "token"); controller.SetCurrentUser(new User { Username = "username" }); - controller.SetPrincipal("username"); + // Act + var result = controller.ConfirmOwner("foo", "username", "token"); + + // Assert Assert.IsType(result); } @@ -391,8 +393,8 @@ public void WithNonExistentPackageIdReturnsHttpNotFound() public void WithIdentityNotMatchingUserInRequestReturnsViewWithMessage() { var controller = CreateController(); + controller.SetCurrentUser("userA"); var result = controller.ConfirmOwner("foo", "userB", "token"); - controller.SetPrincipal("userA"); var model = ResultAssert.IsView(result); Assert.Equal(ConfirmOwnershipResult.NotYourRequest, model.Result); @@ -412,8 +414,7 @@ public void AcceptsResultOfPackageServiceIfOtherwiseValid(ConfirmOwnershipResult packageService.Setup(p => p.ConfirmPackageOwner(package, user, "token")).Returns(confirmationResult); var controller = CreateController(packageService: packageService); controller.SetCurrentUser(user); - controller.SetPrincipal(user.Username); - + var result = controller.ConfirmOwner("foo", "username", "token"); var model = ResultAssert.IsView(result); @@ -527,13 +528,10 @@ public void UpdatesUnlistedIfSelected() packageService.Setup(svc => svc.MarkPackageUnlisted(It.IsAny(), It.IsAny())).Verifiable(); packageService.Setup(svc => svc.FindPackageByIdAndVersion("Foo", "1.0", true)).Returns(package).Verifiable(); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Frodo"); - var indexingService = new Mock(); - var controller = CreateController(packageService: packageService, httpContext: httpContext, indexingService: indexingService); + var controller = CreateController(packageService: packageService, indexingService: indexingService); + controller.SetCurrentUser("Frodo"); controller.Url = new UrlHelper(new RequestContext(), new RouteCollection()); // Act @@ -563,13 +561,10 @@ public void UpdatesUnlistedIfNotSelected() packageService.Setup(svc => svc.MarkPackageUnlisted(It.IsAny(), It.IsAny())).Throws(new Exception("Shouldn't be called")); packageService.Setup(svc => svc.FindPackageByIdAndVersion("Foo", "1.0", true)).Returns(package).Verifiable(); - var httpContext = new Mock(); - httpContext.Setup(h => h.Request.IsAuthenticated).Returns(true); - httpContext.Setup(h => h.User.Identity.Name).Returns("Frodo"); - var indexingService = new Mock(); - var controller = CreateController(packageService: packageService, httpContext: httpContext, indexingService: indexingService); + var controller = CreateController(packageService: packageService, indexingService: indexingService); + controller.SetCurrentUser("Frodo"); controller.Url = new UrlHelper(new RequestContext(), new RouteCollection()); // Act @@ -588,13 +583,8 @@ public class TheListPackagesMethod [Fact] public void TrimsSearchTerm() { - var fakeIdentity = new Mock(); - var httpContext = new Mock(); var searchService = new Mock(); - - var controller = CreateController( - httpContext: httpContext, - searchService: searchService); + var controller = CreateController(searchService: searchService); controller.SetCurrentUser(TestUtility.FakeUser); var result = controller.ListPackages(" test ") as ViewResult; @@ -652,6 +642,7 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() var messageService = new Mock(); messageService.Setup( s => s.ReportAbuse(It.Is(r => r.Message == "Mordor took my finger"))); + var user = new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 1 }; var package = new Package { PackageRegistration = new PackageRegistration { Id = "mordor" }, @@ -664,11 +655,11 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() packageService: packageService, messageService: messageService, httpContext: httpContext); - controller.SetCurrentUser(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 1 }); + controller.SetCurrentUser(user); var model = new ReportAbuseViewModel { Message = "Mordor took my finger", - Reason = ReportPackageReason.IsFraudulent, + Reason = ReportPackageReason.IsFraudulent }; TestUtility.SetupUrlHelper(controller, httpContext); @@ -687,9 +678,10 @@ public void SendsMessageToGalleryOwnerWithUserInfoWhenAuthenticated() [Fact] public void FormRedirectsPackageOwnerToReportMyPackage() { + var user = new User { EmailAddress = "darklord@mordor.com", Username = "Sauron" }; var package = new Package { - PackageRegistration = new PackageRegistration { Id = "Mordor", Owners = { new User { Username = "Sauron" } } }, + PackageRegistration = new PackageRegistration { Id = "Mordor", Owners = { user } }, Version = "2.0.1" }; var packageService = new Mock(); @@ -698,7 +690,7 @@ public void FormRedirectsPackageOwnerToReportMyPackage() var controller = CreateController( packageService: packageService, httpContext: httpContext); - controller.SetCurrentUser(new User { EmailAddress = "darklord@mordor.com", Username = "Sauron" }); + controller.SetCurrentUser(user); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportAbuse("Mordor", "2.0.1"); @@ -757,13 +749,14 @@ public void FormRedirectsNonOwnersToReportAbuse() PackageRegistration = new PackageRegistration { Id = "Mordor", Owners = { new User { Username = "Sauron", Key = 1 } } }, Version = "2.0.1" }; + var user = new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 2 }; var packageService = new Mock(); packageService.Setup(p => p.FindPackageByIdAndVersion("Mordor", It.IsAny(), true)).Returns(package); var httpContext = new Mock(); var controller = CreateController( packageService: packageService, httpContext: httpContext); - controller.SetCurrentUser(new User { EmailAddress = "frodo@hobbiton.example.com", Username = "Frodo", Key = 2 }); + controller.SetCurrentUser(user); TestUtility.SetupUrlHelper(controller, httpContext); ActionResult result = controller.ReportMyPackage("Mordor", "2.0.1"); @@ -1735,6 +1728,7 @@ public void IndexingAndPackageServicesAreUpdated() var indexingService = new Mock(); var controller = CreateController(packageService: packageService, httpContext: httpContext, indexingService: indexingService); + controller.SetCurrentUser("Smeagol"); controller.Url = new UrlHelper(new RequestContext(), new RouteCollection()); // Act diff --git a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs index 759133acf3..0ce698396c 100644 --- a/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs +++ b/tests/NuGetGallery.Facts/Controllers/UsersControllerFacts.cs @@ -591,7 +591,7 @@ public void AddsModelErrorIfAuthServiceFails() .Returns(false); var controller = GetController(); - controller.SetPrincipal("user"); + controller.SetCurrentUser("user"); var inputModel = new PasswordChangeViewModel() { @@ -623,7 +623,7 @@ public void RedirectsToPasswordChangedIfUserServiceSucceeds() .Setup(u => u.ChangePassword("user", "old", "new")) .Returns(true); var controller = GetController(); - controller.SetPrincipal("user"); + controller.SetCurrentUser("user"); var inputModel = new PasswordChangeViewModel() { OldPassword = "old", diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 8ec27dfb20..6c109b04ee 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -103,16 +103,11 @@ internal static void ConfigureEntitiesContext(FakeEntitiesContext ctxt) } } - public static Mock CreateOwinContext() + public static IOwinContext CreateOwinContext() { - var context = new Mock(); - context.Setup(c => c.Environment).Returns(new Dictionary()); - context.Setup(c => c.Request).Returns(new Mock().Object); - context.Setup(c => c.Response).Returns(new Mock().Object); - context.Setup(c => c.Authentication).Returns(new Mock().Object); - context.Setup(c => c.Request.PathBase).Returns("/testroot"); - context.Setup(c => c.Request.Headers).Returns(new HeaderDictionary(new Dictionary())); - return context; + var ctx = new OwinContext(); + ctx.Set, object>>("server.OnSendingHeaders", (_, __) => { }); + return ctx; } } } diff --git a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs index 79ec12bd6b..1179b9636a 100644 --- a/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs +++ b/tests/NuGetGallery.Facts/Framework/TestExtensionMethods.cs @@ -14,16 +14,26 @@ public static class TestExtensionMethods /// /// Should only be used in the rare cases where you are testing an action that /// does NOT use AppController.GetCurrentUser()! In those cases, use - /// AppController.SetCurrentUser instead. + /// TestExtensionMethods.SetCurrentUser(AppController, User) instead. /// /// - public static void SetPrincipal(this AppController self, string name) + public static void SetCurrentUser(this AppController self, string name) { + var principal = new ClaimsPrincipal( + new ClaimsIdentity( + new [] { new Claim(ClaimTypes.Name, String.IsNullOrEmpty(name) ? "theUserName" : name) })); + var mock = Mock.Get(self.HttpContext); mock.Setup(c => c.Request.IsAuthenticated).Returns(true); - mock.Setup(c => c.User).Returns(new ClaimsPrincipal( - new ClaimsIdentity( - new [] { new Claim(ClaimTypes.Name, name) }))); + mock.Setup(c => c.User).Returns(principal); + + self.OwinContext.Request.User = principal; + } + + public static void SetCurrentUser(this AppController self, User user) + { + SetCurrentUser(self, user.Username); + self.OwinContext.Environment[Constants.CurrentUserOwinEnvironmentKey] = user; } } } diff --git a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs index 881969a42d..f18388d132 100644 --- a/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs +++ b/tests/NuGetGallery.Facts/Framework/UnitTestBindings.cs @@ -71,7 +71,7 @@ public override void Load() .InSingletonScope(); Bind() - .ToMethod(_ => Fakes.CreateOwinContext().Object) + .ToMethod(_ => Fakes.CreateOwinContext()) .InSingletonScope(); } From 27a8a4dc4b4aadc71bc1f84885d5e3d4d8e373ac Mon Sep 17 00:00:00 2001 From: anurse Date: Mon, 21 Oct 2013 17:49:54 -0700 Subject: [PATCH 18/18] Added middleware to force ssl when authenticated --- src/NuGetGallery/App_Start/AppActivator.cs | 5 +- src/NuGetGallery/App_Start/OwinStartup.cs | 15 +- .../ApiKeyAuthenticationHandler.cs | 13 +- .../Authentication/AuthenticationService.cs | 33 ++- .../Authentication/AuthenticationTypes.cs | 3 +- .../ForceSslWhenAuthenticatedMiddleware.cs | 104 ++++++++++ src/NuGetGallery/Controllers/ApiController.cs | 14 +- .../Controllers/AuthenticationController.cs | 11 +- .../Controllers/PackagesController.cs | 3 - .../Diagnostics/NullDiagnosticsSource.cs | 2 + .../Filters/ApiAuthorizeAttribute.cs | 2 +- .../RequireSslAttribute.cs} | 24 +-- src/NuGetGallery/NuGetGallery.csproj | 3 +- src/NuGetGallery/Services/IReportService.cs | 2 + .../Services/IStatisticsService.cs | 2 + src/NuGetGallery/Web.config | 4 - .../AuthenticationServiceFacts.cs | 5 +- ...orceSslWhenAuthenticatedMiddlewareFacts.cs | 147 +++++++++++++ .../AuthenticationControllerFacts.cs | 14 +- .../Controllers/PackagesControllerFacts.cs | 15 +- .../RequireRemoteHttpsAttributeFacts.cs | 193 ------------------ .../Filters/RequireSslAttributeFacts.cs | 126 ++++++++++++ tests/NuGetGallery.Facts/Framework/Fakes.cs | 10 + .../NuGetGallery.Facts.csproj | 5 +- .../TestUtils/OwinAssert.cs | 88 ++++++++ .../TestUtils/OwinTestExtensions.cs | 37 ++++ 26 files changed, 600 insertions(+), 280 deletions(-) create mode 100644 src/NuGetGallery/Authentication/ForceSslWhenAuthenticatedMiddleware.cs rename src/NuGetGallery/{RequireRemoteHttpsAttribute.cs => Filters/RequireSslAttribute.cs} (69%) create mode 100644 tests/NuGetGallery.Facts/Authentication/ForceSslWhenAuthenticatedMiddlewareFacts.cs delete mode 100644 tests/NuGetGallery.Facts/Filters/RequireRemoteHttpsAttributeFacts.cs create mode 100644 tests/NuGetGallery.Facts/Filters/RequireSslAttributeFacts.cs create mode 100644 tests/NuGetGallery.Facts/TestUtils/OwinAssert.cs create mode 100644 tests/NuGetGallery.Facts/TestUtils/OwinTestExtensions.cs diff --git a/src/NuGetGallery/App_Start/AppActivator.cs b/src/NuGetGallery/App_Start/AppActivator.cs index a51b4f83f0..21d66280c8 100644 --- a/src/NuGetGallery/App_Start/AppActivator.cs +++ b/src/NuGetGallery/App_Start/AppActivator.cs @@ -1,5 +1,7 @@ using System; using System.Data.Entity; +using System.Security.Claims; +using System.Web.Helpers; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; @@ -30,6 +32,8 @@ public static class AppActivator public static void PreStart() { + AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; + NinjectPreStart(); ElmahPreStart(); GlimpsePreStart(); @@ -101,7 +105,6 @@ private static void AppPostStart() GlobalFilters.Filters.Add(new ElmahHandleErrorAttribute()); GlobalFilters.Filters.Add(new ReadOnlyModeErrorFilter()); GlobalFilters.Filters.Add(new AntiForgeryErrorFilter()); - GlobalFilters.Filters.Add(new RequireRemoteHttpsAttribute() { OnlyWhenAuthenticated = true }); ValueProviderFactories.Factories.Add(new HttpHeaderValueProviderFactory()); } diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index b9ef03c525..be07b9a465 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -5,6 +5,7 @@ using Owin; using Ninject; using Microsoft.Owin; +using Microsoft.Owin.Logging; using Microsoft.Owin.Extensions; using Microsoft.Owin.Diagnostics; using Microsoft.Owin.Security; @@ -21,13 +22,23 @@ public class OwinStartup // This method is auto-detected by the OWIN pipeline. DO NOT RENAME IT! public static void Configuration(IAppBuilder app) { - + // Get config var config = Container.Kernel.Get(); var cookieSecurity = config.Current.RequireSSL ? CookieSecureOption.Always : CookieSecureOption.Never; + // Configure logging + app.SetLoggerFactory(new DiagnosticsLoggerFactory()); + + if (config.Current.RequireSSL) + { + // Put a middleware at the top of the stack to force the user over to SSL + // if authenticated. + app.UseForceSslWhenAuthenticated(config.Current.SSLPort); + } + app.UseCookieAuthentication(new CookieAuthenticationOptions() { - AuthenticationType = AuthenticationTypes.Cookie, + AuthenticationType = AuthenticationTypes.Password, AuthenticationMode = AuthenticationMode.Active, CookieHttpOnly = true, CookieSecure = cookieSecurity, diff --git a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs index ae490a47d4..bf35e9708d 100644 --- a/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs +++ b/src/NuGetGallery/Authentication/ApiKeyAuthenticationHandler.cs @@ -60,17 +60,18 @@ protected override Task AuthenticateCoreAsync() if (!String.IsNullOrEmpty(apiKey)) { // Get the user - var user = Auth.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); - if (user != null) + var authUser = Auth.Authenticate(CredentialBuilder.CreateV1ApiKey(apiKey)); + if (authUser != null) { // Set the current user - Context.Set(Constants.CurrentUserOwinEnvironmentKey, user); + Context.Set(Constants.CurrentUserOwinEnvironmentKey, authUser); return Task.FromResult( new AuthenticationTicket( - new ClaimsIdentity(new[] { - new Claim(NuGetClaims.ApiKey, apiKey) - }), + AuthenticationService.CreateIdentity( + authUser.User, + AuthenticationTypes.ApiKey, + new Claim(NuGetClaims.ApiKey, apiKey)), new AuthenticationProperties())); } else diff --git a/src/NuGetGallery/Authentication/AuthenticationService.cs b/src/NuGetGallery/Authentication/AuthenticationService.cs index 8b732ca845..e2df02c538 100644 --- a/src/NuGetGallery/Authentication/AuthenticationService.cs +++ b/src/NuGetGallery/Authentication/AuthenticationService.cs @@ -48,7 +48,7 @@ public virtual AuthenticatedUser Authenticate(string userNameOrEmail, string pas Trace.Information("Password validation failed: " + userNameOrEmail); return null; } - + var passwordCredentials = user .Credentials .Where(c => c.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) @@ -215,7 +215,7 @@ public virtual User GeneratePasswordResetToken(string usernameOrEmail, int expir if (expirationInMinutes < 1) { throw new ArgumentException( - "Token expiration should give the user at least a minute to change their password", "tokenExpirationMinutes"); + "Token expiration should give the user at least a minute to change their password", "expirationInMinutes"); } var user = FindByUserNameOrEmail(usernameOrEmail); @@ -241,13 +241,24 @@ public virtual User GeneratePasswordResetToken(string usernameOrEmail, int expir return user; } - public static ClaimsIdentity CreateIdentity(User user, string authenticationType) + public static ClaimsIdentity CreateIdentity(User user, string authenticationType, params Claim[] additionalClaims) { + var claims = Enumerable.Concat(new[] { + new Claim(ClaimsIdentity.DefaultNameClaimType, user.Username), + new Claim(ClaimTypes.AuthenticationMethod, authenticationType), + + // Needed for anti-forgery token, also good practice to have a unique identifier claim + new Claim(ClaimTypes.NameIdentifier, user.Username) + }, user.Roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r.Name))); + + if (additionalClaims.Length > 0) + { + claims = Enumerable.Concat(claims, additionalClaims); + } + ClaimsIdentity identity = new ClaimsIdentity( - claims: Enumerable.Concat(new[] { - new Claim(ClaimsIdentity.DefaultNameClaimType, user.Username) - }, user.Roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r.Name))), - authenticationType: authenticationType, + claims, + authenticationType, nameType: ClaimsIdentity.DefaultNameClaimType, roleType: ClaimsIdentity.DefaultRoleClaimType); return identity; @@ -257,7 +268,7 @@ private void ReplaceCredentialInternal(User user, Credential credential) { // Find the credentials we're replacing, if any var creds = user.Credentials - .Where(cred => + .Where(cred => // If we're replacing a password credential, remove ALL password credentials (credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase) && cred.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) || @@ -280,7 +291,7 @@ private Credential FindMatchingCredential(Credential credential) .Include(u => u.User.Roles) .Where(c => c.Type == credential.Type && c.Value == credential.Value) .ToList(); - + if (results.Count == 0) { return null; @@ -314,8 +325,8 @@ private User FindByUserNameOrEmail(string userNameOrEmail) { var allMatches = users .Where(u => u.EmailAddress == userNameOrEmail) - .Take(2) - .ToList(); + .Take(2) + .ToList(); if (allMatches.Count == 1) { diff --git a/src/NuGetGallery/Authentication/AuthenticationTypes.cs b/src/NuGetGallery/Authentication/AuthenticationTypes.cs index 361f45f4a5..7caa64e046 100644 --- a/src/NuGetGallery/Authentication/AuthenticationTypes.cs +++ b/src/NuGetGallery/Authentication/AuthenticationTypes.cs @@ -7,8 +7,7 @@ namespace NuGetGallery.Authentication { public static class AuthenticationTypes { - public static readonly string Cookie = "cookie"; + public static readonly string Password = "password"; public static readonly string ApiKey = "apikey"; - public static readonly string ResolvedUser = "resolveduser"; } } \ No newline at end of file diff --git a/src/NuGetGallery/Authentication/ForceSslWhenAuthenticatedMiddleware.cs b/src/NuGetGallery/Authentication/ForceSslWhenAuthenticatedMiddleware.cs new file mode 100644 index 0000000000..19ac1fd4d3 --- /dev/null +++ b/src/NuGetGallery/Authentication/ForceSslWhenAuthenticatedMiddleware.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using NuGetGallery.Configuration; +using Owin; + +namespace NuGetGallery.Authentication +{ + public class ForceSslWhenAuthenticatedMiddleware : OwinMiddleware + { + public static readonly string DefaultCookieName = ".ForceSsl"; + + private readonly ILogger _logger; + public string CookieName { get; private set; } + public int SslPort { get; private set; } + + public ForceSslWhenAuthenticatedMiddleware(OwinMiddleware next, IAppBuilder app, string cookieName, int sslPort) + : base(next) + { + CookieName = cookieName; + SslPort = sslPort; + _logger = app.CreateLogger(); + } + + public override async Task Invoke(IOwinContext context) + { + // Check for the SSL Cookie + var forceSSL = context.Request.Cookies[CookieName]; + if (!String.IsNullOrEmpty(forceSSL) && !context.Request.IsSecure) + { + _logger.WriteVerbose("Force SSL Cookie found. Redirecting to SSL"); + // Presence of the cookie is all we care about, value is ignored + context.Response.Redirect(new UriBuilder(context.Request.Uri) + { + Scheme = Uri.UriSchemeHttps, + Port = SslPort + }.Uri.AbsoluteUri); + } + else + { + // Invoke the rest of the pipeline + await Next.Invoke(context); + + var cookieOptions = new CookieOptions() { HttpOnly = true }; + if (context.Authentication.AuthenticationResponseGrant != null) + { + _logger.WriteVerbose("Auth Grant found, writing Force SSL cookie"); + // We're granting new authentication, so drop a force ssl cookie + // for later. + context.Response.Cookies.Append(CookieName, "true", cookieOptions); + } + else if (context.Authentication.AuthenticationResponseRevoke != null) + { + _logger.WriteVerbose("Auth Revoke found, removing Force SSL cookie"); + // We're revoking authentication, so remove the force ssl cookie + context.Response.Cookies.Delete(CookieName, new CookieOptions() + { + HttpOnly = true + }); + } + } + } + } +} + +namespace Owin { + using NuGetGallery.Authentication; + + public static class ForceSslWhenAuthenticatedExtensions + { + public static IAppBuilder UseForceSslWhenAuthenticated(this IAppBuilder self) + { + return UseForceSslWhenAuthenticated( + self, + ForceSslWhenAuthenticatedMiddleware.DefaultCookieName, + 443); + } + + public static IAppBuilder UseForceSslWhenAuthenticated(this IAppBuilder self, int sslPort) + { + return UseForceSslWhenAuthenticated( + self, + ForceSslWhenAuthenticatedMiddleware.DefaultCookieName, + sslPort); + } + + public static IAppBuilder UseForceSslWhenAuthenticated(this IAppBuilder self, string cookieName) + { + return UseForceSslWhenAuthenticated( + self, + cookieName, + 443); + } + + public static IAppBuilder UseForceSslWhenAuthenticated(this IAppBuilder self, string cookieName, int sslPort) + { + return self.Use(typeof(ForceSslWhenAuthenticatedMiddleware), self, cookieName, sslPort); + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Controllers/ApiController.cs b/src/NuGetGallery/Controllers/ApiController.cs index 7b8f893f51..993568f9c3 100644 --- a/src/NuGetGallery/Controllers/ApiController.cs +++ b/src/NuGetGallery/Controllers/ApiController.cs @@ -162,6 +162,7 @@ public virtual Task GetNuGetExe() } [HttpGet] + [RequireSsl] [ApiAuthorize] [ActionName("VerifyPackageKeyApi")] public virtual ActionResult VerifyPackageKey(string id, string version) @@ -187,18 +188,18 @@ public virtual ActionResult VerifyPackageKey(string id, string version) } [HttpPut] + [RequireSsl] [ApiAuthorize] [ActionName("PushPackageApi")] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual Task CreatePackagePut() { return CreatePackageInternal(); } [HttpPost] + [RequireSsl] [ApiAuthorize] [ActionName("PushPackageApi")] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual Task CreatePackagePost() { return CreatePackageInternal(); @@ -257,9 +258,9 @@ private async Task CreatePackageInternal() } [HttpDelete] + [RequireSsl] [ApiAuthorize] [ActionName("DeletePackageApi")] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual ActionResult DeletePackage(string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); @@ -273,7 +274,7 @@ public virtual ActionResult DeletePackage(string id, string version) if (!package.IsOwner(user)) { return new HttpStatusCodeWithBodyResult( - HttpStatusCode.Forbidden, String.Format(CultureInfo.CurrentCulture, Strings.ApiKeyNotAuthorized, "delete")); + HttpStatusCode.Forbidden, Strings.ApiKeyNotAuthorized); } PackageService.MarkPackageUnlisted(package); @@ -282,9 +283,9 @@ public virtual ActionResult DeletePackage(string id, string version) } [HttpPost] + [RequireSsl] [ApiAuthorize] [ActionName("PublishPackageApi")] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] public virtual ActionResult PublishPackage(string id, string version) { var package = PackageService.FindPackageByIdAndVersion(id, version); @@ -346,7 +347,6 @@ protected internal virtual INupkg ReadPackageFromRequest() } [HttpGet] - [ApiAuthorize] [ActionName("PackageIDs")] public virtual ActionResult GetPackageIds(string partialId, bool? includePrerelease) { @@ -359,7 +359,6 @@ public virtual ActionResult GetPackageIds(string partialId, bool? includePrerele } [HttpGet] - [ApiAuthorize] [ActionName("PackageVersions")] public virtual ActionResult GetPackageVersions(string id, bool? includePrerelease) { @@ -372,7 +371,6 @@ public virtual ActionResult GetPackageVersions(string id, bool? includePrereleas } [HttpGet] - [ApiAuthorize] [ActionName("StatisticsDownloadsApi")] public virtual async Task GetStatsDownloads(int? count) { diff --git a/src/NuGetGallery/Controllers/AuthenticationController.cs b/src/NuGetGallery/Controllers/AuthenticationController.cs index 3b0fcdcebd..632af9584d 100644 --- a/src/NuGetGallery/Controllers/AuthenticationController.cs +++ b/src/NuGetGallery/Controllers/AuthenticationController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Web.Mvc; using NuGetGallery.Authentication; +using NuGetGallery.Filters; namespace NuGetGallery { @@ -22,7 +23,7 @@ public AuthenticationController( AuthService = authService; } - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] + [RequireSsl] public virtual ActionResult LogOn(string returnUrl) { // I think it should be obvious why we don't want the current URL to be the return URL here ;) @@ -39,7 +40,7 @@ public virtual ActionResult LogOn(string returnUrl) [HttpPost] [ValidateAntiForgeryToken] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] + [RequireSsl] public virtual ActionResult SignIn(SignInRequest request, string returnUrl) { // I think it should be obvious why we don't want the current URL to be the return URL here ;) @@ -67,13 +68,13 @@ public virtual ActionResult SignIn(SignInRequest request, string returnUrl) return View(); } - AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Cookie); + AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Password); return SafeRedirect(returnUrl); } [HttpPost] [ValidateAntiForgeryToken] - [RequireRemoteHttps(OnlyWhenAuthenticated = false)] + [RequireSsl] public virtual ActionResult Register(RegisterRequest request, string returnUrl) { // I think it should be obvious why we don't want the current URL to be the return URL here ;) @@ -104,7 +105,7 @@ public virtual ActionResult Register(RegisterRequest request, string returnUrl) return View(); } - AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Cookie); + AuthService.CreateSession(OwinContext, user.User, AuthenticationTypes.Password); if (RedirectHelper.SafeRedirectUrl(Url, returnUrl) != RedirectHelper.SafeRedirectUrl(Url, null)) { diff --git a/src/NuGetGallery/Controllers/PackagesController.cs b/src/NuGetGallery/Controllers/PackagesController.cs index 15ddee9edc..f39166e081 100644 --- a/src/NuGetGallery/Controllers/PackagesController.cs +++ b/src/NuGetGallery/Controllers/PackagesController.cs @@ -34,7 +34,6 @@ public partial class PackagesController : AppController private readonly IPackageFileService _packageFileService; private readonly ISearchService _searchService; private readonly IUploadFileService _uploadFileService; - private readonly IUserService _userService; private readonly IEntitiesContext _entitiesContext; private readonly IIndexingService _indexingService; private readonly ICacheService _cacheService; @@ -43,7 +42,6 @@ public partial class PackagesController : AppController public PackagesController( IPackageService packageService, IUploadFileService uploadFileService, - IUserService userService, IMessageService messageService, ISearchService searchService, IAutomaticallyCuratePackageCommand autoCuratedPackageCmd, @@ -57,7 +55,6 @@ public PackagesController( { _packageService = packageService; _uploadFileService = uploadFileService; - _userService = userService; _messageService = messageService; _searchService = searchService; _autoCuratedPackageCmd = autoCuratedPackageCmd; diff --git a/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs b/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs index 08af78befd..1f65bd6e72 100644 --- a/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs +++ b/src/NuGetGallery/Diagnostics/NullDiagnosticsSource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -7,6 +8,7 @@ namespace NuGetGallery.Diagnostics { public class NullDiagnosticsSource : IDiagnosticsSource { + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Type is immutable")] public static readonly NullDiagnosticsSource Instance = new NullDiagnosticsSource(); private NullDiagnosticsSource() { } diff --git a/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs index 134f3021ac..5604246831 100644 --- a/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs +++ b/src/NuGetGallery/Filters/ApiAuthorizeAttribute.cs @@ -7,7 +7,7 @@ namespace NuGetGallery.Filters { - public class ApiAuthorizeAttribute : AuthorizeAttribute + public sealed class ApiAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { diff --git a/src/NuGetGallery/RequireRemoteHttpsAttribute.cs b/src/NuGetGallery/Filters/RequireSslAttribute.cs similarity index 69% rename from src/NuGetGallery/RequireRemoteHttpsAttribute.cs rename to src/NuGetGallery/Filters/RequireSslAttribute.cs index fe33220f56..e05b5a9866 100644 --- a/src/NuGetGallery/RequireRemoteHttpsAttribute.cs +++ b/src/NuGetGallery/Filters/RequireSslAttribute.cs @@ -7,29 +7,20 @@ using Ninject.Web.Mvc.Filter; using NuGetGallery.Configuration; -namespace NuGetGallery +namespace NuGetGallery.Filters { // This code is identical to System.Web.Mvc except that we allow for working in localhost environment without https and we force authenticated users to use SSL [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] - public sealed class RequireRemoteHttpsAttribute : FilterAttribute, IAuthorizationFilter + public sealed class RequireSslAttribute : FilterAttribute, IAuthorizationFilter { private IAppConfiguration _configuration; - private IFormsAuthenticationService _formsAuth; - + public IAppConfiguration Configuration { get { return _configuration ?? (_configuration = Container.Kernel.Get()); } set { _configuration = value; } } - public IFormsAuthenticationService FormsAuthentication - { - get { return _formsAuth ?? (_formsAuth = Container.Kernel.Get()); } - set { _formsAuth = value; } - } - - public bool OnlyWhenAuthenticated { get; set; } - public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext == null) @@ -38,19 +29,12 @@ public void OnAuthorization(AuthorizationContext filterContext) } var request = filterContext.HttpContext.Request; - if (Configuration.RequireSSL && !request.IsSecureConnection && ShouldForceSSL(filterContext.HttpContext)) + if (Configuration.RequireSSL && !request.IsSecureConnection) { HandleNonHttpsRequest(filterContext); } } - private bool ShouldForceSSL(HttpContextBase context) - { - return !OnlyWhenAuthenticated || // If OnlyWhenAuthenticated == false, then we should force SSL - context.Request.IsAuthenticated || // If Authenticated, force SSL (we should already be on SSL, since the cookie is secure, but just in case...) - FormsAuthentication.ShouldForceSSL(context); // If the "ForceSSL" cookie is present. - } - private void HandleNonHttpsRequest(AuthorizationContext filterContext) { // only redirect for GET requests, otherwise the browser might not propagate the verb and request diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index 41a8789683..e811d29908 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -324,6 +324,7 @@ + @@ -907,7 +908,7 @@ - + diff --git a/src/NuGetGallery/Services/IReportService.cs b/src/NuGetGallery/Services/IReportService.cs index 6f39862ea7..8816d66bb4 100644 --- a/src/NuGetGallery/Services/IReportService.cs +++ b/src/NuGetGallery/Services/IReportService.cs @@ -1,4 +1,5 @@  +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using NuGetGallery.Infrastructure; @@ -11,6 +12,7 @@ public interface IReportService public class NullReportService : IReportService { + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Type is immutable")] public static readonly NullReportService Instance = new NullReportService(); private NullReportService() { } diff --git a/src/NuGetGallery/Services/IStatisticsService.cs b/src/NuGetGallery/Services/IStatisticsService.cs index 1d1da6e9a6..1d44c5b0ce 100644 --- a/src/NuGetGallery/Services/IStatisticsService.cs +++ b/src/NuGetGallery/Services/IStatisticsService.cs @@ -1,6 +1,7 @@  using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -26,6 +27,7 @@ public interface IStatisticsService public class NullStatisticsService : IStatisticsService { + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Type is immutable")] public static readonly NullStatisticsService Instance = new NullStatisticsService(); private NullStatisticsService() { } diff --git a/src/NuGetGallery/Web.config b/src/NuGetGallery/Web.config index 8e6b957e6e..f6b296a77a 100644 --- a/src/NuGetGallery/Web.config +++ b/src/NuGetGallery/Web.config @@ -39,10 +39,6 @@ - - -