DPS907 WSA500 Assignment 8

Work on claims management and user account management activities.

Read/skim all of this document before you begin work.

 

Due date

Wednesday, November 9, 2016, at 11:00pm ET

Grade value: 5% of your final course grade

If you wish to submit the assignment before the due date and time, you can do that.

 

Objective(s)

Work with the security components in a web service, specifically claims management and user account management activities.

 

Introduction to the problem to be solved

In a new security-aware app, we start with the claims and user management foundations that we learned about this week. Then, we will add to the foundations, and implement a few common use cases.

For this assignment, we will not be creating entities for data, other than what’s needed for claims and user account management.

 

Specifications overview and work plan

The following specifications apply to all of your assignments:

  • Follows best practices
  • Implements the recommended system design guidance
  • Customized appearance on the landing web page
  • Uses Entity Framework and Code First technology
  • Includes a Fiddler log file that shows complete coverage of tests

For this assignment, here is what we’re looking for:

  • Implement a “master list” of claims for the app, and configure some initial claims
  • Validate custom claims during the create/register user account process
  • Enable the ability to edit a claim on the master list
  • Enable the ability to delete (or “retire”) a claim on the master list
  • Add more useful use cases to the user account management tasks
  • Add a simple “test” controller that will enable you to test your work as you complete it

 

During the class/session, your professor will help you get started and make progress on this assignment.

Every week, in the computer-lab class/session, your teacher will record a grade when you complete a specific small portion of the assignment. We call this “in-class grading“.

The in-class grading will be announced in-class by your professor.

DPS907 students must complete a few additional use cases.

 

Getting started

Create a new web service, named Assignment8. It will use the Visual Studio standard ASP.NET Web Application template, for a Web API app, with Individual User Accounts authentication.

Remember to customize the home controller’s index view with your personal information, and the _Layout.cshtml view template with the application name.

Build/compile, and run (without debugging), to ensure that the app’s home > index view loads in a browser.

 

Doing the work

Before continuing, add/configure the infrastructure components that are needed, including:

  • AutoMapper version 4.2.1
  • The AuthInfo controller from the Week 7 SimpleClaims code example (yes, you have permission to do this)
  • The CustomAuthorizeAttributeApi.cs source code file from the same code example (yes, permission)

Build/compile (to make sure that there are no errors). Run, and ensure that the response from /api/values is HTTP 401.

 

Add code for the “master list” of claims, and user management

From the Week 8 ManageUserAccounts code example, add these to your project. You have permission to use these as-is (and modify them in the future):

  1. The AppClaim design model class (and its DbSet<TEntity> configuration); it is OK to add it to the IdentityModels.cs source code file, or you could put the code in a design model classes source code file.
  2. The AppClaim_vm.cs source code file.
  3. The AppClaimsController source code file.
  4. The Manager.cs source code file.
  5. The user account management resource models source code file.
  6. The user account management controller source code file.
  7. AutoMapper create-maps.

Build/compile (to make sure that there are no errors).

 

Decide what claims will be on the “master list”

Before continuing, decide what claims will be on the “master list” when the app runs for the first time. Note that this is NOT a coding task. It is a thinking task, that results in a decision. Document your decision (write it down or put it in a digital note). You will use this info soon.

What claims should you have? Well, we should have claim types OU and Task. What values? Let’s think about the music business example again (the one that uses the Chinook sample database). We will not be using its data or database, but just the ideas or concepts from the music business.

Think of (and document) five (5) OU claims, and five (5) Task claims.

For the OU claims, think about the music business. If you have to do a bit of research or learning, go ahead and do that. What kind of organizational units are you likely to find in the music business? Figure out five (5) of them. Use proper case for the values (be neat and tidy).

For the Task claims, what kind of tasks or activities can be done in the app? For example, edit customer info? Figure out five (5) of them. Use proper case or camel case for the values (again, be neat and tidy). For your reference, here is the Chinook sample database class diagram.

 

Initial user(s) configuration

From the Week 8 ManageUserAccounts code example, add the IdentityInitialize.cs source code file to your project. Yes, you have permission to use this file as-is (and modify it in the future).

If you wish, you can add more code, to create more user accounts.

 

Initial claims configuration

Add a method to the IdentityInitialize class, which will add the claims that you documented above.

As suggested by the comment in the code example, get a reference to the manager object, ask whether there are any claims, and if not, add them. Remember to call this method from your code.

At this point in time, build/compile (to make sure that there are no errors). Then, run the app again. This time, the data store will be initialized, because of the calls to the methods in the IdentityInitialize class.

 

Enable the ability to edit a claim on the master list

Sometimes a claim type or value on the master list would need to be updated. Maybe with a type or value change, or a description change. Look at the design model class, and decide which properties can be updated. Use common sense.

Implement this capability. It will need (as any edit task needs) a resource model class, and logic to handle the update.

 

Enable the ability to delete (or “retire”) a claim on the master list

At some point in time, we may want to delete (or more accurately, “retire”) a claim on the master list. In typical use, we do not really want to delete the actual object, but we don’t want to use it any longer.

Look at the design model class, and you will notice a “DateRetired” property. That should be updated.

The controller method can be coded as a “delete” method. Then, its manager method can (as noted above) update the “DateRetired” property.

 

Create a “test” controller

Create a standard Web API controller that you can use to test your work as you complete it. Name the controller A8Test.

It will have simple methods that will deliver a text message. Each method will have:

  • A custom [Route()] attribute of your own design
  • Authorize attributes – standard or custom – that will enable you to determine whether your work is successful

The method body can be as simple as this:

[Authorize(Roles = "whatever")]
[AuthorizeClaim(ClaimType = "foo", ClaimValue = "bar")]
[Route("api/a8test/get5")]
public IHttpActionResult Get5()
{
  // optional - can do a task if you wish
  return Ok("request was successful");
}

 

The testing routine would go something like this:

  1. For example, add support for a new claim
  2. Use Fiddler to add the claim to the master list of allowed claims
  3. Add a new user account, which includes the new claim
  4. Login (request a token) as the new user account
  5. Using the token, attempt to request the resource in the A8Test controller

 

Edit the “register user account” code assets

By now, you are familiar with the process: The RegisterBindingModel and the AccountController classes need edits to support additional claims. Make the edits to both. Use the guidance and code examples from weeks 7 and 8 to complete this task.

The code example Register() method in the Account controller validates role claims. You must write code to validate the custom claims.

The user will be sending a collection of one or more custom claims in their request. Each custom claim will be a string, for example: OU = Cowborts

As a reminder, the Register() method is designed to accept either JSON or HTML Forms data. If you are sending JSON, then a string collection can include the equals sign as-is. However, if you are sending HTML Forms data (content type is x-www-form-urlencoded), then you must encode the equals sign (as %3d).

More information about this… If we had an HTML Form in a browser, entering a custom claim that includes an equals sign (e.g. OU = Cowborts) will automatically get encoded by the browser before it sends the request. In the request body, you will be able to see the %3d encoding. When the request is received at the server, the model binding part of the request-processing pipeline decodes the %3d back into an equals sign, as it writes the value of the string property.

In summary:

Location of the data Format / content
This is how you visualize the type=value pair
(also, how you would enter it in an HTML Form in a browser)
OU=Cowborts
How it appears in the body of the HTTP POST request OU%3dCowborts
After decoding by the model binder, this is how it ends up in the string property OU=Cowborts

 

Why does this matter?

When you are writing code to decode and validate the custom claims, you will be looping through the CustomClaims collection in a way that’s similar to the role claims processing.

It is possible that the user/requestor will attempt to send bad data. For example, a Type=Value pair that does not have one of the parts (Type or Value). The data would still be a valid string, so the model binder will correctly place the submitted data into a string property. However, you must test/check to ensure that the result is a Type=Value pair. How would you do that?

Well, the value of each custom claim should be a Type=Value string. You must break the string apart into the “ClaimType” part, and the “ClaimValue” part. Use the string Split method to do this easily – the result will be a two-element array.

Take a moment to read the documentation, and the first overload.

The return type will be an array of one or more elements.

We’re looking for exactly two (2) elements. If not two, then the custom claim string is NOT valid, right?

 

Make sure that you “clean” the result, by using the Trim() method.

At this point in time, the data format of a custom claim will be OK – at least it has a Type and Value. Next, ask the question: Does it match an existing item on the master list of app claims? (There is an existing manager method that will answer that question.)

After validation, in the “if (canRegister)…” statement, add the code that will add the custom claims:

  • Decode the submitted claim
  • Add it

 

Use case: All users with a specific claim

As you know, the ManageUserAccounts code example included some use cases for user account management tasks (get all, get one by email address, etc.). In this part of the assignment, you will add support for more use cases.

For this use case, we are interested in all users with a specific claim. For example, all users with the OU=Cowborts claim.

That should be reasonably easy to complete, because it’s very similar to an existing “get all users by surname” use case that’s in the code example.

 

Use case: List of all claims, ordered first by type, then by value

Notice that we’re interested in the claims in the user accounts for this use case. Not the claims on the master list.

You will probably need help to solve this very challenging use case: 

Here are the constraints:

  1. The ASP.NET Identity API does not appear to expose a claims collection
  2. The data context does not appear to expose the AspNetUserClaims entity collection

That alone will make it difficult to get “all claims”. So, we’ll have to think about a different strategy.

We can get some claims however, on a per user account basis.

So, maybe we can somehow assemble the data that we need, by going through the user account collection. Let’s look at this, to see whether there’s a solution.

Assume that we get the claims for the first user. Great – now we have a collection of claims (e.g. allClaims).

Next, get the claims for the second user. How can we “combine” the claims from both users together? It turns out that this is reasonably easy. The collections that we work with implement the IEnumerable<T> interface. If you read/skim the list of extension methods, you will notice the Concat method. It could work like this:

// First collection
var allUsers = userManager.Users.ElementAt[0].Claims;
// Second collection
var moreUsers = userManager.Users.ElementAt[1].Claims;

// Combine them together
// Must ToList() the result of Concat(), which returns an IEnumerable<T>
allUsers = allUsers.Concat(moreUsers).ToList();

 

Are we done? Not really, for two reasons:

  1. The result will appear to have many duplicate-like entries (e.g. many OU=SICT claims), and we will want our result without duplicate-like entries
  2. The result will not be sorted/sequenced

OK, how can we get rid of the duplicate-like entries? The IEnumerable<T> interface has some set-oriented methods that can work with two collections:

  • Except – elements in the first collection, except those in the second collection
  • Intersect – only the elements in both collections
  • Union – elements in both collections (no duplicates)

Ah, the last one – Union – might do the job. For example, the last statement in the code example above becomes:

// Do a set union operation
allUsers = allUsers.Union(moreUsers).ToList();

 

This method (and the other two) use a default way to compare equality, which is to compare objects. That’s a problem for us. Why? Let’s look at the contents of a “role” claim for two user accounts, “student1” and “student2”:

// student1...
{
  "Id":9,
  "ClaimType":"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
  "ClaimValue":"Student",
  "UserId":"10bc15d3-a84e-4459-86dd-228889c0f2c8"
}

// student2...
{
  "Id":17,
  "ClaimType":"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
  "ClaimValue":"Student",
  "UserId":"96a6f980-738b-4230-bec5-b5b64342fce4"
}

 

Are they equal? Maybe, because both have a ClaimType of “…role”, and a ClaimValue of “Student”.

But are they really equal? No, because the Id and UserId property values are different. The result of the Union statement above will be the same as with Concat, because the objects will in fact be different.

So… can we consider them to be equal when we compare them? Yes. If we create a custom class that does the comparison, then the Union method can use that custom class. The comparison can be done only on ClaimType and ClaimValue. Then, we get a clearer result, with no “duplicates”.

In this week’s GitHub code repository, there is a source code file named ClaimComparer.cs. Get it, and add it to your project’s Models folder. Then, you can use it in the Union statement, like this:


// Do a set union operation, using a custom comparer
allClaims = allClaims.Union(userClaims, new ClaimComparer()).ToList();

 

In summary, here is a coding plan to get this manager method written:

  1. Plan to return a collection of MSClaimBase objects
  2. Get a reference to the user manager
  3. Fetch the collection of user accounts
  4. Create an empty collection of claims (specifically IdentityUserClaim objects)
  5. For each user account in the collection of user accounts…
    1. Fetch the collection of claims
    2. Do a union, with the custom comparer
  6. Return the mapped collection of claims

 

Minor improvement to the results

As you can see, the method returns a collection of MSClaimBase objects. They have extra properties that we don’t really need, Id and UserId. We can get rid of these. How?

With another resource model class (and AutoMapper create/map). For example:

Create a resource model class, maybe named MSClaimList. It has only the ClaimType and ClaimValue properties. Then, return a collection of MSClaimList objects.

 

(DPS907 students…) Implement more use cases

DPS907 students will implement a few more use cases:

 

Claim – all users without a specific claim

For example, all users who do NOT have the “Employee” role claim.

This will be very similar to the previous “all users with a specific claim”. Here’s how to think about it:

  1. Fetch the collection of all user accounts
  2. Create a collection of matching users WITH the specific claim
  3. Remove the “matching” user accounts from the collection of “all” user accounts

The result will be what you want.

Earlier, you learned that the extension methods in the IEnumerable<T> interface included some set-oriented methods. Look at them – maybe “Except” will do the job.

 

Claim – add claim to a selection of users

This is a common situation in real life within organizations, and it’s a good opportunity to write a code solution for it.

For example, assume that you have the email addresses for a set of users, who all have the Task=PlaylistAdd custom claim. As a result of a management and department personnel change in the organization, a new claim must be added to each user in the set. Implement this.

You will need a resource model to capture the data from the user/requestor. Maybe it has these properties:

  • Claim type
  • Claim value
  • Collection of email addresses

Make sure that you remember to fetch/retrieve the users you want to work with into an in-memory collection, before you query and process a user account. However, remember that the in-memory collection will not be live/linked to the data store, so you will have to take a collection object’s identifier and use that to interact with the ASP.NET Identity API.

Also, make sure that you remember to validate the new claim against the master list of allowed claims.

In summary, here is a coding plan to get this manager method written:

  1. Plan to return whatever you want (void or a collection of updated user account info)
  2. Get a reference to the user manager
  3. Clean/tidy/trim the incoming type and value strings
  4. Validate the requested claim against the master list of allowed claims (call the existing method for that)
  5. For each email address in the collection…
    1. Using the user manager object, attempt to find the user account by its email address
    2. If found, check whether it already has the requested claim
    3. If not, then add the claim

 

FYI – In step 5.3 above, the AddClaim() method returns an IdentityResult. You have seen that before, in the Register method of the Account controller. The pseudocode/syntax of the method call will be something like…

IdentityResult result = userManager.AddClaim(user account identifier, new claim object);

Then (like you’ve seen before), you can test result.Succeeded if you have to.

Also FYI – After you complete this task, you will have learned enough to implement more use cases, such as edit and remove. (We are NOT asking for those in this assignment, but wanted simply to point out that the thought process will be similar.)

 

Testing your work

Use Fiddler.

Ensure that it has been configured to save the message bodies in requests and responses. (A default installation does not do this.) If you are using a College computer, this should have been configured, but check anyway. If you installed Fiddler on your own computer, follow the instructions on this document.

Test all scenarios (use cases). Make sure that you test error or error-like scenarios.

 

Saving – “exporting” – your tests

On the left side list of requests, you can delete items that you do not want included in the export.

When you’re ready to save, choose File > Export Sessions > All Sessions…

The export format will be “HTTPArchive v1.2”. Click the Next button to choose a save location (your project’s root, in the same folder level as the “packages” folder and specify a filename. Name the file by using the project name (e.g. “<whatever>.har”).

(You can test whether the export was successful. How? First, close then re-open Fiddler. Choose File > Import Sessions. Select “HTTPArchive” as the import format. Navigate to the folder that holds the “har” file, and select it. Finally, browse through the request-response sessions.)

 

Reminder about academic honesty

You must comply with the College’s academic honesty policy.

Although you may interact and collaborate with others, you must submit your own work.

 

Submitting your work

At this point in time, you should be familiar with the process:

  1. Copy your project
  2. Remove its packages, bin, and obj folders
  3. Zip and upload to the designated location on My.Seneca/Blackboard before the due date-and-time

If you need more details, look at the info in assignments 1 through 3.

 

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

%d bloggers like this: