diff options
Diffstat (limited to 'src/OpenID/OpenIdProviderMvc/Code')
7 files changed, 463 insertions, 0 deletions
diff --git a/src/OpenID/OpenIdProviderMvc/Code/AccountMembershipService.cs b/src/OpenID/OpenIdProviderMvc/Code/AccountMembershipService.cs new file mode 100644 index 0000000..1a5dfd4 --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/AccountMembershipService.cs @@ -0,0 +1,40 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.Security; + + public class AccountMembershipService : IMembershipService { + private MembershipProvider provider; + + public AccountMembershipService() + : this(null) { + } + + public AccountMembershipService(MembershipProvider provider) { + this.provider = provider ?? Membership.Provider; + } + + public int MinPasswordLength { + get { + return this.provider.MinRequiredPasswordLength; + } + } + + public bool ValidateUser(string userName, string password) { + return this.provider.ValidateUser(userName, password); + } + + public MembershipCreateStatus CreateUser(string userName, string password, string email) { + MembershipCreateStatus status; + this.provider.CreateUser(userName, password, email, null, null, true, null, out status); + return status; + } + + public bool ChangePassword(string userName, string oldPassword, string newPassword) { + MembershipUser currentUser = this.provider.GetUser(userName, true /* userIsOnline */); + return currentUser.ChangePassword(oldPassword, newPassword); + } + } +} diff --git a/src/OpenID/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs b/src/OpenID/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs new file mode 100644 index 0000000..6dc210d --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs @@ -0,0 +1,42 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Web.Security; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Provider; + using OpenIdProviderMvc.Models; + + internal class AnonymousIdentifierProvider : PrivatePersonalIdentifierProviderBase { + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousIdentifierProvider"/> class. + /// </summary> + internal AnonymousIdentifierProvider() + : base(Util.GetAppPathRootedUri("anon?id=")) { + } + + /// <summary> + /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. + /// </summary> + /// <param name="localIdentifier">The OP local identifier.</param> + /// <returns>The salt to use in the hash.</returns> + /// <remarks> + /// It is important that this method always return the same value for a given + /// <paramref name="localIdentifier"/>. + /// New salts can be generated for local identifiers without previously assigned salt + /// values by calling <see cref="CreateSalt"/> or by a custom method. + /// </remarks> + protected override byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. + var membership = (ReadOnlyXmlMembershipProvider)Membership.Provider; + string username = User.GetUserFromClaimedIdentifier(new Uri(localIdentifier)); + string salt = membership.GetSalt(username); + return Convert.FromBase64String(salt); + + // If users were encountered without a salt, one could be generated like this, + // and would also need to be saved to the user's account. + //// var newSalt = AnonymousIdentifierProviderBase.GetNewSalt(5); + //// user.Salt = newSalt; + //// return newSalt; + } + } +} diff --git a/src/OpenID/OpenIdProviderMvc/Code/FormsAuthenticationService.cs b/src/OpenID/OpenIdProviderMvc/Code/FormsAuthenticationService.cs new file mode 100644 index 0000000..22db860 --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/FormsAuthenticationService.cs @@ -0,0 +1,21 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.Security; + + public class FormsAuthenticationService : IFormsAuthentication { + public string SignedInUsername { + get { return HttpContext.Current.User.Identity.Name; } + } + + public void SignIn(string userName, bool createPersistentCookie) { + FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); + } + + public void SignOut() { + FormsAuthentication.SignOut(); + } + } +} diff --git a/src/OpenID/OpenIdProviderMvc/Code/IFormsAuthentication.cs b/src/OpenID/OpenIdProviderMvc/Code/IFormsAuthentication.cs new file mode 100644 index 0000000..d4c8a01 --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/IFormsAuthentication.cs @@ -0,0 +1,24 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; +using System.Web.Security; + + /// <summary> + /// An interface that wraps the <see cref="FormsAuthentication"/> type. + /// </summary> + /// <remarks> + /// The FormsAuthentication type is sealed and contains static members, so it is difficult to + /// unit test code that calls its members. The interface and helper class below demonstrate + /// how to create an abstract wrapper around such a type in order to make the AccountController + /// code unit testable. + /// </remarks> + public interface IFormsAuthentication { + string SignedInUsername { get; } + + void SignIn(string userName, bool createPersistentCookie); + + void SignOut(); + } +} diff --git a/src/OpenID/OpenIdProviderMvc/Code/IMembershipService.cs b/src/OpenID/OpenIdProviderMvc/Code/IMembershipService.cs new file mode 100644 index 0000000..94459f8 --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/IMembershipService.cs @@ -0,0 +1,40 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + using System.Web.Security; + + public interface IMembershipService { + /// <summary> + /// Gets the length of the min password. + /// </summary> + int MinPasswordLength { get; } + + /// <summary> + /// Validates the user. + /// </summary> + /// <param name="userName">Name of the user.</param> + /// <param name="password">The password.</param> + /// <returns>Whether the given username and password is correct.</returns> + bool ValidateUser(string userName, string password); + + /// <summary> + /// Creates a new user account. + /// </summary> + /// <param name="userName">Name of the user.</param> + /// <param name="password">The password.</param> + /// <param name="email">The email.</param> + /// <returns>The success or reason for failure of account creation.</returns> + MembershipCreateStatus CreateUser(string userName, string password, string email); + + /// <summary> + /// Changes the password for a user. + /// </summary> + /// <param name="userName">Name of the user.</param> + /// <param name="oldPassword">The old password.</param> + /// <param name="newPassword">The new password.</param> + /// <returns>A value indicating whether the password change was successful.</returns> + bool ChangePassword(string userName, string oldPassword, string newPassword); + } +} diff --git a/src/OpenID/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs b/src/OpenID/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs new file mode 100644 index 0000000..d66573f --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs @@ -0,0 +1,279 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Configuration.Provider; + using System.Security.Permissions; + using System.Web; + using System.Web.Hosting; + using System.Web.Security; + using System.Xml; + + public class ReadOnlyXmlMembershipProvider : MembershipProvider { + private Dictionary<string, MembershipUser> users; + private string xmlFileName; + + // MembershipProvider Properties + public override string ApplicationName { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override bool EnablePasswordRetrieval { + get { return false; } + } + + public override bool EnablePasswordReset { + get { return false; } + } + + public override int MaxInvalidPasswordAttempts { + get { throw new NotSupportedException(); } + } + + public override int MinRequiredNonAlphanumericCharacters { + get { throw new NotSupportedException(); } + } + + public override int MinRequiredPasswordLength { + get { throw new NotSupportedException(); } + } + + public override int PasswordAttemptWindow { + get { throw new NotSupportedException(); } + } + + public override MembershipPasswordFormat PasswordFormat { + get { throw new NotSupportedException(); } + } + + public override string PasswordStrengthRegularExpression { + get { throw new NotSupportedException(); } + } + + public override bool RequiresQuestionAndAnswer { + get { throw new NotSupportedException(); } + } + + public override bool RequiresUniqueEmail { + get { throw new NotSupportedException(); } + } + + // MembershipProvider Methods + public override void Initialize(string name, NameValueCollection config) { + // Verify that config isn't null + if (config == null) { + throw new ArgumentNullException("config"); + } + + // Assign the provider a default name if it doesn't have one + if (string.IsNullOrEmpty(name)) { + name = "ReadOnlyXmlMembershipProvider"; + } + + // Add a default "description" attribute to config if the + // attribute doesn't exist or is empty + if (string.IsNullOrEmpty(config["description"])) { + config.Remove("description"); + config.Add("description", "Read-only XML membership provider"); + } + + // Call the base class's Initialize method + base.Initialize(name, config); + + // Initialize _XmlFileName and make sure the path + // is app-relative + string path = config["xmlFileName"]; + + if (string.IsNullOrEmpty(path)) { + path = "~/App_Data/Users.xml"; + } + + if (!VirtualPathUtility.IsAppRelative(path)) { + throw new ArgumentException("xmlFileName must be app-relative"); + } + + string fullyQualifiedPath = VirtualPathUtility.Combine( + VirtualPathUtility.AppendTrailingSlash(HttpRuntime.AppDomainAppVirtualPath), + path); + + this.xmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath); + config.Remove("xmlFileName"); + + // Make sure we have permission to read the XML data source and + // throw an exception if we don't + FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, this.xmlFileName); + permission.Demand(); + + // Throw an exception if unrecognized attributes remain + if (config.Count > 0) { + string attr = config.GetKey(0); + if (!string.IsNullOrEmpty(attr)) { + throw new ProviderException("Unrecognized attribute: " + attr); + } + } + } + + public override bool ValidateUser(string username, string password) { + // Validate input parameters + if (string.IsNullOrEmpty(username) || + string.IsNullOrEmpty(password)) { + return false; + } + + try { + // Make sure the data source has been loaded + this.ReadMembershipDataStore(); + + // Validate the user name and password + MembershipUser user; + if (this.users.TryGetValue(username, out user)) { + if (user.Comment == password) { // Case-sensitive + // NOTE: A read/write membership provider + // would update the user's LastLoginDate here. + // A fully featured provider would also fire + // an AuditMembershipAuthenticationSuccess + // Web event + return true; + } + } + + // NOTE: A fully featured membership provider would + // fire an AuditMembershipAuthenticationFailure + // Web event here + return false; + } catch (Exception) { + return false; + } + } + + public override MembershipUser GetUser(string username, bool userIsOnline) { + // Note: This implementation ignores userIsOnline + + // Validate input parameters + if (string.IsNullOrEmpty(username)) { + return null; + } + + // Make sure the data source has been loaded + this.ReadMembershipDataStore(); + + // Retrieve the user from the data source + MembershipUser user; + if (this.users.TryGetValue(username, out user)) { + return user; + } + + return null; + } + + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { + // Note: This implementation ignores pageIndex and pageSize, + // and it doesn't sort the MembershipUser objects returned + + // Make sure the data source has been loaded + this.ReadMembershipDataStore(); + + MembershipUserCollection users = new MembershipUserCollection(); + + foreach (KeyValuePair<string, MembershipUser> pair in this.users) { + users.Add(pair.Value); + } + + totalRecords = users.Count; + return users; + } + + public override int GetNumberOfUsersOnline() { + throw new NotSupportedException(); + } + + public override bool ChangePassword(string username, string oldPassword, string newPassword) { + throw new NotSupportedException(); + } + + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { + throw new NotSupportedException(); + } + + public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { + throw new NotSupportedException(); + } + + public override bool DeleteUser(string username, bool deleteAllRelatedData) { + throw new NotSupportedException(); + } + + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { + throw new NotSupportedException(); + } + + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { + throw new NotSupportedException(); + } + + public override string GetPassword(string username, string answer) { + throw new NotSupportedException(); + } + + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { + throw new NotSupportedException(); + } + + public override string GetUserNameByEmail(string email) { + throw new NotSupportedException(); + } + + public override string ResetPassword(string username, string answer) { + throw new NotSupportedException(); + } + + public override bool UnlockUser(string userName) { + throw new NotSupportedException(); + } + + public override void UpdateUser(MembershipUser user) { + throw new NotSupportedException(); + } + + internal string GetSalt(string userName) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. + this.ReadMembershipDataStore(); + return this.users[userName].Email; + } + + // Helper method + private void ReadMembershipDataStore() { + lock (this) { + if (this.users == null) { + this.users = new Dictionary<string, MembershipUser>(16, StringComparer.InvariantCultureIgnoreCase); + XmlDocument doc = new XmlDocument(); + doc.Load(this.xmlFileName); + XmlNodeList nodes = doc.GetElementsByTagName("User"); + + foreach (XmlNode node in nodes) { + // Yes, we're misusing some of these fields. A real app would + // have the right fields from a database to use. + MembershipUser user = new MembershipUser( + Name, // Provider name + node["UserName"].InnerText, // Username + null, // providerUserKey + node["Salt"].InnerText, // Email + string.Empty, // passwordQuestion + node["Password"].InnerText, // Comment + true, // isApproved + false, // isLockedOut + DateTime.Now, // creationDate + DateTime.Now, // lastLoginDate + DateTime.Now, // lastActivityDate + DateTime.Now, // lastPasswordChangedDate + new DateTime(1980, 1, 1)); // lastLockoutDate + + this.users.Add(user.UserName, user); + } + } + } + } + } +}
\ No newline at end of file diff --git a/src/OpenID/OpenIdProviderMvc/Code/Util.cs b/src/OpenID/OpenIdProviderMvc/Code/Util.cs new file mode 100644 index 0000000..6623952 --- /dev/null +++ b/src/OpenID/OpenIdProviderMvc/Code/Util.cs @@ -0,0 +1,17 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + internal static class Util { + internal static Uri GetAppPathRootedUri(string value) { + string appPath = HttpContext.Current.Request.ApplicationPath.ToLowerInvariant(); + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appPath + value); + } + } +} |