Adding an authentication service, and wiring the authenticated user model object into base viewmodel property, and as property of components that implement ICurrentUser. That's done with SecurityFilter and SecurityModule respectively.

--HG--
extra : convert_revision : svn%3A5ff7c347-ad56-4c35-b696-ccb81de16e03/trunk%4039779
This commit is contained in:
loudej
2009-11-12 03:46:14 +00:00
parent 1c8cf1da12
commit 51d756a573
14 changed files with 226 additions and 63 deletions

View File

@@ -1,13 +1,15 @@
using System;
using System.Linq;
using System.Web.Mvc;
using Orchard.Data;
using Orchard.Models;
using Orchard.Notify;
using Orchard.Security;
using Orchard.Users.Models;
using Orchard.Users.ViewModels;
namespace Orchard.Users.Controllers {
public class AdminController : Controller {
public class AdminController : Controller, ICurrentUser {
private readonly IModelManager _modelManager;
private readonly IRepository<UserRecord> _userRepository;
private readonly INotifier _notifier;
@@ -62,6 +64,8 @@ namespace Orchard.Users.Controllers {
_notifier.Information("User information updated");
return RedirectToAction("Edit", new { id });
}
public IUser CurrentUser { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Orchard.Data;
using Orchard.Logging;
using Orchard.Models;
using Orchard.Security;
@@ -7,9 +8,11 @@ using Orchard.Users.Models;
namespace Orchard.Users.Services {
public class MembershipService : IMembershipService {
private readonly IModelManager _modelManager;
private readonly IRepository<UserRecord> _userRepository;
public MembershipService(IModelManager modelManager) {
public MembershipService(IModelManager modelManager, IRepository<UserRecord> userRepository) {
_modelManager = modelManager;
_userRepository = userRepository;
Logger = NullLogger.Instance;
}
@@ -31,7 +34,15 @@ namespace Orchard.Users.Services {
}
public IUser GetUser(string username) {
throw new NotImplementedException();
var userRecord = _userRepository.Get(x => x.UserName == username);
if (userRecord == null) {
return null;
}
return _modelManager.Get(userRecord.Id).As<IUser>();
}
public IUser Identify(string username, string password) {
return GetUser(username);
}
}
}

View File

@@ -1,6 +1,6 @@
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<AdminViewModel>" %>
<%@ Import Namespace="Orchard.Mvc.ViewModels"%>
<%@ Import Namespace="Orchard.Mvc.ViewModels" %>
<div id="doc3" class="yui-t2">
<div id="hd" role="banner">
<div class="yui-g">
@@ -11,14 +11,16 @@
<%= Html.ActionLink("Your Site", "Index", new { Area = "", Controller = "Home" })%>
</div>
<div class="yui-u">
<%--<div id="login">
User: JoWall | <a href="#">Logout</a></div>--%>
<% if (Model.CurrentUser != null) { %>
<div id="login">
User:
<%=Model.CurrentUser.UserName%>
| <%=Html.ActionLink("Logout", "LogOff", "Account", new {}, new{area=""})%></div>
<%} %>
</div>
</div>
</div>
<div id="bd" role="main">
<div id="yui-main">
<div class="yui-b">
<% Html.RenderPartial("Messages", Model.Messages); %>

View File

@@ -4,28 +4,20 @@ using System.Globalization;
using System.Security.Principal;
using System.Web.Mvc;
using System.Web.Security;
using Orchard.Security;
namespace Orchard.Controllers {
[HandleError]
public class AccountController : Controller {
// This constructor is used by the MVC framework to instantiate the controller using
// the default forms authentication and membership providers.
private readonly IAuthenticationService _authenticationService;
private readonly IMembershipService _membershipService;
public AccountController()
: this(null, null) {}
// This constructor is not used by the MVC framework but is instead provided for ease
// of unit testing this type. See the comments at the end of this file for more
// information.
public AccountController(IFormsAuthentication formsAuth, IMembershipServiceShim service) {
FormsAuth = formsAuth ?? new FormsAuthenticationService();
MembershipService = service ?? new AccountMembershipService();
public AccountController(IAuthenticationService authenticationService, IMembershipService membershipService) {
_authenticationService = authenticationService;
_membershipService = membershipService;
}
public IFormsAuthentication FormsAuth { get; private set; }
public IMembershipServiceShim MembershipService { get; private set; }
public ActionResult LogOn() {
return View();
}
@@ -34,11 +26,13 @@ namespace Orchard.Controllers {
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl) {
if (!ValidateLogOn(userName, password)) {
var user = ValidateLogOn(userName, password);
if (!ModelState.IsValid) {
return View();
}
FormsAuth.SignIn(userName, rememberMe);
_authenticationService.SignIn(user, rememberMe);
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl);
}
@@ -48,31 +42,40 @@ namespace Orchard.Controllers {
}
public ActionResult LogOff() {
FormsAuth.SignOut();
_authenticationService.SignOut();
return RedirectToAction("Index", "Home");
}
int MinPasswordLength {
get {
var settings = new MembershipSettings();
_membershipService.ReadSettings(settings);
return settings.MinRequiredPasswordLength;
}
}
public ActionResult Register() {
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["PasswordLength"] = MinPasswordLength;
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(string userName, string email, string password, string confirmPassword) {
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["PasswordLength"] = MinPasswordLength;
if (ValidateRegistration(userName, email, password, confirmPassword)) {
// Attempt to register the user
var createStatus = MembershipService.CreateUser(userName, password, email);
var user = _membershipService.CreateUser(new CreateUserParams(userName, password, email, null, null, true));
if (createStatus == MembershipCreateStatus.Success) {
FormsAuth.SignIn(userName, false /* createPersistentCookie */);
if (user != null) {
_authenticationService.SignIn(user, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else {
ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
ModelState.AddModelError("_FORM", ErrorCodeToString(/*createStatus*/MembershipCreateStatus.ProviderError));
}
}
@@ -82,7 +85,7 @@ namespace Orchard.Controllers {
[Authorize]
public ActionResult ChangePassword() {
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["PasswordLength"] = MinPasswordLength;
return View();
}
@@ -92,14 +95,14 @@ namespace Orchard.Controllers {
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Exceptions result in password not being changed.")]
public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword) {
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["PasswordLength"] = MinPasswordLength;
if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword)) {
return View();
}
try {
if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword)) {
if (true/*MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword)*/) {
return RedirectToAction("ChangePasswordSuccess");
}
else {
@@ -130,11 +133,11 @@ namespace Orchard.Controllers {
if (String.IsNullOrEmpty(currentPassword)) {
ModelState.AddModelError("currentPassword", "You must specify a current password.");
}
if (newPassword == null || newPassword.Length < MembershipService.MinPasswordLength) {
if (newPassword == null || newPassword.Length < MinPasswordLength) {
ModelState.AddModelError("newPassword",
String.Format(CultureInfo.CurrentCulture,
"You must specify a new password of {0} or more characters.",
MembershipService.MinPasswordLength));
MinPasswordLength));
}
if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal)) {
@@ -144,18 +147,19 @@ namespace Orchard.Controllers {
return ModelState.IsValid;
}
private bool ValidateLogOn(string userName, string password) {
private IUser ValidateLogOn(string userName, string password) {
if (String.IsNullOrEmpty(userName)) {
ModelState.AddModelError("username", "You must specify a username.");
}
if (String.IsNullOrEmpty(password)) {
ModelState.AddModelError("password", "You must specify a password.");
}
if (!MembershipService.ValidateUser(userName, password)) {
var user = _membershipService.Identify(userName, password);
if (user == null) {
ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
}
return ModelState.IsValid;
return user;
}
private bool ValidateRegistration(string userName, string email, string password, string confirmPassword) {
@@ -165,11 +169,11 @@ namespace Orchard.Controllers {
if (String.IsNullOrEmpty(email)) {
ModelState.AddModelError("email", "You must specify an email address.");
}
if (password == null || password.Length < MembershipService.MinPasswordLength) {
if (password == null || password.Length < MinPasswordLength) {
ModelState.AddModelError("password",
String.Format(CultureInfo.CurrentCulture,
"You must specify a password of {0} or more characters.",
MembershipService.MinPasswordLength));
MinPasswordLength));
}
if (!String.Equals(password, confirmPassword, StringComparison.Ordinal)) {
ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.");
@@ -219,24 +223,6 @@ namespace Orchard.Controllers {
#endregion
}
public interface IFormsAuthentication {
void SignIn(string userName, bool createPersistentCookie);
void SignOut();
}
public class FormsAuthenticationService : IFormsAuthentication {
#region IFormsAuthentication Members
public void SignIn(string userName, bool createPersistentCookie) {
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}
public void SignOut() {
FormsAuthentication.SignOut();
}
#endregion
}
public interface IMembershipServiceShim {
int MinPasswordLength { get; }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Autofac.Builder;
using Autofac.Integration.Web.Mvc;
using Orchard.Environment;
@@ -24,6 +25,10 @@ namespace Orchard.Mvc {
};
moduleBuilder.RegisterModule(module);
moduleBuilder
.Register(ctx => HttpContext.Current ==null ? null : new HttpContextWrapper(HttpContext.Current))
.As<HttpContextBase>()
.FactoryScoped();
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Orchard.Mvc {
// Now that the request container is known - try to resolve the controller
object controller;
if (container != null &&
container.TryResolve(serviceName, out controller)) {
container.TryResolve(serviceName, out controller, TypedParameter.From(requestContext))) {
return (IController) controller;
}
return base.CreateController(requestContext, controllerName);

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Orchard.Notify;
using Orchard.Security;
namespace Orchard.Mvc.ViewModels {
public class BaseViewModel {
@@ -8,5 +9,6 @@ namespace Orchard.Mvc.ViewModels {
}
public IList<NotifyEntry> Messages { get; set; }
public IUser CurrentUser { get; set; }
}
}

View File

@@ -154,6 +154,11 @@
<Compile Include="Mvc\Routes\RouteExtensions.cs" />
<Compile Include="Mvc\ViewModels\AdminViewModel.cs" />
<Compile Include="Mvc\ViewModels\BaseViewModel.cs" />
<Compile Include="Security\IAuthenticationService.cs" />
<Compile Include="Security\ICurrentUser.cs" />
<Compile Include="Security\Providers\FormsAuthenticationService.cs" />
<Compile Include="Security\SecurityFilter.cs" />
<Compile Include="Security\SecurityModule.cs" />
<Compile Include="UI\Menus\AdminMenuFilter.cs" />
<Compile Include="UI\Navigation\INavigationBuilder.cs" />
<Compile Include="UI\Navigation\INavigationProvider.cs" />

View File

@@ -0,0 +1,9 @@
using System.Web;
namespace Orchard.Security {
public interface IAuthenticationService : IDependency {
void SignIn(IUser user, bool createPersistentCookie);
void SignOut();
IUser Authenticated();
}
}

View File

@@ -0,0 +1,5 @@
namespace Orchard.Security {
public interface ICurrentUser {
IUser CurrentUser { get; set; }
}
}

View File

@@ -10,6 +10,8 @@ namespace Orchard.Security {
IUser CreateUser(CreateUserParams createUserParams);
IUser GetUser(string username);
IUser Identify(string username, string password);
}
public class MembershipSettings {

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using Orchard.Logging;
using Orchard.Models;
using Orchard.Services;
namespace Orchard.Security.Providers {
public class FormsAuthenticationService : IAuthenticationService {
private readonly IClock _clock;
private readonly IModelManager _modelManager;
private readonly HttpContextBase _httpContext;
public FormsAuthenticationService(IClock clock, IModelManager modelManager, HttpContextBase httpContext) {
_clock = clock;
_modelManager = modelManager;
_httpContext = httpContext;
Logger = NullLogger.Instance;
// TEMP: who can say...
ExpirationTimeSpan = TimeSpan.FromHours(6);
}
public ILogger Logger { get; set; }
public TimeSpan ExpirationTimeSpan { get; set; }
public void SignIn(IUser user, bool createPersistentCookie) {
var now = _clock.UtcNow.ToLocalTime();
var userData = Convert.ToString(user.Id);
var ticket = new FormsAuthenticationTicket(
1 /*version*/,
user.UserName,
now,
now.Add(ExpirationTimeSpan),
createPersistentCookie,
userData,
FormsAuthentication.FormsCookiePath);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.Path = FormsAuthentication.FormsCookiePath;
if (FormsAuthentication.CookieDomain != null) {
cookie.Domain = FormsAuthentication.CookieDomain;
}
_httpContext.Response.Cookies.Add(cookie);
}
public void SignOut() {
FormsAuthentication.SignOut();
}
public IUser Authenticated() {
if (!_httpContext.Request.IsAuthenticated || !(_httpContext.User.Identity is FormsIdentity)) {
return null;
}
var formsIdentity = (FormsIdentity)_httpContext.User.Identity;
var userData = formsIdentity.Ticket.UserData;
int userId;
if (!int.TryParse(userData, out userId)) {
Logger.Fatal("User id not a parsable integer");
}
return _modelManager.Get(userId).As<IUser>();
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Orchard.Mvc.Filters;
using Orchard.Mvc.ViewModels;
namespace Orchard.Security {
public class SecurityFilter : FilterProvider, IResultFilter {
private readonly IAuthenticationService _authenticationService;
public SecurityFilter(IAuthenticationService authenticationService) {
_authenticationService = authenticationService;
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult == null)
return;
var baseViewModel = viewResult.ViewData.Model as BaseViewModel;
if (baseViewModel == null)
return;
if (baseViewModel.CurrentUser == null)
baseViewModel.CurrentUser = _authenticationService.Authenticated();
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Autofac.Builder;
namespace Orchard.Security {
public class SecurityModule : Module {
protected override void AttachToComponentRegistration(Autofac.IContainer container, Autofac.IComponentRegistration registration) {
if (typeof(ICurrentUser).IsAssignableFrom(registration.Descriptor.BestKnownImplementationType)) {
registration.Activated += OnActivated;
}
}
static void OnActivated(object sender, Autofac.ActivatedEventArgs e) {
var userContainer = (ICurrentUser)e.Instance;
var authenticationService = e.Context.Resolve<IAuthenticationService>();
userContainer.CurrentUser = authenticationService.Authenticated();
}
}
}