Searched SO and Everywhere else, including the .net developers guide to directory services programming book - no luck.
I am trying to create a simple password reset web page that allows the user to change their password. The change password portion of the code is working fine. For the users I would also like to display when their current password will expire next.
Using the sample code from the book mentioned above I was able to get all of the code setup however, the attribute that is returned is always equal to Long.MinValue and hence cannot be inverted to a positive number, plus this means it did not find the proper domain setting.
Does anyone have sample code or references for getting the password expiration in a Windows 2008 or R2 domain environment where password policies can be different for each user?
Updated to include code
Constructor that gets the policy object:
public PasswordExpires()
{
//Get Password Expiration
Domain domain = Domain.GetCurrentDomain();
DirectoryEntry root = domain.GetDirectoryEntry();
using (domain)
using (root)
{
this.policy = new DomainPolicy(root);
}
}
Domain Policy Constructor:
public DomainPolicy(DirectoryEntry domainRoot)
{
string[] policyAttributes = new string[] {
"maxPwdAge", "minPwdAge", "minPwdLength",
"lockoutDuration", "lockOutObservationWindow",
"lockoutThreshold", "pwdProperties",
"pwdHistoryLength", "objectClass",
"distinguishedName"
};
//we take advantage of the marshaling with
//DirectorySearcher for LargeInteger values...
DirectorySearcher ds = new DirectorySearcher(
domainRoot,
"(objectClass=domainDNS)",
policyAttributes,
SearchScope.Base
);
SearchResult result = ds.FindOne();
//do some quick validation...
if (result == null)
{
throw new ArgumentException(
"domainRoot is not a domainDNS object."
);
}
this.attribs = result.Properties;
}
Call this method to get the password expiration:
public TimeSpan MaxPasswordAge
{
get
{
string val = "maxPwdAge";
if (this.attribs.Contains(val))
{
long ticks = GetAbsValue(
this.attribs[val][0]
);
if (ticks > 0)
return TimeSpan.FromTicks(ticks);
}
return TimeSpan.MaxValue;
}
}
Code fails here because it cannot convert Long.MinValue, which it should not be in the first place
private long GetAbsValue(object longInt)
{
return Math.Abs((long)longInt);
}
Here is the debugger output and values. According to the MSDN Site the overflow exception is caused from the minvalue. My numbers match the examples for minvalue.
Screenshot http://www.brentpabst.com/capture.png
Password expiration times are stored such that if lastPwdSet - maxPwdAge < DateTime.UtcNow is true, then your password is expired. So if you set your password a week ago, but the password will expire in 10 days, the left side will be (DateTime.UtcNow - 7) - (-10), or DateTime.UtcNow - 7 + 10, or DateTime.UtcNow + 3, which is not less than DateTime.UtcNow, so your password won't be expired.
This means that setting maxPwdAge to long.MinValue will effectively give you thousands of years before your password expires. So if you are getting long.MinValue, your policy says that passwords won't expire. You should just look for that value and treat it properly, possibly like this:
private long GetAbsValue(object longInt) // poorly named
{
long val = (long)longInt;
if (val == long.MinValue)
return long.MaxValue;
return Math.Abs((long)longInt);
}
Also, I should point out that the values are stored in 100-nanosecond increments, so you should expect values in the billions.
Related
I am trying to get the user account control properties using library Novell.Directory.Ldap in ASP .NET Core 5. When I search the users attributes I found the attribute name userAccountControl which is set to some number. After searching solution I am able to find:
bool isUserActive = false;
bool userMustChangePassword = false;
bool passwordNeverExpires = false;
bool passwordCannotBeChanged = false;
var flags = Convert.ToInt32(attributeSet.GetAttribute("userAccountControl").StringValue);
isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled
if ((flags == 66048)) //65536+512
{
passwordNeverExpires = true; //2. Password never expires property
}
long value = Convert.ToInt64(attributeSet.GetAttribute("pwdLastSet").StringValue);
if (value == 0)
{
userMustChangePassword = true; //3. User must change password at next login
}
But I am not able to figure out how to get the User cannot change password and if the account is locked properties? Or how can I compare the binary value like 0x0040? Please help
Edit:
I tried the steps given by #Gabriel Luci in https://www.gabescode.com/active-directory/2019/07/25/nt-security-descriptors.html and tried following code:
var act = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
ADsSecurityUtility secUtility = new ADsSecurityUtility();
IADsSecurityDescriptor convertAttrToSD = (IADsSecurityDescriptor)secUtility.ConvertSecurityDescriptor(act, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID);
var byteArray = (byte[])secUtility.ConvertSecurityDescriptor(
convertAttrToSD,
(int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID,
(int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW
);
var security = new CommonSecurityDescriptor(true, true, byteArray, 0);
If I check the security it shows
I am not getting where to look user cannot change the password settings?
Edit 2:
According to #Gabriel Luci updated answer, it worked for me like this:
var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true, new byte[] { 48, 3, 2, 1, 7 }));
var getNtSecurityByteValue=attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, getNtSecurityByteValue, 0);
var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");
foreach (var ace in security.DiscretionaryAcl)
{
if(ace.GetType().Name == "ObjectAce")
{
ObjectAce objAce = (ObjectAce)ace;
if (objAce.AceType == AceType.AccessDeniedObject && objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword)
{
cannotChangePassword = true;
break;
}
}
}
The userAccountControl value is a bit flag, meaning that every bit in the binary representation of the number is an "on" or "off" depending on if it's a 1 or 0. So the decimal value is meaningless.
You are already checking the value properly when you're checking if it's enabled:
isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled
Likewise, you should do the same when checking any of the other flags. The value of each is listed in the documentation.
When you're checking if the password is set to never expire, you're comparing the decimal value, which won't always give you a correct answer. Instead, check the bit value:
passwordNeverExpires = Convert.ToBoolean(flags & 0x10000);
Similar for account is locked:
var accountLocked = Convert.ToBoolean(flags & 0x0010);
For the user cannot change password setting, unfortunately that's more difficult and requires reading the permissions on the user account, which I have never done using the Novell.Directory.Ldap library. But I can try to point you in the right direction.
The account permissions are in the nTSecurityDescriptor attribute. Read this issue about how to get the byte array from that attribute: How to read/set NT-Security-Descriptor attributes?
I wrote an article about how to get the byte array into a usable format: Active Directory: Handling NT Security Descriptor attributes.
Then you'll be looking for two permissions that get added when the 'User cannot change password' checkbox is checked:
Deny Change Password to 'Everyone'
Deny Change Password to 'SELF'
You can probably get away with only looking for #2.
Update: I finally tried this out for myself. I had never used the Novell.Directory.Ldap library before, so this was new to me.
With the help of this answer, I figured out that you need to set an LDAP control for it to return the nTSecurityDescriptor attribute at all:
var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true
, new byte[] {48, 3, 2, 1, 7}));
Once you retrieve the object, you can check the permissions like this:
var byteValue = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, byteValue, 0);
var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");
var cannotChangePassword = false;
foreach (var ace in (security.DiscretionaryAcl)) {
if (ace is ObjectAce objAce && objAce.AceType == AceType.AccessDeniedObject
&& objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword) {
cannotChangePassword = true;
break;
}
}
The GUID of the User-Change-Password permission is taken from the Control Access Rights documentation.
Notice that you don't need to use IADsSecurityDescriptor, and thus you don't need a reference to Interop.ActiveDs. This is because we're given the value as a byte array already.
I am using c# with mvc.
I used this code to generate token and generated successfully. but after generate token when join channel using .join() it return DYNAMIC_KEY_EXPIRED.
I Used "AgoraRTCSDK-3.1.0.js"
I used https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/csharp to generate dynamic token
If any one has experience on Agora.io, please help me.
Sample code is..
AccessToken token = new AccessToken(apiKey, appCertificate, channelName, "0");
token.addPrivilege(Privileges.kJoinChannel, _expiredTs);
token.addPrivilege(Privileges.kPublishAudioStream, _expiredTs);
token.addPrivilege(Privileges.kPublishVideoStream, _expiredTs);
string strToken = token.build();
public string build()
{
this._messageRawContent = Utils.pack(this.message);
this._signature = generateSignature(_appCertificate
, _appId
, _channelName
, _uid
, _messageRawContent);
this._crcChannelName = Crc32CAlgorithm.Compute(this._channelName.GetByteArray());
this._crcUid = Crc32CAlgorithm.Compute(this._uid.GetByteArray());
PackContent packContent = new PackContent(_signature, _crcChannelName, _crcUid, this._messageRawContent);
byte[] content = Utils.pack(packContent);
return getVersion() + this._appId + Utils.base64Encode(content);
}
Whenever you generate a token for Agora applications, you need to keep in mind that the expiration time is calculated as a timestamp (time since 1970) so you need to ensure the the expiration time is set to the currentTime + expirationTimeInSeconds.
In the above example you are passing the expiration time as 0 which generates a token that is already considered expired.
consider using:
// set a expiration time of 1 hour in seconds
let expireTime = 3600;
// calculate current time in seconds
const currentTime = Math.floor(Date.now() / 1000);
// calculate privilege expire time
const privilegeExpireTime = currentTime + expireTime;
I wrote the following code to expire all the cookies in my request. But along with expiring them, I need to set the domain property for some of the cookies explicitly. I was wondering if there is a better way to do the same...
protected void Page_Load(object sender, EventArgs e)
{
Session.Abandon();
HttpCookie cookie;
string cookieName;
int cookieCount = Request.Cookies.Count;
for (int i = 0; i < cookieCount; i++)
{
cookieName = Request.Cookies[i].Name;
cookie = new HttpCookie(cookieName);
cookie.Expires = DateTime.Now.AddDays(-1);
if (string.Compare(cookieName, "cookie1") == 0
|| string.Compare(cookieName, "cookie2") == 0
|| string.Compare(cookieName, "cookie3") == 0
|| string.Compare(cookieName, "cookie4") == 0)
{
cookie.Domain = ".mydomain.com";
}
Response.Cookies.Add(cookie);
}
Response.Redirect("my redirect link");
}
I am new to C# so not sure if there is any other better way to set the domain value to the selected list of cookies only. I am from scripting background, basically from tcl and there usually I can simply check if the given item is in the list or not like:
if ($item in $list){
// Do something
}
which is similar to performing lsearch.
Anyways, the reason I don't like the logical operators in if is because in future i might have more cookies in the list to perform the same action and I don't want to keep stuffing the if condition checks.
I can probably do the same here as tcl like have a list of all the cookie names and then in for loop check for each cookie name in the list, but not sure how expensive it is.
Any comments would be appreciated.
You use new HttpCookie to create a new cookie. Therefor the domain of the existing Cookie is not copied. You'd be better off updating the existing cookie. For example at: Remove and delete all cookies of my ASP NET c-sharp application
Copied the code below from the url above:
foreach (string key in Request.Cookies.AllKeys)
{
HttpCookie c = Request.Cookies(key);
c.Expires = Now.AddMonths(-1);
Response.AppendCookie(c);
}
You can invalidate a Cookie by setting the expires date to a date in the past.
I'd like to query some user attribute in our AD, but on a specific DC, in C#.
We've a few dozens of DCs and I suspect that there is some replication issue between them. I'd like to make a cleanup of unused accounts, to which I'd like to use the last logon time attribute and I'd like to query this on all DCs one bye one (I know this is a bit like brute forcing, however I don't intended to do such a thing too often) so I can see if the most recent value is up-to date or not.
I had the code to query all the DCs:
Domain TestDomain = Domain.GetCurrentDomain();
Console.WriteLine("Number of found DCs in the domain {0}", TestDomain.DomainControllers.Count);
foreach (DomainController dc in TestDomain.DomainControllers)
{
Console.WriteLine("Name: " + dc.Name);
///DO STUFF
}
And I also found help to construct the code that can query a user from AD:
PrincipalContext context = new PrincipalContext(ContextType.Domain, "test.domain.com");
string userName = "testusername";
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
Console.WriteLine(user.LastLogon.Value.ToString());
Console.ReadKey();
And here I stuck. Now I'd like to get the user's last logon timestamp from all DCs.
In the past I already deleted accidentaly account that seemed to be unused for a long time (check on only one DC), than it turned out the user use it every day so the info came from the DC was not synced.
I'm aware that the most reasonable action would be to review what cause this incorrect sync phenomena, however in my current status that would take ages and probably ended without any finding...
Thanks in advance for any constructive response/comment!
While the accepted answer was a good kick in the right direction, I found it to ultimately not produce the expected result: As hinted in the answer, "lastLogon" is not replicated between controllers, whereas the attribute "lastLogonTimeStamp" is replicated (but it is, on the other hand, not guaranteed to be more than 14 days wrong, cf. this answer).
Now, rather confusingly, UserPrincipal.LastLogon refers not to the unreplicated but precise "lastLogon" but to the replicated but imprecise "lastLogonTimeStamp", and I found that by running the code in the accepted answer, all produced DateTimes were equal and wrong.
Instead, inspired by this answer, I found that in order to find the most recent logon date for a user with a given sAMAccountName (which you can easily extend to search for all users), I would have to do something like the following:
public DateTime? FindLatestLogonDate(string username)
{
var logons = new List<DateTime>();
DomainControllerCollection domains = Domain.GetCurrentDomain().DomainControllers;
foreach (DomainController controller in domains)
{
using (var directoryEntry = new DirectoryEntry($"LDAP://{controller.Name}"))
{
using (var searcher = new DirectorySearcher(directoryEntry))
{
searcher.PageSize = 1000;
searcher.Filter = $"((sAMAccountName={username}))";
searcher.PropertiesToLoad.AddRange(new[] { "lastLogon" });
foreach (SearchResult searchResult in searcher.FindAll())
{
if (!searchResult.Properties.Contains("lastLogon")) continue;
var lastLogOn = DateTime.FromFileTime((long)searchResult.Properties["lastLogon"][0]);
logons.Add(lastLogOn);
}
}
}
}
return logons.Any() ? logons.Max() : (DateTime?)null;
}
EDIT: After re-reading your question I realised what the problem actually is. You believe you have a replication issue because a user's Last Logon attribute doesnt match on all domain controllers? This is by design! That attribute is domain controller specific and IS NOT REPLICATED. To check the true last logon time of a user you must always query every domain controller to find the latest time!
You are almost there, try this:
public static List<UserPrincipal> GetInactiveUsers(TimeSpan inactivityTime)
{
List<UserPrincipal> users = new List<UserPrincipal>();
using (Domain domain = Domain.GetCurrentDomain())
{
foreach (DomainController domainController in domain.DomainControllers)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainController.Name))
using (UserPrincipal userPrincipal = new UserPrincipal(context))
using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
using (PrincipalSearchResult<Principal> results = searcher.FindAll())
{
users.AddRange(results.OfType<UserPrincipal>().Where(u => u.LastLogon.HasValue));
}
}
}
return users.Where(u1 => !users.Any(u2 => u2.UserPrincipalName == u1.UserPrincipalName && u2.LastLogon > u1.LastLogon))
.Where(u => (DateTime.Now - u.LastLogon) >= inactivityTime).ToList();
}
It won't show people who've never logged in though. If you need that you can probably figure it out.
in my app. there's a log in mechanism which save a cookie with the info of the user who just logged in
private void CreateCookie(LoginEventArgs args)
{
HttpCookie cookie = new HttpCookie("user");
cookie.Values["name"] = args.User_Name;
cookie.Values["id"] = args.ID;
cookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(cookie);
}
on my master page load i perform a check to see if this cookie exists or not :
HttpCookie cookie = Request.Cookies["user"] ;
if( (cookie != null) && (cookie.Value != ""))
{
if (Session["user"] == null)
Login_Passed(this, new LoginEventArgs(cookie.Values["name"].ToString(), int.Parse(cookie.Values["id"])));
}
now if i Log in ( Create A cookie ) , close the browser , and run my app. again the cookie
exists it's values are correct and the user is "automatically" logged in .
if i first redirect to a different content page from the start up content page
the cookies values are also intact ,
the problem is when i redirect back to a different content page a second time,
the master page loads , makes the check
the cookie exists but the values are deleted ...
any ideas on why this happens ?
btw maybe the way i log out could be the reason for this problem :
when i log-out i create a cookie with the same name that expires 1 day ago .
private void Remove_Cookie()
{
HttpCookie cookie = new HttpCookie("user");
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(cookie);
}
in the case iv'e described i don't log-out formally , i just end my app , so this shouldn't
have any effect .
o'k , the problem was unthinkable
special thanks to Peter Bromberg
http://www.eggheadcafe.com/tutorials/aspnet/198ce250-59da-4388-89e5-fce33d725aa7/aspnet-cookies-faq.aspx
in the section of the Article " The Disappearing Cookie "
the author states that if you have a watch on Response.Cookies["cookie_name"]
the browser creates a new empty cookie that overrides your cookie .
i used such a watch which made my cookie loose it's values ,and when i took it off the cookie kept its values.
the moral is DON't WATCH Response.Cookies[" "]
also i read in some other post that if you check
if( Response.Cookies["cookie_name"] != null )
for example it also gets overridden.
To reiterate and build upon what has already been stated (yes, I know this is a 4 year old question) I have found it best to build a utility to handle this - mostly because I want to check that specific cookie often.
This will not touch the Response but only read from the Request.
public static HttpCookie GetCookie(string cookieName)
{
HttpCookie rqstCookie = HttpContext.Current.Request.Cookies.Get(cookieName);
/*** NOTE: it will not be on the Response!
* this will trigger the error noted in the original question and
* create a new, empty cookie which overrides it
*
HttpCookie respCookie = HttpContext.Current.Response.Cookies.Get(cookieName);
*
*/
if (rqstCookie != null && !String.IsNullOrEmpty(rqstCookie.Value))
{
// is found on the Request
return rqstCookie;
}
else
{
return null;
}
}
rule-of-thumb
Always read from the Request and write to the Response.
Thanks eran! this post was exactly what I needed
try the following:
If you are developing on your local machine, put your app on some free web page, so there will be no 'special treatment' because you're in the local host.
If you already are on a web-server, and if the re-directions are between tow different domains, you may want to search google for 'same origin policy' or read this: http://en.wikipedia.org/wiki/Same_origin_policy (the document talks about javascript, but its true also for cookies).
Use the following approach to get a value from cookies:
public string GetValueFromCookies(HttpCookieCollection cookies)
{
if (cookies == null)
{
throw new ArgumentNullException(nameof(cookies));
}
// check the existence of key in the list first
if (Array.IndexOf(cookies.AllKeys, key) < 0)
{
return null;
}
// because the following line adds a cookie with empty value if it's not there
return cookies[key].Value;
}