Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
51
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 12

LastLockoutDate — Maps to the lockoutTime attribute on the user class. If question-and- answer-based password reset has been enabled, then the lockout date may also come from the custom attributes that track bad password answer attempts.

Other properties on MembershipUser are either not mapped by default or have default mappings to directory attributes that you can change.

Username — By default, the provider maps this property to the userPrincipalName attribute in the directory. This mapping will work for you if each of your directory users is created with a user principal name. For older directories, though, you may be using the NT4-style SAM account names, in which case you will need to change the mapping for this property. You can change the mapping to the sAMAccountName attribute in this case. Note that if you try to use the provider with an already populated directory, and you are scratching your head wondering why you can’t find any users or successfully validate any credentials, it is probably because your users have SAM account names, but you have not configured the provider to use the sAMAccountName attribute for MembershipUser.Username.

Email — By default, the provider maps this property to the mail attribute. If you want, you can change this mapping to any single-valued attribute on the user class that is of type Unicode String.

PasswordQuestion — This property is not mapped by default to anything in the directory. If you intend to use question-and-answer-based password resets with the provider, there are actually five different attributes that need to be mapped on the user class. The section on “Working with Active Directory” walks you through adding custom attributes to the AD schema and setting up password reset functionality.

Because Active Directory operates in a multimaster environment, some of the properties on MembershipUser cannot be reliably implemented based on directory attributes.

LastActivityDate — This property has no mapping and is not supported by the provider. There is no concept in either AD or ADAM of touching the user object every time something happens. Unlike the SQL providers where different features all update a LastActivityDate column in the database, attempting to engineer a similar approach for AD wasn’t feasible. First, there would be no way for other features such as Profile to reach into a user object in a directory and update an arbitrary field (suddenly you would have System.DirectoryServices code sitting in the middle of the SQL provider code, which would be a bit strange to say the least). Another problem is that for this value to make any sense in a multimaster environment you would have to replicate the field to all of the various domain controllers. Because it isn’t likely that most customers would want to add a custom attribute and then replicate it across their domain infrastructure each and every time the attribute was changed, the decision was made not to support the concept of a last activity date for the provider.

LastLoginDate — Both AD and ADAM store the last logon time for a user using the lastLogon and lastLogonTimestamp attributes, respectively. However, these attributes aren’t replicated across domain controllers, and the property is not available from the global catalog. So, it is very likely that the provider would either get differing values for this property or stale property values in any domain that had at least two domain controllers. Rather than having the provider iterate through all domain controllers in a domain attempting to find the latest value the decision was made to not implement this property.

472

ActiveDirectoryMembershipProvider

If you want to change any of the configurable attribute mappings for the provider, you can do so by using the following configuration attributes in the provider’s <add /> element:

attributeMapUserName — You can use this provider configuration attribute to change which attribute on the user class the provider uses for identifying a user. You can set this to either userPrincipalName (the default) or to sAMAccountName.

attributeMapEmail — If you don’t want to store user’s email addresses in the default mail attribute, you can tell the provider to use a different directory attribute instead. The only restriction is that that the directory attribute must be of type Unicode String.

attributeMapPasswordQuestion — This configuration attribute must be defined for the provider if you set enablePasswordReset to true. The configuration attribute must reference a directory attribute of type Unicode String.

attributeMapPasswordAnswer — This configuration attribute must be defined for the provider if you set enablePasswordReset to true. The configuration attribute must reference a directory attribute of type Unicode String.

attributeMapFailedPasswordAnswerCount — This configuration attribute must be defined for the provider if you set enablePasswordReset to true. The configuration attribute must reference a directory attribute of type Integer.

attributeMapFailedPasswordAnswerTime — This configuration attribute must be defined for the provider if you set enablePasswordReset to true. The configuration attribute must reference a directory attribute of type Large Integer/Interval.

attributeMapFailedPasswordAnswerLockoutTime — This configuration attribute must be defined for the provider if you set enablePasswordReset to true. The configuration attribute must reference a directory attribute of type Large Integer/Interval.

Later on in the “Working with Active Directory” section I walk you through enabling question-and- answer-based password reset, including the necessary configuration steps for extending the schema in the directory.

Along with the directory schema mappings comes a set of default size restrictions on the length of various string properties. With the SQL provider, it is pretty easy to determine length restrictions by just looking in the database at the column definitions. For the AD provider, this is harder to accomplish unless you can look at the actual directory schema. The default length restrictions for various MembershipUser-related properties are shown in the following list. Note though that it is possible for you to edit the AD and ADAM schemas to enforce even shorter size restrictions. If you have done this, the provider will honor the size restrictions defined in your directory’s schema.

Username — If you mapped username to sAMAccountName then your username cannot be longer than 20 characters. This is a hard-coded size restriction from NT4 days. If you mapped username to userPrincipalName, then a username cannot be longer than 64 characters.

Password — As with the SQL provider, the plaintext password for a user cannot be longer than 128 characters.

Comment — The provider only allows comments up to 1024 characters in length. This differs from the SQL provider, where you could basically store the entire English dictionary if you wanted in a user’s Comment property.

473

Chapter 12

Email — A user’s email property cannot be longer than 256 characters.

PasswordQuestion — A user’s password question cannot be longer than 256 characters.

PasswordAnswer — A user’s cleartext password answer cannot be longer than 128 characters. However, the end result of encrypting the password answer also cannot be longer than 128 characters. Because the ActiveDirectoryMembershipProvider always encrypts the password answer using the same encryption method described in Chapter 11 for SqlMembershipProvider, this limits users to around a 42-character long cleartext password answer.

Provider Settings for Search

There are a handful of other custom configuration attributes supported on the provider that deal specifically with how the provider interacts with AD and ADAM.

enableSearchMethods — By default, the provider sets this property to false. You can choose to set it to true to enable the following provider methods: FindUsersByName, FindUsersByEmail, and GetAllUsers. When you carry out LDAP search operations against AD and ADAM the most efficient way to query large numbers of users is through the use of stateful search facilities. For example, if you perform directory searches using the System.DirectoryServices classes you can perform paged searches to limit the amount of processing the directory server incurs during any one query operation. This type of search implies that your code hangs on to an object

(the DirectorySearcher) over the course of moving through multiple pages of results. However, the ActiveDirectoryMembershipProvider is designed for use in stateless web applications. This means after each call to a provider search method, all of the underlying System

.DirectoryServices objects that were used during the search are released. As a result, the provider is not able to take advantage of the paged search facilities in AD and ADAM. This means that if the search methods were allowed by default, it would be possible for a developer to accidentally point the provider at a large directory and then grind the directory servers to a halt by searching through sets of users. For this reason, the search methods on the provider can be enabled or disabled — with the default state being disabled.

clientSearchTimeout — By default, the provider does not set this property. You can set this attribute to the number of seconds you want the provider to wait for a response from any LDAP query it sends to the server. This configuration attribute is used to set the ClientTimeout property on the DirectorySearcher instance that the provider uses internally. Note that this timeout applies to any LDAP search operation that the provider issues and, thus, also applies to methods like UpdateUser or GetUser that need to find a single user object as part of their normal processing.

serverSearchTimeout — By default, the provider does not set this property. You can set this attribute to the number of seconds the directory server should spend performing a single search operation. The configuration attribute is used to set the ServerPageTimeLimit property on the DirectorySearcher instance that the provider uses internally. As with clientSearchTimeout, the value for this configuration attribute will affect any LDAP query that the provider issues and, thus, the configuration setting will affect methods like UpdateUser and GetUser.

As you can see, the area of searching users caused some degree of concern with the feature team. Searching for a specific user wasn’t the problem because that type of operation yields one or no results and involves searching for a single user object in the directory. But performing broad searches has the potential to yield a large number of users, and the problem of mapping the provider’s paging semantics on top of AD’s paging semantics can exacerbate performance issues.

474

ActiveDirectoryMembershipProvider

If you have ever used the DirectorySearcher class, you know that the class also supports a PageSize property that is normally used in conjunction with the timeout properties. However, there is no provider configuration attribute that exposes a page size. Instead, when you run a provider method like FindUsersByName the provider requests results from AD and ADAM in fixed page sizes of 512 entries. Then the provider internally iterates through the results and determines whether any search results in

a 512-entry page also lie within the set of rows that were requested by the calling code. Effectively, the provider has to map the page size and page index parameters on methods like FindUsersByName to the underlying set of pages that the provider is retrieving via the DirectorySearcher class.

Because of this behavior, the clientSearchTimeout and serverSearchTimeout attributes really only apply to each page of 512 search results retrieved by the provider. For example, if you specify a serverSearchTimeout setting of 10 seconds in configuration, and the provider internally needs to retrieve 10 different pages of results from the directory server to complete a method call, the provider can take up to 100 seconds to retrieve all of the data without exceeding the server’s timeout.

The net result of this is that for a single method call to the provider, the provider internally may need to fetch multiple pages of results from the directory server in order to fulfill the request. For this reason, if you choose to enable the search methods on the provider, be sure that you do the following:

Do not call GetAllUsers. This method is going to start with the first user in a directory container and keep on walking through all of the other users. On a large directory, this will be an incredibly expensive method to call.

For FindUsersByName, always specify at least a partial value for the usernameToMatch parameter. This will at least allow the directory server to narrow the set of results based on either the userPrincipalName or sAMAccountName attributes.

For FindUsersByEmail always specify at least a partial value for the emailToMatch parameter. This will allow the directory server to narrow the set of results returned based on the “mail” attribute.

Membership Provider Settings

Because the ActiveDirectoryMembershipProvider inherits from MembershipProvider, it supports many of the same configuration settings as found on the SqlMembershipProvider. However, even though many of the settings are the same, in some cases the way the

ActiveDirectoryMembershipProvider uses the settings will differ.

applicationName — Although you can configure this setting on the provider (and you can retrieve it from the ApplicationName property), it has no effect on the provider’s functionality. The directory scope within which the provider operates is determined solely by the connection string. The provider supports configuring applicationName simply for visual consistency with the SqlMembershipProvider — that is, the configuration looks the same, but that’s about it.

requiresUniqueEmail — If this is set to true, then the provider’s CreateUser and UpdateUser methods will perform a subtree search rooted at the location specified by the connection string and look for any other user objects with a matching value in their mail attribute. This means that the provider is guaranteeing local uniqueness of the email value; the provider does not guarantee that the email value is globally unique in the domain or the forest. Of course, if your connection string is pointed at an AD domain (that is, you have no container specified in your connection string), then the provider will effectively be guaranteeing email uniqueness for that domain because the search will be rooted at the domain’s default naming context.

475

Chapter 12

enablePasswordReset — The default setting is false. If you set this attribute to true, then you must also set requiresQuestionAndAnswer to true, and you must specify the five mapping attributes described earlier so the provider knows where to store bad password answertracking information.

requiresQuestionAndAnswer — The default setting is false. You can actually set this attribute to true without setting enablePasswordReset to true. If requiresQuestionAndAnswer

is set to true, then you must tell the provider the schema mappings in the directory for the password question and answer by using the attributeMapPasswordQuestion and

attributeMapPasswordAnswer attributes. You might require questions and answers in order to start having users enter this information when their accounts are being created, and then at a later point turn on password resets. Alternatively, you could just use the PasswordQuestion property on the MembershipUser object to store some more information about the user (that is, use it as a second property like the Comment property).

minRequiredPasswordLength — By default, this property is set to 7. The provider uses this setting to enforce a minimum password length prior to sending the password down to the directory server. Note that this property setting only adds a layer of password validation on top of the directory’s existing password strength enforcement rules. Regardless of the setting you use for this configuration attribute, a user’s password must always pass the password strength restrictions defined for the directory server.

minRequiredNonalphanumericCharacters — Defaults to requiring one nonalphanumeric character. As with minRequiredPasswordLength this restriction is enforced in addition to whatever password strength restrictions are currently enforced by the directory server.

passwordStrengthRegularExpression — There is no regular expression set by default. If you do set a regular expression for this attribute, the regex is enforced in addition to the password strength restrictions currently enforced by the directory server.

maxInvalidPasswordAttempts — By default, this is set to 5. In the case of the ActiveDirectoryMembershipProvider, the name of this configuration attribute is a little misleading. In reality, the provider always depends on the directory server for dealing with bad password attempts. Because AD and ADAM already have extensive support for tracking bad password attempts and locking out users as a result of too many bad password attempts, this setting only affects bad password answers. If you have enabled question-and-answer-based password reset, then the provider will mark the account as locked out when the number of bad password answer attempts reaches the limit specified in this configuration attribute.

passwordAttemptWindow — Defaults to 10 minutes. The value of this configuration attribute is used by the provider in conjunction with the maxInvalidPasswordAttempts and passwordAnswerAttemptLockoutDuration configuration attributes for tracking bad password answer attempts. Although the name of this attribute is a bit misleading, it has no effect

on what happens when bad passwords are used. The provider always relies on AD and ADAM to handle tracking bad passwords as well as locking users out when too many bad password attempts have occurred.

passwordAnswerAttemptLockoutDuration — Because AD and ADAM have the concept of automatically unlocking a user account after a configurable time period, the ActiveDirectoryMembershipProvider supports the same capability when tracking bad

password answer attempts. By default, this attribute is set to 30 minutes — which is the same default setting used by AD and ADAM for auto-unlocking user accounts that had too many

476

ActiveDirectoryMembershipProvider

bad password attempts. After 30 minutes have passed, the provider will consider a user account unlocked in the case that the account was originally locked out because of too many bad password answer attempts.

Unique Aspects of Provider Functionality

In general, the ActiveDirectoryMembershipProvider’s implementation of MembershipProvider properties and methods matches the functionality described in earlier chapters for the Membership API and the SqlMembershipProvider. However, there are some differences in functionality that you should keep in mind so that you are not surprised when you start working with the provider.

Each of the provider’s methods is listed here with a description of the directory specific functionality that occurs in each method.

CreateUser — You cannot create users with an explicit value for the providerUserKey parameter. If you attempt to create a new user with a non-null providerUserKey, the provider will throw an exception. If the creation was successful the provider returns an instance of ActiveDirectoryMembershipUser — this custom class is discussed further in the next section. If you create a user in AD, and the username is mapped to userPrincipalName (UPN), the provider will perform a GC lookup to confirm that the UPN is not already in use elsewhere in the forest. This means that if you use the provider in an extranet environment and you use UPNs for the username, your web servers will require network connectivity to a global catalog server to perform this check. Also if you use a UPN for the username the provider will automatically generate a random 20-character value for the sAMAccountName attribute (this will look something like $A31000-2B7QQ9PMDFOG). Even though the provider never uses this random value, it must generate a unique value because AD enforces uniqueness of SAM account

names within a domain. On an ADAM server, the provider doesn’t do anything special for sAMAccountName because this attribute doesn’t exist in the ADAM schema. For both AD and ADAM, the provider also automatically sets the cn attribute (that is, the common name for the user object) to the value passed in the username parameter. If requiresUniqueMail is set to true in the provider’s configuration, then the provider also verifies that the email address is unique by performing a subtree search for other users with the same email address. The subtree search is rooted at the container specified by the connection string. Users are always created in the directory container determined by the connection string. The actual process of creating the user takes three to four steps: first, the user object is created, then the password is set on the object (effectively IADsUser::SetPassword is called), and then the disabled status of the user object is set. In the case of ADAM, the new user account is also added to the Readers security group for the application partition. If any phase of user creation after the first step fails, the provider will attempt to clean up after itself by deleting the partially created user object from the directory. This last step is the reason the identity used by the provider needs the ability to both create and delete user objects for the CreateUser method to work.

ChangePassword — The provider relies on AD and ADAM to keep track of bad passwords that may be passed to this method. If enablePasswordReset is set to true, the provider will also disallow password changes if the user account was already locked out because of bad password answers. If enablePasswordReset is set to true, the provider resets the password-answer- tracking fields each time a good password is used with this method. The password change is effectively being invoked with a call to IADsUser::ChangePassword.

477

Chapter 12

ChangePasswordQuestionAndAnswer — As with ChangePassword and ValidateUser, the provider lets AD and ADAM handle tracking of bad passwords. If enablePasswordReset is set to true, the provider will also disallow changes to the question and answer if the user

account was already locked out because of bad password answers. If enablePasswordReset is set to true, the provider resets the password-answer-tracking fields each time a good password is used with this method.

DeleteUser — No directory-specific functionality. Deleting a user is just a straightforward removal of the user from the container determined by the connection string.

FindUsersByEmail — If the provider configuration attribute enableSearchMethod is not set to true, this method will throw a NotSupportedException. You can use the LDAP wildcard character * to perform the equivalent of SQL LIKE queries with this method. See the earlier “Provider Settings for Search” section for details on how the provider performs broad searches against a directory. The MembershipUserCollection that is returned contains instances of the

ActiveDirectoryMembershipUser class.

FindUsersByName — If the provider configuration attribute enableSearchMethods is not set to true, this method will throw a NotSupportedException. You can use the LDAP wildcard character (*) to perform the equivalent of SQL LIKE queries with this method. See the earlier “Provider Settings for Search” for details on how the provider performs broad searches against a directory. The MembershipUserCollection that is returned contains instances of the

ActiveDirectoryMembershipUser class.

GeneratePassword — This method generates a random password using the same logic used by the SqlMembershipProvider. Internally, this method just calls Membership

.GeneratePassword. The important thing to note here is that the provider’s ResetPassword method relies on GeneratePassword. However, Membership.GeneratePassword has no awareness of the password complexity policy set for the domain or ADAM server. As a result, it is possible that the password generated by this method will not pass the directory’s password complexity rules. If you encounter this situation, you will need to derive from ActiveDirectoryMembershipProvider and override this method with custom logic that generates conforming passwords.

GetAllUsers — If the provider configuration attribute enableSearchMethods is not set to true, this method will throw a NotSupportedException. See the earlier “Provider Settings for Search” section for details on how the provider performs broad searches against a directory. The MembershipUserCollection that is returned contains instances of the

ActiveDirectoryMembershipUser class.

GetNumberOfUsersOnline — This method always throws a NotSupportedException because the provider does not implement any logic for keeping track of the online state of a user.

GetPassword — This method always throws a NotSupportedException. Even though theoretically you can configure your directory to use reversible encryption, this is not a recommended security practice for AD and ADAM. The feature team decided not to support this functionality because they did not want to encourage the usage of reversible encryption.

GetUser — Both overloads look for the user object using a subtree search rooted at the container determined from the connection string. In the case of the overload that accepts the providerUserKey parameter, you can supply an instance of System.Security

.Principal.SecurityIdentifier to the provider, and it will search for a user with a matching SID in its objectSID attribute. The user object that is returned is an instance of

ActiveDirectoryMembershipUser. Both overloads ignore the userIsOnline parameter because the provider does not track the online status of users.

478

ActiveDirectoryMembershipProvider

GetUserNameByEmail — Performs a subtree search rooted at the container determined by the connection string for a user with a matching email address. If the requiresUniqueEmail configuration attribute is set to true, and more than one match is found, the provider throws a ProviderException. Otherwise, the provider returns the username from the first matching user object that is found.

ResetPassword — if enablePasswordReset is set to false, the provider just throws a

NotSupportedException. The provider disallows password resets for locked-out users, regardless of whether the user was locked out because of too many bad password attempts or too many bad password answer attempts. The provider will automatically keep track of bad password answer attempts using the custom attributes that you configure for the provider. If a valid password answer is supplied in the passwordAnswer parameter, the provider resets the bad-password-answer-tracking attributes in the directory to their default values (the counter and two date-time tracking fields are all set to zero). Assuming that a good password answer is supplied and the user is not locked out, the provider effectively calls IADsUser::SetPassword to reset the password to a randomly generated new password value. See the earlier notes on GeneratePassword for caveats about the randomly generated password and the directory’s password complexity policy.

UnlockUser — Resets the user to an unlocked state. For bad password attempts, this means that the user object’s lockoutTime attribute is reset to zero. The bad-password-answer-tracking attributes (both the counter field and the two date-time fields) are also reset to zero. Note that unlike SqlMembershipProvider, after a user is locked out in AD the account will automatically become unlocked, assuming that the account lockout policy in AD and ADAM has been configured to allow this. As noted earlier, if you are also using the question-and-answer-based password reset, the provider also supports automatically unlocking a user account after a configurable time assuming that the lockout occurred because of too many bad password answers.

UpdateUser — You can pass either a MembershipUser instance or an

ActiveDirectoryMembershipUser instance to this method. If an

ActiveDirectoryMembershipUser instance is provided, the provider will check to see which updatable properties have changed and will only write the subset of changed properties back to the directory. The provider supports updating only the Email, Comment, and IsApproved properties in the UpdateUser method.

ValidateUser — Because the provider always operates within the scope of the container

(or container hierarchy) determined by the connection string, the provider makes an extra check in this method. If a valid username-password pair is supplied, then the provider checks to see if the user actually exists within the scope determined from the provider’s connection string.

If the user does not exist within the directory scope, the method still returns false. For example, if user foo exists in OU=bar, but the provider is pointed at a peer container called OU=baz, then even if the foo account supplies the correct password, the method still will return false because the user account does not exist within OU=bar. The provider relies on the bad password lockout mechanism provided by AD and ADAM for handling bad password attempts. If a correct password is supplied and enablePasswordReset is set to true, the provider will automatically reset the bad-password-answer-tracking attributes to zero. Because ValidateUser is probably the most heavily called method, you should keep in mind the performance overhead of enabling password resets on this method. If you don’t use password resets, this method performs one directory search to verify the user is located within the provider’s container scope, and one LDAP bind to actually verify the credentials. If password resets are enabled, then an additional LDAP call is always made to check the password-answer-tracking attributes. If these attributes need to be reset, a second call is made to reset the password-answer-tracking attributes.

479

Chapter 12

The provider also implements the public properties defined by the MembershipProvider base class as well as a few extra directory-specific properties. The directory-specific properties and MembershipProvider properties with special behavior are:

ApplicationName — The getter just returns the value set in the provider’s configuration. Like SqlMembershipProvider, if this value was not set in configuration it returns either the virtual path of the current web application or the name of the .exe (sans the .exe extension) that is currently running. Again, this behavior was done just to make the property somewhat consistent with the SQL provider’s behavior. Internally, the provider never uses the ApplicationName property, and thus the trick of overriding the ApplicationName getter to handle dynamic portal-style applications will not work. The setter for this property throws a NotSupportedException.

CurrentConnectionProtection — This returns the type of connection protection that the provider ultimately settled on. This property doesn’t return the value of the connectionProtection attribute in configuration. Remember that when you set the

connectionProtection attribute to Secure in configuration, the provider still needs to follow its internal heuristics to determine the precise type of connection security it will use. If you set connectionProtection to None, this property returns the enumeration value

ActiveDirectoryConnectionProtection.None. If you set connectionProtection to Secure, then this property will return either ActiveDirectoryConnectionProtection.Ssl or ActiveDirectoryConnectionProtection.SignAndSeal, depending on which type of connection security the provider settled on.

EnablePasswordRetrieval — Because the provider never supports password retrieval this property always returns false.

EnableSearchMethods — Returns the value of the enableSearchMethods provider configuration attribute. This allows you to write code that conditionally exposes search logic based on the provider’s configuration.

PasswordAttemptLockoutDuration — Returns the value of the passwordAttemptLockoutDuration configuration attribute. If you enabled question- and-answer-based password resets for the provider, then this property indicates the number of minutes after which an account that was locked out because of too many bad password answers will be considered to have automatically unlocked.

PasswordFormat — Regardless of whether the underlying directory server has enabled reversible encryption for passwords this property always returns the value MembershipPasswordFormat

.Hashed.

ActiveDirector yMembershipUser

As part of the provider’s implementation, it uses a custom derivation of MembershipUser called ActiveDirectoryMembershipUser. This custom user type serves the following purposes:

It makes the SecurityIdentifier that is the ProviderUserKey property serializable. Because the Membership feature expects MembershipUser instances to be serializable, and the

SecurityIdentifier class itself is not serializable, the ActiveDirectoryMembershipUser has some special logic to translate the ProviderUserKey property into a serializable format.

480

ActiveDirectoryMembershipProvider

The LastLoginDate and LastActivityDate properties are overridden to throw NotSupportedExceptions from both their getters and setters. This ensures that developers will recognize that user objects returned from AD or ADAM do not support these property values.

The class implements a constructor that matches the wide constructor overload on the

MembershipUser base class. The ActiveDirectoryMemberhipUser class makes a validation check inside of its constructor to ensure that if a non-null value is supplied for the providerUserKey parameter that it is of type System.Security.Principal

.SecurityIdentifier.

The custom class overrides the Email, Comment, and IsApproved properties. Inside of the setters the ActiveDirectoryMembershipUser class sets internal flags marking each property value as dirty. This is done as a performance optimization to cut down on the need to update properties on the directory server if their original values have not changed. The provider checks the dirty flag for each property inside of its UpdateUser implementation. If the ActiveDirectoryMembershipUser instance indicates that a property has changed, then

the provider adds it to the set of attributes that will be updated in the directory. Note that the user class considers a call to a property setter as sufficient indication that the property has changed. It does not attempt a value comparison to confirm that the value has really changed. Additionally, the provider does not compare the current value of any of the user properties to the corresponding values in the directory. The provider assumes that if the user class has marked as property as dirty, its value should be written back to the directory.

IsApproved and IsLockedOut

Both the IsApproved property and the IsLockedOut properties are computed by ActiveDirectoryMembershipProvider when a user object is retrieved from the directory. For

the IsApproved property, the provider will compute the value as false if the user object is marked as disabled in the directory (for example, if you view the user with the AD Users and Computers snap-in, the Account is Disabled check box is checked). If the user object is enabled in the directory, though, then the IsApproved property is computed as true. In other words, there is a one-to-one correspondence between the value of the IsApproved property and the enabled status of the user in AD and ADAM.

However, this is not the case for the IsLockedOut property. If the user was locked because of too many bad password attempts, then both the IsLockedOut property and the locked out status stored in the directory will match. However, if you have enabled question-and-answer-based password resets, it

is possible that IsLockedOut will return true because the user had too many bad password answer attempts. In this case when you look at the user object in the directory (that is, you look at the msDS- User-Account-Control-Computed attribute in a Windows Server 2003 AD or an ADAM directory), the account won’t show as being locked out.

This also means that a user could attempt to log in to your website, and have the login fail — yet if that same user sits down at her desk, she will be able to successfully log on to her machine. If you have management tools or scripts that query for locked out users, you will need to update them to also look at the failed password answer lockout time attribute that you have to add to the directory’s user class when enabling password resets. If the difference between the current UTC time and the lockout

time stored in the directory is less than or equal to the lockout duration specified in the provider’s passwordAnswerAttemptLockoutDuration configuration attribute, then the user should be considered in a locked out state.

481