Skip to content

Commit

Permalink
support space-delimited claims
Browse files Browse the repository at this point in the history
  • Loading branch information
et1975 committed Jun 5, 2020
1 parent 85d43e8 commit 11bf6a1
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 26 deletions.
12 changes: 7 additions & 5 deletions AAD.Giraffe/PartProtector.fs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ module PartProtector =

let introspect =
(TokenCache.mkDefault(), audiences, conf) |||> Introspector.mkNew
let inline filter claim =
ResourceOwner.ClaimFilters.isAppRole claim
|| ResourceOwner.ClaimFilters.isRole claim
|| ResourceOwner.ClaimFilters.isScope claim
let project claim =
seq {
yield! ResourceOwner.ClaimProjection.ofAppRole claim
yield! ResourceOwner.ClaimProjection.ofRole claim
yield! ResourceOwner.ClaimProjection.ofScope claim
}

return mkNew introspect
(ResourceOwner.validate '/' filter)
(ResourceOwner.validate '/' project)
audiences
conf
}
14 changes: 8 additions & 6 deletions AAD.Suave/PartProtector.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,15 @@ module PartProtector =

let introspect =
(TokenCache.mkDefault(), audiences, conf) |||> Introspector.mkNew
let inline filter claim =
ResourceOwner.ClaimFilters.isAppRole claim
|| ResourceOwner.ClaimFilters.isRole claim
|| ResourceOwner.ClaimFilters.isScope claim

let project claim =
seq {
yield! ResourceOwner.ClaimProjection.ofAppRole claim
yield! ResourceOwner.ClaimProjection.ofRole claim
yield! ResourceOwner.ClaimProjection.ofScope claim
}

return mkNew introspect
(ResourceOwner.validate '/' filter)
(ResourceOwner.validate '/' project)
audiences
conf
}
22 changes: 19 additions & 3 deletions AAD.Test/ResourceOwnerTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module Internals =
tokenHandler.CreateJwtSecurityToken(
oidcConfig.Issuer,
audience,
ClaimsIdentity([Claim(ClaimTypes.Role, "Test/read/*")]),
ClaimsIdentity([Claim("role", "Test/read/*")]),
Nullable DateTime.UtcNow,
Nullable (DateTime.UtcNow + (TimeSpan.FromHours 1.)),
Nullable (DateTime.UtcNow + (TimeSpan.FromHours 1.)),
Expand Down Expand Up @@ -84,16 +84,32 @@ module Internals =
let token = Introspection.Introspects()
let result =
token
|> Result.bind (ResourceOwner.validate '/' ResourceOwner.ClaimFilters.isRole (Pattern ["Test"; "read"; "A"]))
|> Result.bind (ResourceOwner.validate '/' ResourceOwner.ClaimProjection.ofRole (Pattern ["Test"; "read"; "A"]))
true =! Result.isOk result

[<Fact>]
let ``Scope demand is not satisfied`` () =
let token = Introspection.Introspects()
let result =
token
|> Result.bind (ResourceOwner.validate '/' ResourceOwner.ClaimFilters.isScope (Pattern ["Test"; "read"; "A"]))
|> Result.bind (ResourceOwner.validate '/' ResourceOwner.ClaimProjection.ofScope (Pattern ["Test"; "read"; "A"]))
true =! Result.isError result

[<Fact>]
let ``Scope demand is satisfied`` () =
let claims = Seq.singleton (Claim("scp", "Test.read.A Test.write.B"))
let token = JwtSecurityToken(claims = claims)
let result =
ResourceOwner.validate '.' ResourceOwner.ClaimProjection.ofScope (Pattern ["Test"; "read"; "A"]) token
true =! Result.isOk result

[<Fact>]
let ``AppRole demand is satisfied`` () =
let claims = [ Claim("roles", "Test/read/A"); Claim("roles", "Test/write/B") ]
let token = JwtSecurityToken(claims = claims)
let result =
ResourceOwner.validate '/' ResourceOwner.ClaimProjection.ofAppRole (Pattern ["Test"; "read"; "A"]) token
true =! Result.isOk result


/// Fixture to share initialization across all 3BodyProblem tests
Expand Down
25 changes: 13 additions & 12 deletions AAD.fs/ResourceOwner.fs
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,18 @@ module ResourceOwner =
open Microsoft.IdentityModel.Protocols.OpenIdConnect
open System.Security.Claims

module ClaimFilters =
let isScope (claim: Claim) =
claim.Type = "scp"
|| claim.Type = "http://schemas.microsoft.com/identity/claims/scope"
module ClaimProjection =
let ofScope (claim: Claim) : seq<string> =
if claim.Type = "scp" then claim.Value |> String.split ' ' :> _
else Seq.empty

let isRole (claim: Claim) =
claim.Type = "role"
|| claim.Type = ClaimTypes.Role
let ofRole (claim: Claim) : seq<string> =
if claim.Type = "role" then Seq.singleton claim.Value
else Seq.empty

let isAppRole (claim: Claim) =
claim.Type = "roles"
let ofAppRole (claim: Claim) : seq<string> =
if claim.Type = "roles" then Seq.singleton claim.Value
else Seq.empty

let mkNew (introspect: TokenString -> Awaitable<Result<JwtSecurityToken,string>>)
(validate: Demand -> JwtSecurityToken -> Result<JwtSecurityToken,string>)
Expand Down Expand Up @@ -118,11 +119,11 @@ module ResourceOwner =
}
}

let validate (splitChar: char) (claimsFilter: Claim -> bool) (demand: Demand) (t: JwtSecurityToken) =
let validate (splitChar: char) (claimsProjection: Claim -> #seq<string>) (demand: Demand) (t: JwtSecurityToken) =
let claims =
t.Claims
|> Seq.filter claimsFilter
|> Seq.map (fun c -> c.Value |> String.split splitChar)
|> Seq.collect claimsProjection
|> Seq.map (String.split splitChar)
demand
|> Demand.eval claims
|> function true -> Ok t | _ -> Error (sprintf "Demand not met: %A" demand)
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 1.3.0

* Breakup space-delimited claims into multiple entries

### 1.2.0

* OSS release
Expand Down

0 comments on commit 11bf6a1

Please sign in to comment.