layout: post
title: “Constraining capabilities based on identity and role”
description: “A functional approach to authorization, part 2”
seriesId: “A functional approach to authorization”
seriesOrder: 2
categories: []

image: “/assets/img/auth_3.png”

UPDATE: Slides and video from my talk on this topic

In the previous post, we started looking at “capabilities” as the basis for ensuring that code could not do any more than it was supposed to do.
And I demonstrated this with a simple application that changed a configuration flag.

In this post, we’ll look at how to constrain capabilities based on the current user’s identity and role.

So let’s switch from the configuration example to a typical situation where stricter authorization is required.

Database capabilities example

Consider a website and call-centre with a backing database. We have the following security rules:

  • A customer can only view or update their own record in the database (via the website)
  • A call-centre operator can view or update any record in the database

This means that at some point, we’ll have to do some authorization based on the identity and role of the user. (We’ll assume that the user has been authenticated successfully).

The tendency in many web frameworks is to put the authorization in the UI layer, often in the controller.
My concern about this approach is that once you are “inside” (past the gateway), any part of the app has full authority to access the database,
and it is all to easy for code to do the wrong thing by mistake, resulting in a security breach.

Not only that, but because the authority is everywhere (“ambient”), it is hard to review the code for potential security issues.

To avoid these issues, let’s instead put the access logic as “low” as possible, in the database access layer in this case.

We’ll start with an obvious approach. We’ll add the identity and role to each database call and then do authorization there.

The following method assumes that there is a CustomerIdBelongsToPrincipal function that checks whether the customer id being accessed is owned by the principal requesting access.
Then, if the customerId does belong to the principal, or the principal has the role of “CustomerAgent”, the access is granted.

  1. public class CustomerDatabase
  2. {
  3. public CustomerData GetCustomer(CustomerId id, IPrincipal principal)
  4. {
  5. if ( CustomerIdBelongsToPrincipal(id, principal) ||
  6. principal.IsInRole("CustomerAgent") )
  7. {
  8. // get customer data
  9. }
  10. else
  11. {
  12. // throw authorization exception
  13. }
  14. }
  15. }

Note that I have deliberately added the IPrincipal to the method signature — we are not allowing any “magic” where the principal is fetched from a global context.
As with the use of any global, having implicit access hides the dependencies and makes it hard to test in isolation.

Here’s the F# equivalent, using a Success/Failure return value rather than throwing exceptions:

  1. let getCustomer id principal =
  2. if customerIdBelongsToPrincipal id principal ||
  3. principal.IsInRole("CustomerAgent")
  4. then
  5. // get customer data
  6. Success "CustomerData"
  7. else
  8. Failure AuthorizationFailed

This “inline” authorization approach is all too common, but unfortunately it has many problems.

  • It mixes up security concerns with the database logic. If the authorization logic gets more complicated, the code will also get more complicated.
  • It throws an exception (C#) or returns an error (F#) if the authorization fails. It would be nice if we could tell in advance if we had the authorization rather than waiting until the last minute.

Let’s compare this with a capability-based approach. Instead of directly getting a customer, we first obtain the capability of doing it.

  1. class CustomerDatabase
  2. {
  3. // "real" code is hidden from public view
  4. private CustomerData GetCustomer(CustomerId id)
  5. {
  6. // get customer data
  7. }
  8. // Get the capability to call GetCustomer
  9. public Func<CustomerId,CustomerData> GetCustomerCapability(CustomerId id, IPrincipal principal)
  10. {
  11. if ( CustomerIdBelongsToPrincipal(id, principal) ||
  12. principal.IsInRole("CustomerAgent") )
  13. {
  14. // return the capability (the real method)
  15. return GetCustomer;
  16. }
  17. else
  18. {
  19. // throw authorization exception
  20. }
  21. }
  22. }

As you can see, if the authorization succeeds, a reference to the GetCustomer method is returned to the caller.

It might not be obvious, but the code above has a rather large security hole. I can request the capability for a particular customer id, but I get back a function that can called for any
customer id! That’s not very safe, is it?

What we need to is “bake in” the customer id to the capability, so that it can’t be misused. The return value will now be a Func<CustomerData>, with the customer id not available
to be passed in any more.

  1. class CustomerDatabase
  2. {
  3. // "real" code is hidden from public view
  4. private CustomerData GetCustomer(CustomerId id)
  5. {
  6. // get customer data
  7. }
  8. // Get the capability to call GetCustomer
  9. public Func<CustomerData> GetCustomerCapability(CustomerId id, IPrincipal principal)
  10. {
  11. if ( CustomerIdBelongsToPrincipal(id, principal) ||
  12. principal.IsInRole("CustomerAgent") )
  13. {
  14. // return the capability (the real method)
  15. return ( () => GetCustomer(id) );
  16. }
  17. else
  18. {
  19. // throw authorization exception
  20. }
  21. }
  22. }

With this separation of concerns in place, we can now handle failure nicely, by returning an optional value which is present if we get the capability, or absent if not. That is,
we know whether we have the capability at the time of trying to obtain it, not later on when we try to use it.

  1. class CustomerDatabase
  2. {
  3. // "real" code is hidden from public view
  4. // and doesn't need any checking of identity or role
  5. private CustomerData GetCustomer(CustomerId id)
  6. {
  7. // get customer data
  8. }
  9. // Get the capability to call GetCustomer. If not allowed, return None.
  10. public Option<Func<CustomerData>> GetCustomerCapability(CustomerId id, IPrincipal principal)
  11. {
  12. if (CustomerIdBelongsToPrincipal(id, principal) ||
  13. principal.IsInRole("CustomerAgent"))
  14. {
  15. // return the capability (the real method)
  16. return Option<Func<CustomerData>>.Some( () => GetCustomer(id) );
  17. }
  18. else
  19. {
  20. return Option<Func<CustomerData>>.None();
  21. }
  22. }
  23. }

This assumes that we’re using some sort of Option type in C# rather than just returning null!

Finally, we can put the authorization logic into its own class (say CustomerDatabaseCapabilityProvider), to keep the authorization concerns separate from the CustomerDatabase.

We’ll have to find some way of keeping the “real” database functions private to all other callers though.
For now, I’ll just assume the database code is in a different assembly, and mark the code internal.

  1. // not accessible to the business layer
  2. internal class CustomerDatabase
  3. {
  4. // "real" code is hidden from public view
  5. private CustomerData GetCustomer(CustomerId id)
  6. {
  7. // get customer data
  8. }
  9. }
  10. // accessible to the business layer
  11. public class CustomerDatabaseCapabilityProvider
  12. {
  13. CustomerDatabase _customerDatabase;
  14. // Get the capability to call GetCustomer
  15. public Option<Func<CustomerData>> GetCustomerCapability(CustomerId id, IPrincipal principal)
  16. {
  17. if (CustomerIdBelongsToPrincipal(id, principal) ||
  18. principal.IsInRole("CustomerAgent"))
  19. {
  20. // return the capability (the real method)
  21. return Option<Func<CustomerData>>.Some( () => _customerDatabase.GetCustomer(id) );
  22. }
  23. else
  24. {
  25. return Option<Func<CustomerData>>.None();
  26. }
  27. }
  28. }

And here’s the F# version of the same code:

  1. /// not accessible to the business layer
  2. module internal CustomerDatabase =
  3. let getCustomer (id:CustomerId) :CustomerData =
  4. // get customer data
  5. /// accessible to the business layer
  6. module CustomerDatabaseCapabilityProvider =
  7. // Get the capability to call getCustomer
  8. let getCustomerCapability (id:CustomerId) (principal:IPrincipal) =
  9. let principalId = GetIdForPrincipal(principal)
  10. if (principalId = id) || principal.IsInRole("CustomerAgent") then
  11. Some ( fun () -> CustomerDatabase.getCustomer id )
  12. else
  13. None

Here’s a diagram that represents this design:

Example 2

Problems with this model

In this model, the caller is isolated from the CustomerDatabase, and the CustomerDatabaseCapabilityProvider acts as a proxy between them.

Which means, as currently designed, for every function available in CustomerDatabase there must be a parallel function available in CustomerDatabaseCapabilityProvider as well.
We can see that this approach will not scale well.

It would be nice if we had a way to generally get capabilities for a whole set of database functions rather than one at a time. Let’s see if we can do that!

Restricting and transforming capabilities

The getCustomer function in CustomerDatabase can be thought of as a capability with no restrictions, while the getCustomerCapability
returns a capability restricted by identity and role.

But note that the two function signatures are similar (CustomerId -> CustomerData vs unit -> CustomerData), and so they are almost interchangeable from the callers point of view.
In a sense, then, the second capability is a transformed version of the first, with additional restrictions.

Transforming functions to new functions! This is something we can easily do.

So, let’s write a transformer that, given any function of type CustomerId -> 'a, we return a function with the customer id baked in (unit -> 'a),
but only if the authorization requirements are met.

  1. module CustomerCapabilityFilter =
  2. // Get the capability to use any function that has a CustomerId parameter
  3. // but only if the caller has the same customer id or is a member of the
  4. // CustomerAgent role.
  5. let onlyForSameIdOrAgents (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
  6. let principalId = GetIdForPrincipal(principal)
  7. if (principalId = id) || principal.IsInRole("CustomerAgent") then
  8. Some (fun () -> f id)
  9. else
  10. None

The type signature for the onlyForSameIdOrAgents function is (CustomerId -> 'a) -> (unit -> 'a) option. It accepts any CustomerId based function
and returns, maybe, the same function with the customer id already applied if the authorization succeeds. If the authorization does not succeed, None is returned instead.

You can see that this function will work generically with any function that has a CustomerId as the first parameter. That could be “get”, “update”, “delete”, etc.

So for example, given:

  1. module internal CustomerDatabase =
  2. let getCustomer (id:CustomerId) =
  3. // get customer data
  4. let updateCustomer (id:CustomerId) (data:CustomerData) =
  5. // update customer data

We can create restricted versions now, for example at the top level bootstrapper or controller:

  1. let principal = // from context
  2. let id = // from context
  3. // attempt to get the capabilities
  4. let getCustomerOnlyForSameIdOrAgents =
  5. onlyForSameIdOrAgents id principal CustomerDatabase.getCustomer
  6. let updateCustomerOnlyForSameIdOrAgents =
  7. onlyForSameIdOrAgents id principal CustomerDatabase.updateCustomer

The types of getCustomerOnlyForSameIdOrAgents and updateCustomerOnlyForSameIdOrAgents are similar to the original functions in the database module,
but with CustomerId replaced with unit:

  1. val getCustomerOnlyForSameIdOrAgents :
  2. (unit -> CustomerData) option
  3. val updateCustomerOnlyForSameIdOrAgents :
  4. (unit -> CustomerData -> unit) option

The updateCustomerOnlyForSameIdOrAgents has a extra CustomerData parameter, so the extra unit where the CustomerId used to be is a bit ugly.
If this is too annoying, you could easily create other versions of the function which handle this more elegantly. I’ll leave that as an exercise for the reader!

So now we have an option value that might or might not contain the capability we wanted. If it does, we can create a child component and pass in the capability.
If it does not, we can return some sort of error, or hide a element from a view, depending on the type of application.

  1. match getCustomerOnlyForSameIdOrAgents with
  2. | Some cap -> // create child component and pass in the capability
  3. | None -> // return error saying that you don't have the capability to get the data

Here’s a diagram that represents this design:

Example 3

More transforms on capabilities

Because capabilities are functions, we can easily create new capabilities by chaining or combining transformations.

For example, we could create a separate filter function for each business rule, like this:

  1. module CustomerCapabilityFilter =
  2. let onlyForSameId (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
  3. if customerIdBelongsToPrincipal id principal then
  4. Some (fun () -> f id)
  5. else
  6. None
  7. let onlyForAgents (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
  8. if principal.IsInRole("CustomerAgent") then
  9. Some (fun () -> f id)
  10. else
  11. None

For the first business rule, onlyForSameId, we return a capability with the customer id baked in, as before.

The second business rule, onlyForAgents, doesn’t mention customer ids anywhere, so why do we restrict the function parameter to CustomerId -> 'a?
The reason is that it enforces that this rule only applies to customer centric capabilities, not ones relating to products or payments, say.

But now, to make the output of this filter compatible with the first rule (unit -> 'a), we need to pass in a customer id and partially apply it too.
It’s a bit of a hack but it will do for now.

We can also write a generic combinator that returns the first valid capability from a list.

  1. // given a list of capability options,
  2. // return the first good one, if any
  3. let first capabilityList =
  4. capabilityList |> List.tryPick id

It’s a trivial implementation really — this is the kind of helper function that is just to help the code be a little more self-documenting.

With this in place, we can apply the rules separately, take the two filters and combine them into one.

  1. let getCustomerOnlyForSameIdOrAgents =
  2. let f = CustomerDatabase.getCustomer
  3. let cap1 = onlyForSameId id principal f
  4. let cap2 = onlyForAgents id principal f
  5. first [cap1; cap2]
  6. // val getCustomerOnlyForSameIdOrAgents : (CustomerId -> CustomerData) option

Or let’s say we have some sort of restriction; the operation can only be performed during business hours, say.

  1. let onlyIfDuringBusinessHours (time:DateTime) f =
  2. if time.Hour >= 8 && time.Hour <= 17 then
  3. Some f
  4. else
  5. None

We can write another combinator that restricts the original capability. This is just a version of “bind”.

  1. // given a capability option, restrict it
  2. let restrict filter originalCap =
  3. originalCap
  4. |> Option.bind filter

With this in place, we can restrict the “agentsOnly” capability to business hours:

  1. let getCustomerOnlyForAgentsInBusinessHours =
  2. let f = CustomerDatabase.getCustomer
  3. let cap1 = onlyForAgents id principal f
  4. let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
  5. cap1 |> restrict restriction

So now we have created a new capability, “Customer agents can only access customer data during business hours”, which tightens the data access logic a bit more.

We can combine this with the previous onlyForSameId filter to build a compound capability which can access customer data:

  • if you have the same customer id (at any time of day)
  • if you are a customer agent (only during business hours)
  1. let getCustomerOnlyForSameId =
  2. let f = CustomerDatabase.getCustomer
  3. onlyForSameId id principal f
  4. let getCustomerOnlyForSameId_OrForAgentsInBusinessHours =
  5. let cap1 = getCustomerOnlyForSameId
  6. let cap2 = getCustomerOnlyForAgentsInBusinessHours
  7. first [cap1; cap2]

As you can see, this approach is a useful way to build complex capabilities from simpler ones.

Additional transforms

It should be obvious that you can easily create additional transforms which can extend capabilities in other ways. Some examples:

  • a capability that writes to an audit log on each execution.
  • a capability that can only be performed once.
  • a capability that can be revoked when needed.
  • a capability that is throttled and can only be performed a limited number of times in a given time period (such as password change attempts).

And so on.

Here are implementations of the first three of them:

  1. /// Uses of the capability will be audited
  2. let auditable capabilityName f =
  3. fun x ->
  4. // simple audit log!
  5. printfn "AUDIT: calling %s with %A" capabilityName x
  6. // use the capability
  7. f x
  8. /// Allow the function to be called once only
  9. let onlyOnce f =
  10. let allow = ref true
  11. fun x ->
  12. if !allow then //! is dereferencing not negation!
  13. allow := false
  14. f x
  15. else
  16. Failure OnlyAllowedOnce
  17. /// Return a pair of functions: the revokable capability,
  18. /// and the revoker function
  19. let revokable f =
  20. let allow = ref true
  21. let capability = fun x ->
  22. if !allow then //! is dereferencing not negation!
  23. f x
  24. else
  25. Failure Revoked
  26. let revoker() =
  27. allow := false
  28. capability, revoker

Let’s say that we have an updatePassword function, such as this:

  1. module internal CustomerDatabase =
  2. let updatePassword (id,password) =
  3. Success "OK"

We can then create a auditable version of updatePassword:

  1. let updatePasswordWithAudit x =
  2. auditable "updatePassword" CustomerDatabase.updatePassword x

And then test it:

  1. updatePasswordWithAudit (1,"password")
  2. updatePasswordWithAudit (1,"new password")

The results are:

  1. AUDIT: calling updatePassword with (1, "password")
  2. AUDIT: calling updatePassword with (1, "new password")

Or, we could create a one-time only version:

  1. let updatePasswordOnce =
  2. onlyOnce CustomerDatabase.updatePassword

And then test it:

  1. updatePasswordOnce (1,"password") |> printfn "Result 1st time: %A"
  2. updatePasswordOnce (1,"password") |> printfn "Result 2nd time: %A"

The results are:

  1. Result 1st time: Success "OK"
  2. Result 2nd time: Failure OnlyAllowedOnce

Finally, we can create a revokable function:

  1. let revokableUpdatePassword, revoker =
  2. revokable CustomerDatabase.updatePassword

And then test it:

  1. revokableUpdatePassword (1,"password") |> printfn "Result 1st time before revoking: %A"
  2. revokableUpdatePassword (1,"password") |> printfn "Result 2nd time before revoking: %A"
  3. revoker()
  4. revokableUpdatePassword (1,"password") |> printfn "Result 3nd time after revoking: %A"

With the following results:

  1. Result 1st time before revoking: Success "OK"
  2. Result 2nd time before revoking: Success "OK"
  3. Result 3nd time after revoking: Failure Revoked

The code for all these F# examples is available as a gist here.

A complete example in F#

Here’s the code to a complete application in F# (also available as a gist here).

This example consists of a simple console app that allows you to get and update customer records.

  • The first step is to login as a user. “Alice” and “Bob” are normal users, while “Zelda” has a customer agent role.
  • Once logged in, you can select a customer to edit. Again, you are limited to a choice between “Alice” and “Bob”. (I’m sure you can hardly contain your excitement)
  • Once a customer is selected, you are presented with some (or none) of the following options:
    • Get a customer’s data.
    • Update a customer’s data.
    • Update a customer’s password.

Which options are shown depend on which capabilities you have. These in turn are based on who you are logged in as, and which customer is selected.

Implementing the domain

We’ll start with the core domain types that are shared across the application:

  1. module Domain =
  2. open Rop
  3. type CustomerId = CustomerId of int
  4. type CustomerData = CustomerData of string
  5. type Password = Password of string
  6. type FailureCase =
  7. | AuthenticationFailed of string
  8. | AuthorizationFailed
  9. | CustomerNameNotFound of string
  10. | CustomerIdNotFound of CustomerId
  11. | OnlyAllowedOnce
  12. | CapabilityRevoked

The FailureCase type documents all possible things that can go wrong at the top-level of the application. See the “Railway Oriented Programming” talk for more discussion on this.

Defining the capabilities

Next, we document all the capabilities that are available in the application.
To add clarity to the code, each capability is given a name (i.e. a type alias).

  1. type GetCustomerCap = unit -> SuccessFailure<CustomerData,FailureCase>

Finally, the CapabilityProvider is a record of functions, each of which accepts a customer id and principal, and returns an optional capability of the specified type.
This record is created in the top level model and then passed around to the child components.

Here’s the complete code for this module:

  1. module Capabilities =
  2. open Rop
  3. open Domain
  4. // capabilities
  5. type GetCustomerCap = unit -> SuccessFailure<CustomerData,FailureCase>
  6. type UpdateCustomerCap = unit -> CustomerData -> SuccessFailure<unit,FailureCase>
  7. type UpdatePasswordCap = Password -> SuccessFailure<unit,FailureCase>
  8. type CapabilityProvider = {
  9. /// given a customerId and IPrincipal, attempt to get the GetCustomer capability
  10. getCustomer : CustomerId -> IPrincipal -> GetCustomerCap option
  11. /// given a customerId and IPrincipal, attempt to get the UpdateCustomer capability
  12. updateCustomer : CustomerId -> IPrincipal -> UpdateCustomerCap option
  13. /// given a customerId and IPrincipal, attempt to get the UpdatePassword capability
  14. updatePassword : CustomerId -> IPrincipal -> UpdatePasswordCap option
  15. }

This module references a SuccessFailure result type similar to the one discussed here, but which I won’t show.

Implementing authentication

Next, we’ll roll our own little authentication system. Note that when the user “Zelda” is authenticated, the role is set to “CustomerAgent”.

  1. module Authentication =
  2. open Rop
  3. open Domain
  4. let customerRole = "Customer"
  5. let customerAgentRole = "CustomerAgent"
  6. let makePrincipal name role =
  7. let iden = GenericIdentity(name)
  8. let principal = GenericPrincipal(iden,[|role|])
  9. principal :> IPrincipal
  10. let authenticate name =
  11. match name with
  12. | "Alice" | "Bob" ->
  13. makePrincipal name customerRole |> Success
  14. | "Zelda" ->
  15. makePrincipal name customerAgentRole |> Success
  16. | _ ->
  17. AuthenticationFailed name |> Failure
  18. let customerIdForName name =
  19. match name with
  20. | "Alice" -> CustomerId 1 |> Success
  21. | "Bob" -> CustomerId 2 |> Success
  22. | _ -> CustomerNameNotFound name |> Failure
  23. let customerIdOwnedByPrincipal customerId (principle:IPrincipal) =
  24. principle.Identity.Name
  25. |> customerIdForName
  26. |> Rop.map (fun principalId -> principalId = customerId)
  27. |> Rop.orElse false

The customerIdForName function attempts to find the customer id associated with a particular name,
while the customerIdOwnedByPrincipal compares this id with another one.

Implementing authorization

Here are the functions related to authorization, very similar to what was discussed above.

  1. module Authorization =
  2. open Rop
  3. open Domain
  4. let onlyForSameId (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
  5. if Authentication.customerIdOwnedByPrincipal id principal then
  6. Some (fun () -> f id)
  7. else
  8. None
  9. let onlyForAgents (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
  10. if principal.IsInRole(Authentication.customerAgentRole) then
  11. Some (fun () -> f id)
  12. else
  13. None
  14. let onlyIfDuringBusinessHours (time:DateTime) f =
  15. if time.Hour >= 8 && time.Hour <= 17 then
  16. Some f
  17. else
  18. None
  19. // constrain who can call a password update function
  20. let passwordUpdate (id:CustomerId) (principal:IPrincipal) (f:CustomerId*Password -> 'a) =
  21. if Authentication.customerIdOwnedByPrincipal id principal then
  22. Some (fun password -> f (id,password))
  23. else
  24. None
  25. // return the first good capability, if any
  26. let first capabilityList =
  27. capabilityList |> List.tryPick id
  28. // given a capability option, restrict it
  29. let restrict filter originalCap =
  30. originalCap
  31. |> Option.bind filter
  32. /// Uses of the capability will be audited
  33. let auditable capabilityName principalName f =
  34. fun x ->
  35. // simple audit log!
  36. let timestamp = DateTime.UtcNow.ToString("u")
  37. printfn "AUDIT: User %s used capability %s at %s" principalName capabilityName timestamp
  38. // use the capability
  39. f x
  40. /// Return a pair of functions: the revokable capability,
  41. /// and the revoker function
  42. let revokable f =
  43. let allow = ref true
  44. let capability = fun x ->
  45. if !allow then //! is dereferencing not negation!
  46. f x
  47. else
  48. Failure CapabilityRevoked
  49. let revoker() =
  50. allow := false
  51. capability, revoker

Implementing the database

The functions related to database access are similar to those in the earlier examples, only this time we have implemented a crude in-memory database (just a Dictionary).

  1. module CustomerDatabase =
  2. open Rop
  3. open System.Collections.Generic
  4. open Domain
  5. let private db = Dictionary<CustomerId,CustomerData>()
  6. let getCustomer id =
  7. match db.TryGetValue id with
  8. | true, value -> Success value
  9. | false, _ -> Failure (CustomerIdNotFound id)
  10. let updateCustomer id data =
  11. db.[id] <- data
  12. Success ()
  13. let updatePassword (id:CustomerId,password:Password) =
  14. Success () // dummy implementation

Implementing the business services

Next we have the “business services” (for lack of better word) where all the work gets done.

  1. module BusinessServices =
  2. open Rop
  3. open Domain
  4. // use the getCustomer capability
  5. let getCustomer capability =
  6. match capability() with
  7. | Success data -> printfn "%A" data
  8. | Failure err -> printfn ".. %A" err
  9. // use the updateCustomer capability
  10. let updateCustomer capability =
  11. printfn "Enter new data: "
  12. let customerData = Console.ReadLine() |> CustomerData
  13. match capability () customerData with
  14. | Success _ -> printfn "Data updated"
  15. | Failure err -> printfn ".. %A" err
  16. // use the updatePassword capability
  17. let updatePassword capability =
  18. printfn "Enter new password: "
  19. let password = Console.ReadLine() |> Password
  20. match capability password with
  21. | Success _ -> printfn "Password updated"
  22. | Failure err -> printfn ".. %A" err

Note that each of these functions is passed in only the capability needed to do its job. This code knows nothing about databases, or anything else.

Yes, in this crude example, the code is reading and writing directly to the console. Obviously in a more complex (and less crude!) design,
the inputs to these functions would be passed in as parameters.

Here’s a simple exercise: replace the direct access to the console with a capability such as getDataWithPrompt?

Implementing the user interface

Now for the user interface module, where most of the complex code lies.

First up is a type (CurrentState) that represents the state of the user interface.

  • When we’re LoggedOut there is no IPrincipal available.
  • When we’re LoggedIn there is a IPrincipal available, but no selected customer.
  • When we’re in the CustomerSelected state there is both a IPrincipal and a CustomerId available.
  • Finally, the Exit state is a signal to the app to shutdown.

I very much like using a “state” design like this, because it ensures that we can’t accidentally access data that we shouldn’t. For example, we literally cannot
access a customer when none is selected, because there is no customer id in that state!

For each state, there is a corresponding function.

loggedOutActions is run when we are in the LoggedOut state. It presents the available actions to you, and changes the state accordingly.
You can log in as a user, or exit. If the login is successful (authenticate name worked) then the state is changed to LoggedIn.

loggedInActions is run when we are in the LoggedIn state. You can select a customer, or log out.
If the customer selection is successful (customerIdForName customerName worked) then the state is changed to CustomerSelected.

selectedCustomerActions is run when we are in the CustomerSelected state. This works as follows:

  • First, find out what capabilities we have.
  • Next convert each capability into a corresponding menu text (using Option.map because the capability might be missing), then remove the ones that are None.
  • Next, read a line from input, and depending on what it is, call one of the “business services” (getCustomer, updateCustomer, or updatePassword).

Finally the mainUiLoop function loops around until the state is set to Exit.

  1. module UserInterface =
  2. open Rop
  3. open Domain
  4. open Capabilities
  5. type CurrentState =
  6. | LoggedOut
  7. | LoggedIn of IPrincipal
  8. | CustomerSelected of IPrincipal * CustomerId
  9. | Exit
  10. /// do the actions available while you are logged out. Return the new state
  11. let loggedOutActions originalState =
  12. printfn "[Login] enter Alice, Bob, Zelda, or Exit: "
  13. let action = Console.ReadLine()
  14. match action with
  15. | "Exit" ->
  16. // Change state to Exit
  17. Exit
  18. | name ->
  19. // otherwise try to authenticate the name
  20. match Authentication.authenticate name with
  21. | Success principal ->
  22. LoggedIn principal
  23. | Failure err ->
  24. printfn ".. %A" err
  25. originalState
  26. /// do the actions available while you are logged in. Return the new state
  27. let loggedInActions originalState (principal:IPrincipal) =
  28. printfn "[%s] Pick a customer to work on. Enter Alice, Bob, or Logout: " principal.Identity.Name
  29. let action = Console.ReadLine()
  30. match action with
  31. | "Logout" ->
  32. // Change state to LoggedOut
  33. LoggedOut
  34. // otherwise treat it as a customer name
  35. | customerName ->
  36. // Attempt to find customer
  37. match Authentication.customerIdForName customerName with
  38. | Success customerId ->
  39. // found -- change state
  40. CustomerSelected (principal,customerId)
  41. | Failure err ->
  42. // not found -- stay in originalState
  43. printfn ".. %A" err
  44. originalState
  45. let getAvailableCapabilities capabilityProvider customerId principal =
  46. let getCustomer = capabilityProvider.getCustomer customerId principal
  47. let updateCustomer = capabilityProvider.updateCustomer customerId principal
  48. let updatePassword = capabilityProvider.updatePassword customerId principal
  49. getCustomer,updateCustomer,updatePassword
  50. /// do the actions available when a selected customer is available. Return the new state
  51. let selectedCustomerActions originalState capabilityProvider customerId principal =
  52. // get the individual component capabilities from the provider
  53. let getCustomerCap,updateCustomerCap,updatePasswordCap =
  54. getAvailableCapabilities capabilityProvider customerId principal
  55. // get the text for menu options based on capabilities that are present
  56. let menuOptionTexts =
  57. [
  58. getCustomerCap |> Option.map (fun _ -> "(G)et");
  59. updateCustomerCap |> Option.map (fun _ -> "(U)pdate");
  60. updatePasswordCap |> Option.map (fun _ -> "(P)assword");
  61. ]
  62. |> List.choose id
  63. // show the menu
  64. let actionText =
  65. match menuOptionTexts with
  66. | [] -> " (no other actions available)"
  67. | texts -> texts |> List.reduce (fun s t -> s + ", " + t)
  68. printfn "[%s] (D)eselect customer, %s" principal.Identity.Name actionText
  69. // process the user action
  70. let action = Console.ReadLine().ToUpper()
  71. match action with
  72. | "D" ->
  73. // revert to logged in with no selected customer
  74. LoggedIn principal
  75. | "G" ->
  76. // use Option.iter in case we don't have the capability
  77. getCustomerCap
  78. |> Option.iter BusinessServices.getCustomer
  79. originalState // stay in same state
  80. | "U" ->
  81. updateCustomerCap
  82. |> Option.iter BusinessServices.updateCustomer
  83. originalState
  84. | "P" ->
  85. updatePasswordCap
  86. |> Option.iter BusinessServices.updatePassword
  87. originalState
  88. | _ ->
  89. // unknown option
  90. originalState
  91. let rec mainUiLoop capabilityProvider state =
  92. match state with
  93. | LoggedOut ->
  94. let newState = loggedOutActions state
  95. mainUiLoop capabilityProvider newState
  96. | LoggedIn principal ->
  97. let newState = loggedInActions state principal
  98. mainUiLoop capabilityProvider newState
  99. | CustomerSelected (principal,customerId) ->
  100. let newState = selectedCustomerActions state capabilityProvider customerId principal
  101. mainUiLoop capabilityProvider newState
  102. | Exit ->
  103. () // done
  104. let start capabilityProvider =
  105. mainUiLoop capabilityProvider LoggedOut

Implementing the top-level module

With all this in place, we can implement the top-level module.

This module fetches all the capabilities, adds restrictions as explained previously, and creates a capabilities record.

The capabilities record is then passed into the user interface when the app is started.

  1. module Application=
  2. open Rop
  3. open Domain
  4. open CustomerDatabase
  5. open Authentication
  6. open Authorization
  7. let capabilities =
  8. let getCustomerOnlyForSameId id principal =
  9. onlyForSameId id principal CustomerDatabase.getCustomer
  10. let getCustomerOnlyForAgentsInBusinessHours id principal =
  11. let f = CustomerDatabase.getCustomer
  12. let cap1 = onlyForAgents id principal f
  13. let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
  14. cap1 |> restrict restriction
  15. let getCustomerOnlyForSameId_OrForAgentsInBusinessHours id principal =
  16. let cap1 = getCustomerOnlyForSameId id principal
  17. let cap2 = getCustomerOnlyForAgentsInBusinessHours id principal
  18. first [cap1; cap2]
  19. let updateCustomerOnlyForSameId id principal =
  20. onlyForSameId id principal CustomerDatabase.updateCustomer
  21. let updateCustomerOnlyForAgentsInBusinessHours id principal =
  22. let f = CustomerDatabase.updateCustomer
  23. let cap1 = onlyForAgents id principal f
  24. let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
  25. cap1 |> restrict restriction
  26. let updateCustomerOnlyForSameId_OrForAgentsInBusinessHours id principal =
  27. let cap1 = updateCustomerOnlyForSameId id principal
  28. let cap2 = updateCustomerOnlyForAgentsInBusinessHours id principal
  29. first [cap1; cap2]
  30. let updatePasswordOnlyForSameId id principal =
  31. let cap = passwordUpdate id principal CustomerDatabase.updatePassword
  32. cap
  33. |> Option.map (auditable "UpdatePassword" principal.Identity.Name)
  34. // create the record that contains the capabilities
  35. {
  36. getCustomer = getCustomerOnlyForSameId_OrForAgentsInBusinessHours
  37. updateCustomer = updateCustomerOnlyForSameId_OrForAgentsInBusinessHours
  38. updatePassword = updatePasswordOnlyForSameId
  39. }
  40. let start() =
  41. // pass capabilities to UI
  42. UserInterface.start capabilities

The complete code for this example is available as a gist here.

Summary of Part 2

In part 2, we added authorization and other transforms as a separate concern that could be applied to restrict authority.
Again, there is nothing particularly clever about using functions like this, but I hope that this has given you some ideas that might be useful.

Question: Why go to all this trouble? What’s the benefit over just testing an “IsAuthorized” flag or something?

Here’s a typical use of a authorization flag:

  1. if user.CanUpdate() then
  2. doTheAction()

Recall the quote from the previous post: “Capabilities should ‘fail safe’. If a capability cannot be obtained, or doesn’t work, we must not allow any progress
on paths that assumed that it was successful.”

The problem with testing a flag like this is that it’s easy to forget, and the compiler won’t complain if you do.
And then you have a possible security breach, as in the following code.

  1. if user.CanUpdate() then
  2. // ignore
  3. // now do the action anyway!
  4. doTheAction()

Not only that, but by “inlining” the test like this, we’re mixing security concerns into our main code, as pointed out earlier.

In contrast, a simple capability approach looks like this:

  1. let updateCapability = // attempt to get the capability
  2. match updateCapability with
  3. | Some update -> update() // call the function
  4. | None -> () // can't call the function

In this example, it is not possible to accidentally use the capability if you are not allowed to, as you literally don’t have a function to call!
And this has to be handled at compile-time, not at runtime.

Furthermore, as we have just seen, capabilities are just functions, so we get all the benefits of filtering, etc., which are not available with the inlined boolean test version.

Question: In many situations, you don’t know whether you can access a resource until you try. So aren’t capabilities just extra work?

This is indeed true. For example, you might want to test whether a file exists first, and only then try to access it.
The IT gods are always ruthless in these cases, and in the time between checking the file’s existence and trying to open it, the file will probably be deleted!

So since we will have to check for exceptions anyway, why do two slow I/O operations when one would have sufficed?

The answer is that the capability model is not about physical or system-level authority, but logical authority — only having the minimum you need to accomplish a task.

For example, a web service process may be operating at a high level of system authority, and can access any database record.
But we don’t want to expose that to most of our code. We want to make sure that any failures in programming logic cannot accidentally expose unauthorized data.

Yes, of course, the capability functions themselves must do error handling,
and as you can see in the snippets above, I’m using the Success/Failure result type as described here.
As a result, we will need to merge failures from core functions (e.g. database errors) with capability-specific failures such as Failure OnlyAllowedOnce.

Question: You’ve created a whole module with types defined for each capability. I might have hundreds of capabilities. Do you really expect me to do all this extra work?

There are two points here, so let’s address each one in turn:

First, do you have a system that already uses fine-grained authorization,
or has business-critical requirements about not leaking data, or performing actions in an unauthorized context, or needs a security audit?

If none of these apply, then indeed, this approach is complete overkill!

But if you do have such a system, that raises some new questions:

  • should the capabilities that are authorized be explicitly described in the code somewhere?
  • and if so, should the capabilities be explicit throughout the code, or only at the top-level (e.g. in the controller) and implicit everywhere else.

The question comes to down to whether you want to be explicit or implicit.

Personally, I prefer things like this to be explicit. It may be a little extra work initially,
just a few lines to define each capability, but I find that it generally stops problems from occurring further down the line.

And it has the benefit of acting as a single place to document all the security-related capabilities that you support. Any new requirements would
require a new entry here, so can be sure that no capabilities can sneak in under the radar (assuming developers follow these practices).

Question: In this code, you’ve rolled your own authorization. Wouldn’t you use a proper authorization provider instead?

Yes. This code is just an example. The authorization logic is completely separate from the domain logic, so it should be easy to substitute any authorization provider, such as
ClaimsAuthorizationManager class, or something like
XACML.

I’ve got more questions…

If you missed them, some additional questions are answered at the end of part 1.
Otherwise please add your question in the comments below, and I’ll try to address it.

Coming up

In the next post, we’ll look at how to use types to emulate access tokens and prevent unauthorized access to global functions.

NOTE: All the code for this post is available as a gist here
and here.