Active Directory UserPrincipal issue - c#

I have some code which gets a user based on the current context and this happens in the Page_Load() method of my application.
var context = new PrincipalContext(ContextType.Domain, "dc", "DC=domain,DC=com", "user", "password");
UserPrincipal user = UserPrincipal.FindByIdentity(context, User.Identity.Name);
string Name = user.Name.Trim();
lblName.Text = Name;
This works and the label is correctly showing the logged in user (Windows Authentication).
However, another part of my code, on button press, does some looping through a datagrid which contains other usernames and uses a similar method to obtain information about those users.
I've utilized labels on the page to display relevant user information but one of those labels seems to change based on which user it is dealing with in the loop, when it actually needs to only ever refer to the currently logged in user.
Is there a way around this? I thought by having separate methods that essentially do the same thing, I would get different objects. The return values are different so I'm not sure how this is happening.
Relevant code below with some comments to explain or point out where the code is not relevant (sending emails and dealing with exceptions - all removed).
public string getUserEmail()
{
var context = new PrincipalContext(ContextType.Domain, "dc", "DC=domain,DC=com", "user", "password"); ;
UserPrincipal user = UserPrincipal.FindByIdentity(context, User.Identity.Name);
string email = user.EmailAddress;
return email;
}
public string getOtherUserEmail(string user)
{
user = user.Trim();
var context = new PrincipalContext(ContextType.Domain, "dc", "DC=domain,DC=com", "user", "password");
UserPrincipal u = UserPrincipal.FindByIdentity(context, user);
string email2= u.EmailAddress;
return email2;
}
protected void btnEmail_Click(object sender, EventArgs e)
{
foreach (GridViewRow row in gdView1.Rows)
{
if (row.RowType == DataControlRowType.DataRow)
{
Label FullName = gdView1.Rows[index].FindControl("lblFullName") as Label; // other username from gridview
string FromAddress = getUserEmail(); //logged in user
try
{
string otherName = FullName.Text.ToString();
string ToAddress1 = getOtherUserEmail(FullName); //Other user
//Generate an email message....
smtpClient.Send(mail);
lblMessage.Text = "Email sent!";
}
catch
{ // exception handling
}
}
}
protected void gdView1_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
TableCell cell = gdView1.Rows[e.RowIndex].Cells[2]; //Cell containing "other" user name
Message.Text = cell.Text;
string otherName = cell.Text.ToString();
string ToAddress = getOtherUserEmail(otherName); //Other user
string FromAddress = getUserEmail(); //logged in user
try
{ //email as above
}
catch
{ // exception handling
}
}

Based on what you posted, I would expect you to get different objects as well. Maybe there's something happening in code you haven't included?
On a side note, for the current logged on user you should define a field (call it _loggedOnUser) outside of the Page_Load(). That way all methods have access to it. This will keep your code easier to follow, also, and might even solve your reference problem.
public partial class Page1 {
UserPrincipal _loggedOnUser;
public void Page_Load() {
var context = new PrincipalContext(ContextType.Domain, "dc", "DC=domain,DC=com", "user", "password");
_loggedOnUser= UserPrincipal.FindByIdentity(context, User.Identity.Name);
string Name = _loggedOnUser.Name.Trim();
lblName.Text = Name;
}
}
Then you can refer to it in the rest of your class, and you won't have to waste time going back to Active Directory in your loop.
Thus, this line: string FromAddress = getUserEmail();
can be changed to string FromAddress = _loggedOnUser.EmailAddress;
and you avoid another expensive call to AD to get info you already have.

Related

Get the last login date for Active Directory from a list of organizational unit users in c#

I have my code done that reads all users from a organizational unit, now I need to be able to read the list all those users and display the last login of each user.
Here is the code that reads correctly all the users I need
private void btn_LastLoginUser_Click(object sender, EventArgs e)
{
GroupUsers();
}
public void GroupUsers()
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "mydomain", "OU=myEmployees,DC=myCompany,DC=com");
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Enabled = true;
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach (var found in srch.FindAll())
{
lst_Users.Items.Add(found);
}
}
I need help reading that data and now display the last login date for each user and populate it to my listbox.
FunCoder,
This should allow you to get the Last Login Date and Time from the DC. It will convert the user found to an authenticated principle which contains a number of different properties that might help with other information you might be looking for in the future.
foreach(var found in srch.FindAll())
{
var auth = found as AuthenticablePrincipal;
if(auth != null)
{
var Name = auth.Name;
var LastLogin = auth.LastLogon;
}
}

Role-based Security Authorization in web froms using Identity 2.0

I've seen not hundred but THOUSANDS of example where from scratch to complete examples with MVC identity 2.0 are done but not a single one with bloody web forms and the one which are present are not even worth while just very basic.
I'm working on an application where I've three roles, user,admin,superUser and all these are in AspNetRoles table because I'm using identity 2.0. now when I create a user I also assign that user one of these roles too.
before this role and stuff I've worked on customize roles system as we use to do on desktop applications.
so here I tried all the links and articles written on CodeProject about form authentication and all that what we can do in web.config but nothing was helpful
Please take a look at this screen shot http://prntscr.com/6ca09i you might get a little idea what I mean by that.
My C# code on register is
protected void btnSubmit_Click(object sender, EventArgs e)
{
//owin entity
var userStore = new UserStore<IdentityUser>();
userStore.Context.Database.Connection.ConnectionString =
System.Configuration.ConfigurationManager
.ConnectionStrings["GCR"].ConnectionString;
var manager = new UserManager<IdentityUser>(userStore);
//string userInfor;// = new UserInformation();
// check if the url contains an id perameter
if (!String.IsNullOrWhiteSpace(Request.QueryString["id"]))
{
var id = Convert.ToInt32(Request.QueryString["id"]);
var userInfo = new UserInformation
{
Email = txtEmail.Text,
FirstName = txtFirstName.Text,
LastName = txtLastName.Text,
AddressLine1 = txtAddressLine1.Text,
AddressLine2 = txtAddressLine2.Text,
City = txtCity.Text,
State = ddlState.SelectedValue,
ZipCode = Convert.ToInt32(txtZip.Text),
PhoneNumber = txtPhone.Text,
RoleId = Convert.ToInt32(ddlRole.SelectedValue)
and here is my registration page where I am assigning the roles which are not actually getting assigned http://prntscr.com/6ca1xi
Now please tell me how can I create role based app where in a single folder we have different files which User with different role can get access
Please I'd already wasted my two days on Identity I have no wich to waste more time on it
This is how you are going to properly deal with this
var userInfo = new UserInformation
{
Email = txtEmail.Text,
FirstName = txtFirstName.Text,
LastName = txtLastName.Text,
AddressLine1 = txtAddressLine1.Text,
AddressLine2 = txtAddressLine2.Text,
City = txtCity.Text,
State = ddlState.SelectedValue,
ZipCode = Convert.ToInt32(txtZip.Text),
PhoneNumber = txtPhone.Text,
RoleId = ddlRole.SelectedValue
Fist your role should be some text value because it does not take role as id
after saving this object or model above `db.saveChanges();
you in the end are going to add this role to aspnetroles table and how you are going to do that is very simple just a single line
// add role to the user which is created right now
manager.AddToRole(userInfo.GUID, ddlRole.Text.Trim());
The first argument is the id of the user and the second one is the dropdown in which you are selecting the user roles and you can also do that role exist stuff in it. now how you are going to check this one page load is very simple
and its just like this
#region page load
if (!IsPostBack)
{
if (User.IsInRole("admin") || User.IsInRole("superuser"))
{
}
else
{
string unAuthorizedRedirect = WebConfigurationManager.AppSettings["UnAuthorizedRedirect"];
Response.Redirect("~/" + unAuthorizedRedirect);
}
}
#endregion
I hope this helps you completely
I got the roles from aspnetroles table and this is how I got those
var context = new ApplicationDbContext();
var roleStore = new RoleStore<IdentityRole>(context);
var roleMgr = new RoleManager<IdentityRole>(roleStore);
if (User.IsInRole("admin"))
{
//come here
}

Why does my LDAP redirect script fail with one user?

Here is my login script. I have two users 20002143, and 60000027 the first will authenticate and redirect as scripted the second will authenticate and stay on the same page. I cannot figure out why. I have inserted breakpoints all over this code and it tells me it authenticates but then why is the login page just reloading:
public bool AuthenticateActiveDirectory(string Domain, string EmployeeID, string Password)
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + Domain, EmployeeID, Password);
object nativeObject = entry.NativeObject;
return true;
}
catch
{
return false;
}
}
protected void btnLogin_Click(object sender, EventArgs e)
{
string Domain = "domain.local";
string EmployeeID = txtUserID.Text;
string Password = txtPassword.Text;
string ADStatus = null;
if (AuthenticateActiveDirectory(Domain, EmployeeID, Password) == true)
{
ADStatus = "Success";
Session["SessionLoginStatus"] = ADStatus;
Response.Redirect("Intro.aspx?redir=Success&userid=" + EmployeeID);
}
else
{
ADStatus = "Failure";
Session["SessionLoginStatus"] = ADStatus;
lblADError.Visible = true;
lblADError.Text = "Please Check Your Password<br />";
}
}
Here is the other part of this. If I use the URL to login falsely with the second empID
https://www.site.com/folder/intro.aspx?redir=Success&userid=60000027
it will redirect me back to the login but this makes no sense also since Intro.aspx login check is scripted like this.
//checking to see if user logged in
if ((ADStatus == "Success") && (UserID.Length >= 8))
{
}
if ((ADStatus == null) || (UserID.Length < 8))
{
ADStatus = "Failure";
Session["SessionLoginStatus"] = ADStatus;
Response.Redirect("https://www.site.com/folder/userlogin.aspx");
}
else if (ADStatus == "Failure")
{
ADStatus = "Failure";
Session["SessionLoginStatus"] = ADStatus;
Response.Redirect("https://www.site.com/folder/userlogin.aspx");
}
What am I leaving out or doing wrong here?
Edited
The issue was caused by logic on the second page which tossed the user back to the login if the user's ID did not match a list of users defined in a SQL table.
In no way, shape or forum are you authenticating users on LDAP server. In fact, your authentication method will never return false because entry will never be null and the constructor for DirectoryEntry will never throw an exception.
With that being said, check that you're typing in the credentials correctly (because I know you're not). Look at your in statement for the redirect. Since your authenticate method always returns true, it will try to redirect every and anyone however fail because you're using invalid credentials.
So, how about you actually authenticate users using PrincipalContext. Here is a little explaining between the two with this DirectoryEntry question.
By the way, you're going to want to use the bool returned by PrincipalContext.ValidateUser call.

Fetching user details from AD

In my module I have a TextBox, Button and ListBox. Whenever button is pressed, I have to fetch the details of the user (fetched from the text box) and I need to add in the list box. This working now.
My problem is that if I press the button, the first time nothing happens - but if the same button is pressed a second time, I get the value of the user name in the list box.
Please help me identify the actual cause of why its not working on the first click. As of now I am clicking two times for every record.
protected void Add_Click(object sender, EventArgs e)
{
try
{
DirectoryEntry entry1 = new DirectoryEntry("LDAP://corp.com", "123456", "password");
DirectorySearcher dSearch1 = new DirectorySearcher(entry1);
dSearch1.Filter = "(&(objectClass=user)(samAccountName=" + Additional_ID.Text + "))";
dSearch1.PropertiesToLoad.Add("cn");
dSearch1.PropertiesToLoad.Add("mail");
SearchResult result = dSearch1.FindOne();
if (result.Properties["mail"] != null)
{
ListItem L = new ListItem();
L.Text = "" + result.Properties["cn"][0].ToString();
L.Value = "" + result.Properties["mail"][0].ToString();
New_Users_list.Items.Add(L);
Additional_ID.Text = "";
}
entry1.Close();
dSearch1.Dispose();
}
catch
{
Additional_ID.Text = "Enter Valid ID";
}
}
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Determine current domain controller programmatically

I need to query current domain controller, probably primary to change user password.
(P)DC name should be fully qualified, i.e. DC=pdc,DC=example,DC=com (how to properly name such notation?)
How can it be done using C#?
To retrieve the information when the DomainController exists in a Domain in which your machine doesn't belong, you need something more.
DirectoryContext domainContext = new DirectoryContext(DirectoryContextType.Domain, "targetDomainName", "validUserInDomain", "validUserPassword");
var domain = System.DirectoryServices.ActiveDirectory.Domain.GetDomain(domainContext);
var controller = domain.FindDomainController();
We are using something like this for our internal applications.
Should return something like DC=d,DC=r,DC=ABC,DC=com
public static string RetrieveRootDseDefaultNamingContext()
{
String RootDsePath = "LDAP://RootDSE";
const string DefaultNamingContextPropertyName = "defaultNamingContext";
DirectoryEntry rootDse = new DirectoryEntry(RootDsePath)
{
AuthenticationType = AuthenticationTypes.Secure;
};
object propertyValue = rootDse.Properties[DefaultNamingContextPropertyName].Value;
return propertyValue != null ? propertyValue.ToString() : null;
}
(requires System.DirectoryServices.AccountManagement.dll):
using (var context = new System.DirectoryServices.AccountManagement.PrincipalContext(ContextType.Domain))
{
string server = context.ConnectedServer; // "pdc.examle.com"
string[] splitted = server.Split('.'); // { "pdc", "example", "com" }
IEnumerable<string> formatted = splitted.Select(s => String.Format("DC={0}", s));// { "DC=pdc", "DC=example", "DC=com" }
string joined = String.Join(",", formatted); // "DC=pdc,DC=example,DC=com"
// or just in one string
string pdc = String.Join(",", context.ConnectedServer.Split('.').Select(s => String.Format("DC={0}", s)));
}
If you are looking to interact the Active Directory, you shouldn't have to know where the FSMO roles are for the most part. If you want to change the AD topology from your program (I wouldn't), look at the DomainController class.
If you want to change a user password, you can invoke those actions on the User object, and Active Directory will make sure that the changes are properly replicated.
copied from http://www.rootsilver.com/2007/08/how-to-change-a-user-password
public static void ChangePassword(string userName, string oldPassword, string newPassword)
{
string path = "LDAP://CN=" + userName + ",CN=Users,DC=demo,DC=domain,DC=com";
//Instantiate a new DirectoryEntry using an administrator uid/pwd
//In real life, you'd store the admin uid/pwd elsewhere
DirectoryEntry directoryEntry = new DirectoryEntry(path, "administrator", "password");
try
{
directoryEntry.Invoke("ChangePassword", new object[]{oldPassword, newPassword});
}
catch (Exception ex) //TODO: catch a specific exception ! :)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("success");
}

Categories