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

Asp Net 2.0 Security Membership And Role Management

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

Chapter 12

Figure 12-4

Notice that the account now has Full Control on any user objects in the container as well as the Create/ Delete User Objects privilege on the container. The account needs to have two different sets of rights because the intent is for the userpopaadmin account to have a set of specific user object rights within the container as well as the ability to add and remove user objects in the container. Notice that the account doesn’t have Full Control on the container itself. This allows other object types that are managed by other user accounts to be stored in the container.

If you highlight the Full Control row and click the Edit button, you will see the set of permissions that userpopaadmin now has on any user object located in the container. Specifically, it has Write All Properties permission as well as the Reset Password and Change Password permissions. These permissions will allow userpopaadmin the ability to set all of the properties on a newly created user object (including the password property) as well as the ability to reset the password when the ResetPassword method is called on the provider. These permissions also allow the account to be used when calling the Update method because this method updates a number of different properties on a user object in the directory.

With the security configuration for the admin user complete, you can make use of it to connect to the directory with a connection string which points directly at the OU:

492

ActiveDirectoryMembershipProvider

<add name=”DirectoryConnection” connectionString=”LDAP://corsdc2.corsair.com/

OU=UserPopulation_A,DC=corsair,DC=com”/>

In this example, you configure two providers: one for admin operations and one for get/search operations:

<membership defaultProvider=”readonlyprovider”> <providers>

<clear/>

<add name=”adminprovider” type=”System.Web.Security.ActiveDirectoryMembershipProvider, ...” enableSearchMethods=”true” connectionUsername=”userpopaadmin@corsair.com” connectionPassword=”pass!word1” connectionStringName=”DirectoryConnection” />

<add name=”readonlyprovider” type=”System.Web.Security.ActiveDirectoryMembershipProvider, ...”

enableSearchMethods=”true” connectionStringName=”DirectoryConnection” />

</providers>

</membership>

The provider named adminprovider uses the explicit credentials with elevated privileges. The second provider instance named readonlyprovider depends on the default rights that the Authenticated Users group has to read various attributes on a user object. Note that in a production environment you should use protected configuration (discussed in Chapter 4) so that the explicit credentials are not stored as cleartext. You can now create users with the admin provider:

MembershipCreateStatus status;

MembershipProvider mp = Membership.Providers[“adminprovider”];

mp.CreateUser(“demouser103@nowhere.org”, “pass!word1”, “demouser103@nowhere.org”, null, null, true,null, out status);

Response.Write(status.ToString());

Read operations use the default provider running as NETWORK SERVICE, and thus the default provider can only search for users and read attributes on the user object. Note that you can take security lock down a step further by removing the Authenticated Users ACL from the default ACL defined for the user class in the directory’s schema. Doing so gets into the nitty-gritty of managing Active Directory default ACLs, which is a bit far afield from the topic of how to use ActiveDirectoryMembershipProvider.

However, if you have changed the default ACL for the user object (you can see the default ACL using the Active Directory Schema editor, look at the Default Security tab on the Properties dialog box of the user class) by removing the Authenticated Users group, you can create a read-only user account using the same approach just shown for the administrative user. Just create a new read-only user account and with the Delegation of Control Wizard grant read permissions on all user objects in the container to the account. Because the wizard will end up granting read permissions on all attributes of user objects, you can right-click the container and use the Security tab to fine-tune the specific sets of user attributes that you really want the read-only account to have access to. The default set of permissions granted to the Authenticated Users account as described earlier is a good starting point.

493

Chapter 12

Configuring Self-Service Password Reset

Self-service password resets are the one piece of provider functionality that is not “auto-magically” supported without a moderate amount of intervention on your part. Unlike SqlMembershipProvider, where this functionality is just a matter of setting the enablePasswordReset configuration attribute to true, ActiveDirectoryMembershipProvider requires schema changes prior to turning on the functionality. Furthermore, after the schema changes are made you need to configure the ACLs appropriately in the directory so that a provider has rights to read and update these properties.

You could use preexisting directory attributes to store password question-and-answer-related information. Although this saves you from having to modify the directory schema, from a long-term perspective it makes more sense to extend the schema with attributes to support the provider, rather than attempt to reuse existing directory attributes. This will prevent problems down the road if you overloaded a directory attribute for use with the provider, but then find out you actually need to “take back” the attribute for its original purposes.

The attributes that you need to add are those for the following pieces of information:

Password question — A Unicode string attribute to store the user’s password question.

Password answer — A Unicode string attribute to store the user’s password answer.

Failed password answer count — An attribute of type Integer that is the counter for keeping track of the number of failed password answer attempts.

Failed password answer time — An attribute of type Large Integer/Interval that will store the beginning of the time tracking window for failed password answer attempts.

Failed password answer lockout time — An attribute of type Large Integer/Interval that stores the time the account was locked out because of too many failed password answer attempts.

You can use the Active Directory Schema snap-in to create five new attributes for storing these values. Before you do so, note that you have to have rights to edit the schema for your domain. This right is normally reserved for members of the Schema Admins group because of the sensitive nature of schema edits. Schema edits are a one-way affair; after you add an attribute, you can never actually delete it. Instead, you can only deactivate attributes. For this reason, enabling self-service password reset for the provider makes sense only for Internet facing websites that rely on Active Directory. Making irreversible schema edits to an extranet directory is less of an issue than making schema edits to your core corporate directories.

Whenever you create a new directory attribute you need to have a name for the attribute as well as an X.500 OID. If you are an old database developer like me, the need for the OID is sort of weird, but it is a necessary part of creating any new classes or attributes in Active Directory. If you happen to have the Windows 2000 Resource Kit lying around it has a handy command-line tool called oidgen.exe that will automatically generate a base OID for new attributes. I created five new attributes in my directory as follows:

494

ActiveDirectoryMembershipProvider

Attribute Name (Both LDAP and Common)

OID

ampPasswordQuestion 1.2.840.113556.1.4.7000.233.28688.28684.8

.311583.60825.551176.463623.1

ampPasswordAnswer 1.2.840.113556.1.4.7000.233.28688.28684.8

.311583.60825.551176.463623.2

ampFailedPasswordAnswerCount 1.2.840.113556.1.4.7000.233.28688.28684.8

.311583.60825.551176.463623.3

ampFailedPasswordAnswerTime 1.2.840.113556.1.4.7000.233.28688.28684.8

.311583.60825.551176.463623.4

ampFailedPasswordAnswerLockoutTime 1.2.840.113556.1.4.7000.233.28688.28684.8

.311583.60825.551176.463623.5

You can see what configuring the new password answer attribute looks like in Figure 12-5:

Figure 12-5

495

Chapter 12

The configuration for the password question attribute looks exactly the same. Figure 12-6 shows how the password answer count attribute is configured as an Integer type.

Figure 12-6

The configuration of the failed password answer time attribute is shown in Figure 12-7.

496

ActiveDirectoryMembershipProvider

Figure 12-7

Configuring the failed password answer lockout time works the same way, just with a different attribute name and OID.

With the attribute configuration completed, you can add these attributes to the user class in the directory. You just right-click the user class in the MMC, select Properties and in the Attributes tab, add the five new attributes as optional attributes. After you have done this, the Attributes tab will look something like Figure 12-8.

497

Chapter 12

Figure 12-8

Now that the user object has been modified to include extra attributes for storing password-reset-related information, you can configure a provider to make use of the new attributes. Using the administrative provider shown earlier, you can modify its configuration to allow for question-and-answer-based password resets.

<add name=”adminprovider” type=”System.Web.Security.ActiveDirectoryMembershipProvider, ...” enableSearchMethods=”true” connectionUsername=”userpopaadmin@corsair.com” connectionPassword=”pass!word1” attributeMapPasswordQuestion=”ampPasswordQuestion” attributeMapPasswordAnswer=”ampPasswordAnswer” attributeMapFailedPasswordAnswerCount=”ampFailedPasswordAnswerCount” attributeMapFailedPasswordAnswerTime=”ampFailedPasswordAnswerTime”

attributeMapFailedPasswordAnswerLockoutTime=”ampFailedPasswordAnswerLockoutTime”

enablePasswordReset=”true”

requiresQuestionAndAnswer=”true” connectionStringName=”DirectoryConnection” />

498

ActiveDirectoryMembershipProvider

Because the provider now has to store a password answer, and you don’t want the plaintext password answer to be easily viewable by arbitrary accounts (such as Authenticated Users), the provider always encrypts the password answer. Unless you derive from the provider and add in your own custom encryption routines, this means that the provider encrypts the password answer using the encryption key specified in machine.config. Just like SqlMembershipProvider though, ActiveDirectoryMembershipProvider requires you to explicitly set a decryption key. This requirement exists to prevent the problem that would occur if different machines have completely different auto-generated encryption keys. If this were allowed the password answer created on one web server would be useless on another server.

The hashing of the password answer is not supported, because there is no mechanism for having Active Directory hash anything other than a user’s password. Rather than confuse things by adding a passwordFormat attribute on the provider that would be configurable for password answers and have no effect on the actual password, the feature team decided to support encryption of password answers only. In this way, there is no ambiguity around the protections for user passwords (AD hashes them) as opposed to the protections for password answers (they are always encrypted).

As a result of this requirement, the sample application now explicitly defines a decryption key as follows:

<machineKey

decryptionKey=”A225194E99BCCB0F6B92BC9D82F12C2907BD07CF069BC8B4” decryption=”AES” />

With the changes to the admin provider and the definition of a fixed decryption key, the sample application can now create users with question and answers. Because the Login controls work seamlessly with arbitrary membership providers, I just dropped a CreateUserWizard onto a form, configured it to use the admin provider, and started creating test accounts with questions and answers.

After creating a user with CreateUserWizard, you can dump the contents of the user object with a low-level tool like ldp.exe or the ADSI Edit MMC (you can get these tools if you install the server support tools included on the Windows Server 2003 CD). Running ldp.exe and looking at the contents of the newly created user, you can see the following:

1> cn: demouser98@corsair.com;

1> userPrincipalName: demouser98@corsair.com; 1> distinguishedName: CN=

demouser98@corsair.com,OU=UserPopulation_A,DC=corsair,DC=com;

...snip...

1> mail: demouser98@corsair.com; 1> ampPasswordQuestion: question;

1> ampPasswordAnswer: qrwD6QSuoUdaznjvBAe3JPfQmhaJtQVpFgEFARppG3c=;

As you would expect after all of the configuration work, the password question was successfully stored, as was the encrypted version of the password answer.

If you keep using the adminprovider provider, you can create a test page where you attempt to reset the password using the PasswordRecovery control. If you intentionally supply the wrong answer a few times, you will see the tracking information stored in the other attributes of the user object.

1> ampFailedPasswordAnswerCount: 3;

1> ampFailedPasswordAnswerTime: 127692324484470447;

499

Chapter 12

These attributes are showing that so far three failed password answer attempts have been made. The weird-looking password answer time is just the integer representation of the UTC date-time that is the start of the bad password answer tracking window. Because the default number of failed password answer attempts that can be made is five (the same setting as SqlMembershipProvider), after the fifth bad password attempt occurs, the tracking information for the user looks like this:

1> ampPasswordQuestion: question;

1> ampPasswordAnswer: qrwD6QSuoUdaznjvBAe3JPfQmhaJtQVpFgEFARppG3c=; 1> ampFailedPasswordAnswerCount: 5;

1> ampFailedPasswordAnswerTime: 127692325545659847;

1> ampFailedPasswordAnswerLockoutTime: 127692325545659847;

Any attempt at this point to log in with the user’s credentials, change his password or reset his password, will immediately fail because the provider sees that user is now locked out. As with the failed password answer time, the lockout time is stored as an integer representing the UTC time when the lockout occurred. Remember that one difference between this provider and the SQL provider is that if you wait 30 minutes (the default lockout timeout duration if one is configured for the domain), then the user account auto-unlocks despite the previous failed password answer attempts.

Of course, if you are impatient, you can use the Unlock method on the provider to forcibly unlock the user:

MembershipProvider mp = Membership.Providers[“adminprovider”];

mp.UnlockUser(“demouser98@corsair.com”);

The result of unlocking the user with the admin provider looks like this:

1> ampPasswordQuestion: question;

1> ampPasswordAnswer: qrwD6QSuoUdaznjvBAe3JPfQmhaJtQVpFgEFARppG3c=; 1> ampFailedPasswordAnswerCount: 0;

1> ampFailedPasswordAnswerTime: 0;

1> ampFailedPasswordAnswerLockoutTime: 0;

After an unlocking operation, the provider resets the count to zero and also stores a zero value in the two time-tracking fields. At this point, if you choose to reset the password, the new password will be sent to you. As a side note, if you want to get the PasswordRecoveryControl to work on a web server that has the default SMTP service installed, you will need a configuration entry like the following:

<system.net>

<mailSettings>

<smtp deliveryMethod=”PickupDirectoryFromIis”>

<network host=”localhost” port=”25” defaultCredentials=”true”/> </smtp>

</mailSettings>

</system.net>

Without this entry, the PasswordRecoveryControl will fail when it attempts to email the password. In the case of my sample application, because I reset the email address of my test user account to match the domain address of my web server (that is, the demouser98@corsair.com account now has an email address of demouser98@demotest.corsair.com and my local SMTP server is running on a machine with the DNS address of demotest.corsair.com), the PasswordRecoveryControl sent the password reset email to my local drop directory D:\inetpub\mailroot\drop. The text of the email looks like:

500

ActiveDirectoryMembershipProvider

Please return to the site and log in using the following information. User Name: demouser98@corsair.com

Password: l}5x)$}k!KHp]y

This entire process shows the power of the provider model used in conjunction with ActiveDirectoryMembershipProvider and the various Login controls. Although the initial schema edits in the directory are a bit of a hassle, after those are completed you can see that with some edits to web.config to configure the Membership provider and the mail server, the self-service password reset process is pretty much automated. Attempting to hand-code a similar solution yourself, especially using Active Directory (or ADAM for that matter) as the backing store, would be substantially more complex than the process you just walked through.

Note that I intentionally used the admin provider because that provider was running with security credentials in the directory necessary to allow it to reset the password of any user in the UserPopulation_A OU. Clearly, running with the other named provider (readonlyprovider) won’t work for resetting passwords because the Authenticated Users group doesn’t have the privileges necessary to reset arbitrary user passwords.

Within the ActiveDirectoryMembershipProvider methods like ValidateUser, ChangePassword,

ChangePasswordQuestionAndAnswer and GetUser will be able to read the new password answer tracking fields to determine whether the user is considered locked out. This holds true for the special administrative account that created earlier, as well the NETWORK SERVICE account that is being used by the default provider. This is behavior is OK because you want the failed-password-answer-tracking information to be readable by these methods.

There is a subtle requirement though for the ValidateUser and ChangePassword methods. Both of these methods will reset the password-answer-tracking information if the following conditions are met:

The user supplies the correct password.

The password-answer-tracking information contains nondefault values due to previously logged bad password answer attempts.

If both of these conditions are met, then the provider will reset the password answer tracking counters inside of ValidateUser and ChangePassword. For this reason, if you setup a nonadministrative account to handle user logins, make sure to grant this account write access to the three bad-password- answer-tracking attributes.

However, if you feel uncomfortable with running a nonadministrative provider under the default privileges of Authenticated Users, you can lock things further. For example, to prevent a nonadministrative provider from ever being able to read the encrypted password answer, you can go through the following steps to lock down access.

1.Create a read-only account that will be used by the nonadministrative provider to access the OU.

2.Configure a nonadministrative provider instance to run with the read-only user account, just as was done for the administrative provider that we have been using.

3.In the Active Directory Users and Computers MMC, configure the read-only account by denying specific granular user object property rights.

501