I'm creating users for test purposes, using :
string username = ...
string password = ...
string email = "******** not a valid email address! *********";
MembershipUser NewUser = Membership.CreateUser(userName, password, email, "no question", "no answer", true, out createStatus);
if (NewUser == null)
{
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
throw new Exception("There already exists a user with this username.");
case MembershipCreateStatus.InvalidEmail:
throw new Exception("There email address you provided in invalid.");
case MembershipCreateStatus.InvalidPassword:
throw new Exception("The password you provided is invalid. It must be seven characters long.");
default:
throw new Exception("There was an unknown error; the user account was NOT created.");
}
}
When this gets executed, a new user will get created, it doesn't fail with NewUser==null, MembershipCreateStatus.InvalidEmail, which is what I would expect.
Any idea why?
Here's the membership section from config if that has a bearing, although I don't see how:
<membership defaultProvider="myProvider">
<providers>
<add
name="myProvider"
applicationName="/"
connectionStringName="myconnectionsString"
enablePasswordRetrieval="true"
enablePasswordReset="true"
passwordFormat="Clear"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
minRequiredNonalphanumericCharacters="0"
type="System.Web.Security.SqlMembershipProvider"
/>
</providers>
</membership>
Thanks in advance!
From reflector this is the validation on email in SqlMembershipProvider
if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, false, 0x100))
{
status = MembershipCreateStatus.InvalidEmail;
return null;
}
internal static bool ValidateParameter(ref string param, bool checkForNull, bool checkIfEmpty, bool checkForCommas, int maxSize)
{
if (param == null)
{
return !checkForNull;
}
param = param.Trim();
return (((!checkIfEmpty || (param.Length >= 1)) && ((maxSize <= 0) || (param.Length <= maxSize))) && (!checkForCommas || !param.Contains(",")));
}
Looks like it doesn't care abut valid email or not, just that something is provided. You'll want to handle it from your client or override the SqlMembershipProvider.
Related
I'm building an ASP.Net MVC web application, which uses Umbraco7, to replace an old WebForms website.
The old WebForms site uses Basic Authentication on some sections of the site (specified at the directory level in IIS), which specify a default Windows domain with its own Active Directory. The browser requests the user ID and password on the appropriate pages, and the code behind retrieves the user information using the System.Web.UI.Page.User.Identity property.
I would like to provide a similar experience on the new Umbraco site.
Examples of MVC sites using Basic Authentication specify the authentication and default domain as attributes on the Controller methods, http://www.asp.net/mvc/tutorials/older-versions/security/authenticating-users-with-windows-authentication-cs.
Umbraco doesn't appear to provide individual controller methods for its content pages, and I've not found any Umbraco authentication examples that use Basic Authentication and rely on the browser to retrieve the credentials.
Is it possible to use Basic Authentication on an Umbraco content page and retrieve the credentials using the browser?
Updated Answer:
I stumbled on this question several months after posting the original answer, Combining Forms Authentication and Basic Authentication. I have not tested this solution, since that ship has sailed, but it looks promising.
Original Answer:
From what I can tell, the answer to this question is "no". You cannot modify the header of a Umbraco content page, so you can't tell the browser to authenticate itself against a given LDAP server.
However, I was able to use forms authentication to behave in the same way (authenticate against Active Directory and authorize against my database). Below I've included all the code needed to get this authentication to work in Umbraco.
Login Page
The login page is just an Umbraco View with the following Partial View and Surface Control added to it using #Html.Action("MemberUmbLogin", "MemberUmbLoginSurface")
#model CustomUmbraco.Models.MemberUmbLoginModel
#if (User.Identity.IsAuthenticated)
{
<p>Logged in: #User.Identity.Name</p>
<p>#Html.ActionLink("Log out", "MemberUmbLogout", "MemberUmbLoginSurface")</p>
}
else
{
using (Html.BeginUmbracoForm("MemberUmbLogin", "MemberUmbLoginSurface"))
{
#Html.EditorFor(x => Model)
<input type="submit" />
}
<p>#TempData["Status"]</p>
}
Login Model
public class MemberUmbLoginModel
{
public string Username { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Surface Controller
public class MemberUmbLoginSurfaceController : SurfaceController
{
//
// GET: /MemberUmbLogin/
[HttpGet]
[ActionName("MemberUmbLogin")]
public ActionResult MemberUmbLoginGet()
{
return PartialView("MemberUmbLogin", new MemberUmbLoginModel());
}
[HttpGet]
public ActionResult MemberUmbLogout()
{
Session.Clear();
FormsAuthentication.SignOut();
return Redirect("/");
}
[HttpPost]
[ActionName("MemberUmbLogin")]
public ActionResult MemberUmbLoginPost(MemberUmbLoginModel model)
{
string returnUrl = GetValidReturnUrl(Request.UrlReferrer);
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && !String.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToCurrentUmbracoPage();
}
TempData["Status"] = "Invalid username or password";
return RedirectToCurrentUmbracoPage();
}
private static String GetValidReturnUrl(Uri uri)
{
string returnUrl = null;
if (uri != null && !String.IsNullOrWhiteSpace(uri.PathAndQuery) && uri.PathAndQuery.StartsWith("/") &&
!uri.PathAndQuery.StartsWith("//") && !uri.PathAndQuery.StartsWith("/\\"))
{
returnUrl = uri.PathAndQuery;
}
return returnUrl;
}
}
I'm using a custom MembershipProvider with the standard Umbraco Role Provider. I rely on the MembershipProvider to update the roles for a member based on my non-Umbraco database whenever they log in. The MembershipProvider then updates the member with the appropriate groups.
Note: Because I'm using Umbraco's role provider, I need to add the roles from my non-Umbraco database to Umbraco as "Member Groups".
Web.config
<!-- Membership Provider -->
<membership defaultProvider="CustomMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="CustomMembershipProvider" type="CustomUmbraco.MembershipProviders.CustomMembershipProvider" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="NetIDAlias" passwordFormat="Hashed" />
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed" />
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
</providers>
</membership>
<!-- Role Provider -->
<roleManager enabled="true" defaultProvider="UmbracoRoleProvider">
<providers>
<clear />
<add name="UmbracoRoleProvider" type="Umbraco.Web.Security.Providers.MembersRoleProvider" />
</providers>
</roleManager>
MembershipProvider
public class CustomMembershipProvider : MembershipProvider
{
private String GetDomainFreeName(String fullName)
{
return fullName.Contains("\\") ? fullName.Substring(fullName.IndexOf("\\") + 1) : fullName;
}
public override bool ValidateUser(string username, string password)
{
DirectoryEntry directoryEntry = new DirectoryEntry(ConfigurationManager.ConnectionStrings["LDAPConnection"].ConnectionString, username, password);
DirectorySearcher searcher = new DirectorySearcher(directoryEntry);
String domainFreeName = GetDomainFreeName(username);
searcher.Filter = String.Format("(&(objectClass=user)(SAMAccountName={0})(!msExchUserAccountControl=2))", domainFreeName);
SearchResult result;
try
{
result = searcher.FindOne();
}
catch (COMException)
{
return false; // authentication failed
}
if (result != null)
{
NotReallyARoleProvider provider = new NotReallyARoleProvider();
provider.UpdateUserRoles(domainFreeName);
Member m = Member.GetMemberFromLoginName(domainFreeName);
Member.AddMemberToCache(m);
return true;
}
return false;
}
}
UpdateUserRoles methods
public void UpdateUserRoles(String username)
{
var groups = this.GetRolesForUser(username); // this is the method that gets the roles for your user.
this.RemoveUsersFromRoles(new[] { username }, this.GetAllRoles());
this.AddUsersToRoles(new[] { username }, groups);
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
foreach (String username in usernames)
{
Member m = Member.GetMemberFromLoginName(username);
if (m == null)
{
m = Member.MakeNew(username, MemberType.GetByAlias("Member"), new User(0));
m.LoginName = username;
}
roleNames.ForEach(group => m.AddGroup(MemberGroup.GetByName(group).Id));
m.Save(true);
}
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
foreach (String username in usernames)
{
Member m = Member.GetMemberFromLoginName(username);
if (m == null)
{
m = Member.MakeNew(username, MemberType.GetByAlias("Member"), new User(0));
m.LoginName = username;
}
roleNames.ForEach(group => m.RemoveGroup(MemberGroup.GetByName(group).Id));
m.Save(true);
}
}
Finally, the reason I don't use the ActiveDirectoryMembershipProvider is because I couldn't get sufficient permissions to modify accounts.
This solution is far from perfect, but it works for me. If you run into an issue where you log in to a page but it acts as though you're not in the correct group, OR if you remove a member from the Umbraco interface and they still show up when you call Member.GetMemberFromLoginName(username), then you may have to replace your Member saving code in your Provider with the following line once.
ApplicationContext.Current.Services.MemberService.DeleteMembersOfType(MemberType.GetByAlias("Member").Id);
That will clear out any members that are stored in the ethereal repository.
With all this code in place, users can select the groups they want to have access to content pages in the Umbraco backoffice, like normal.
I am using LDAP for User Authentication in MVC.My code goes below as follows:
public ActionResult Login(LoginViewModel model, string returnUrl)
{
bool validation;
try
{
LdapConnection ldc = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
NetworkCredential nc = new NetworkCredential(model.UserName, model.Password, "XXXXXXX");
ldc.Credential = nc;
ldc.AuthType = AuthType.Negotiate;
ldc.Bind(nc); // user has authenticated at this point, as the credentials were used to login to the dc.
validation = true;
return RedirectToAction("Index", "Home");
//validation = true;
}
catch (LdapException)
{
validation = false;
}
return View(model);
}
but i am getting an error as "LDAP server not available"
Web.Config:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="10"/>
</authentication>
<membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
<providers>
<clear />
<add name="AspNetActiveDirectoryMembershipProvider"
type="System.Web.Security.ActiveDirectoryMembershipProvider,System.Web, Version=2.0.0.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="ADConnectionString"
attributeMapUsername="sAMAccountName" />
</providers>
</membership>
<add name="ADConnectionString" connectionString="LDAP://XXXXXXX:389/DC=XXXX,DC=XXXX" />
You did not set the path to the LDAP server (currently it is null)
LdapConnection ldc = new LdapConnection(
new LdapDirectoryIdentifier((string)null, false, false)
);
To debug, get rid of try..catch and see where exactly the error comes from. You might need to verify the path with your network administrator or use any tool like LDAP Browser where you could see if path and credentials would work.
Also, make sure that the way you want to authenticate is correct. If this is an intranet application then it might be that you could setup integrated Windows authentication which will not require any custom login process and can be configured on IIS.
I'm using the AD Membership provider to validate user names and am having issues getting anything other than user#upnDomain.com to work.
Is it possible to get the other username formats to work?
Code
MembershipProvider domainProvider;
domainProvider = Membership.Providers["MyADMembershipProvider"];
if (domainProvider.ValidateUser("zzTest123", "pass"))
{
}
if (domainProvider.ValidateUser(#"PARTNERSGROUP\zzTest123", "pass"))
{
}
if (domainProvider.ValidateUser("zzTest123#company.com", "pass"))
{
}
if (domainProvider.ValidateUser("zzTest123#testfirm.com", "pass"))
{
// this is the UPN and the only one that works.
}
Web.config
<authentication mode="Forms">
<forms loginUrl="~/Account/Login.aspx" name=".ADAuthCookie" timeout="10" />
</authentication>
<membership>
<providers>
<add name="MyADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="TestDomain1ConnectionString" />
</providers>
</membership>
Based on my testing the Membership provider only works with the UPN. To implement support for other types, override ActiveDirectoryMembershipProvider's ValidateUser function and add some variation of the following:
//
// Will validate UPN, shortname only, or domain prefixed (domain\user)
public bool IsAuthenticated( string usr, string pwd)
{
bool authenticated = false;
DirectorySearcher dseSearcher=null;
DirectoryEntry entry = null;
try
{
dseSearcher = new DirectorySearcher();
string rootDSE = dseSearcher.SearchRoot.Path;
entry = new DirectoryEntry(rootDSE, usr, pwd);
object nativeObject = entry.NativeObject;
authenticated = true;
}
catch (DirectoryServicesCOMException cex)
{
//not authenticated; reason why is in cex
}
catch (Exception ex)
{
//not authenticated due to some other exception [this is optional]
}
finally
{
dseSearcher.Dispose();
entry.Dispose();
}
return authenticated;
}
Be aware that the System.DirectoryServices.AccountManagement namespace will only validate the shortname, the UPN, but doesn't appear to validate DOMAIN\Username accounts.
The following code will throw an exception if a username is passed in DOMAIN\Username format
"LdapException: A local error occurred."
var ctx = new PrincipalContext(ContextType.Domain);
if (ctx.ValidateCredentials(username,password , ContextOptions.Negotiate))
{
}
Please help me for this issue.
I have used asp .net membership. while creating new user using asp .net membership using below code. i am getting membership provoder error. can anyone tell me the solution for
this.
MembershipCreateStatus status;
//MembershipUser u = Membership.CreateUser(username, password, email, question,
// answer, true, out status);
MembershipUser u = Membership.CreateUser(username, password, email, question,
answer, true, out status);
if (u == null)
{
throw new MembershipCreateUserException(GetErrorMessage(status));
}
return u;
i have properly set web.config file. please tell me if i am missing anything. here is my web.config file membeship tag. and my database is mysql :
add name="MySQLMembershipProvider"
enablePasswordRetrieval="true"
autogenerateschema="false"
type="MySql.Web.Security.MySQLMembershipProvider, MySql.Web, Version=6.3.5.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"
connectionStringName="LocalMySqlServer"
applicationName="/"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="Clear"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
writeExceptionsToEventLog="false"
This is my error :MembershipCreateStatus.ProviderError
MembershipCreateStatus status;
Membership.CreateUser(username, password, email, question,
answer, true, out status);
if (status == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(username, false);
// Redirect to page
}
else
{
//get the error message here
return ErrorCodeToString(createStatus);
}
// if you get to here, throw an exception!
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// See http://go.microsoft.com/fwlink/?LinkID=177550 for
// a full list of status codes.
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
return "User name already exists. Please enter a different user name.";
//add the rest of the error codes here....
I'm using the ASP.Net SqlMembershipProvider to manage my users. Here is my config:
<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add
name="SqlProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="SiteDatabase"
applicationName="WPR"
minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="Hashed" />
</providers>
</membership>
My problem is this: when I call Membership.CreateUser to create new users, the password is stored in the DB in hashed format with a salt - which is all good. However, when I call Membership.ChangePassword in an admin function, it is storing the password in plain text format. I really cannot understand this behaviour, since the config clearly says "Hashed" and creating a new user creates a hashed password.
Within the ChangePassword() method of the default ASPMembership provider, the password format for an existing user is retrieved from the database and is the format used to encode a new password for an existing user, and not the password format that is set in web.config, which may now specify a different format to use. You can see this for yourself by downloading the source code for the default providers.
My question is then, is the password being stored in clear text for a user who already had a password stored in clear text? You can check this easily by checking the value of the PasswordFormat field for the user in table aspnet_Membership. The values are:
Clear = 0,
Hashed = 1,
Encrypted = 2,
EDIT :
if you need to hash clear passwords yourself, the framework code may come in handy
// generate a salt
public string GenerateSalt()
{
byte[] buf = new byte[16];
(new RNGCryptoServiceProvider()).GetBytes(buf);
return Convert.ToBase64String(buf);
}
// hashes the password, using the supplied salt
public string HashPassword(string pass, string salt)
{
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bAll = new byte[bSalt.Length + bIn.Length];
byte[] bRet = null;
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
// this assumes a Hashed password (PasswordFormat = 1)
HashAlgorithm s = HashAlgorithm.Create( Membership.HashAlgorithmType );
bRet = s.ComputeHash(bAll);
return Convert.ToBase64String(bRet);
}
now you just need to pull all records from the database where the PasswordFormat = 0, run them through a console app to hash the password and save the salt, hashed password to the database, as well as update the PasswordFormat field to 1
To do that I use a console application. Directly in the database I change PasswordFormat in the aspnet_Membership table. Then I change the password to the same with the help of ResetPassword and ChangePassword methods. Also if the user was locked out I unlock it before changing the password and then lock again. In the app.config file I have connection strings for database model and membership provider, as well as the membership provider definition.
Note, that if the old password does not satisfies your latest password requirements in the provider settings then the ChangePassword method would fail.
The code is the following:
static void Main(string[] args)
{
using (var db = new MyEntities())
{
var usersToFix = db.aspnet_Membership.Where(x => x.PasswordFormat == 0).ToList();
foreach (var userToFix in usersToFix)
{
userToFix.PasswordFormat = 1;
var password = userToFix.Password;
var passwordQuestion = userToFix.PasswordQuestion;
var passwordAnswer = userToFix.PasswordAnswer;
var lastLockoutDate = userToFix.LastLockoutDate;
db.SaveChanges();
var user = Membership.GetUser(userToFix.UserId);
bool locked = user.IsLockedOut;
if (locked)
{
user.UnlockUser();
}
var resetPassword = user.ResetPassword();
user.ChangePassword(resetPassword, password);
user.ChangePasswordQuestionAndAnswer(password, passwordQuestion, passwordAnswer);
if (locked)
{
userToFix.IsLockedOut = true;
userToFix.LastLockoutDate = lastLockoutDate;
db.SaveChanges();
}
Console.WriteLine("{0} - OK", user.UserName);
}
}
Console.WriteLine("Done!");
Console.ReadKey();
}
Russ's solution probably works, but there's a simpler way if all your existing users have either clear or encrypted passwords. Set up 2 sql membership providers in your web.config, one using clear (or encryped) passwords and another using hashed. Then execute this code somewhere within your web application:
void ConvertPasswordsToHashed()
{
var clearProvider = Membership.Providers["SqlProvider"];
var hashedProvider = Membership.Providers["SqlProvider_Hashed"];
int dontCare;
if (clearProvider == null || hashedProvider == null) return;
var passwords = clearProvider.GetAllUsers(0, int.MaxValue, out dontCare)
.Cast<MembershipUser>().ToDictionary(u => u.UserName, u => u.GetPassword());
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings[0].ConnectionString))
{
conn.Open();
using (var cmd = new SqlCommand("UPDATE [aspnet_Membership] SET [PasswordFormat]=1", conn))
cmd.ExecuteNonQuery();
}
foreach (var entry in passwords)
{
var resetPassword = hashedProvider.ResetPassword(entry.Key, null);
hashedProvider.ChangePassword(entry.Key, resetPassword, entry.Value);
}
}