Skip to content

Commit

Permalink
add checkAllPolicies config option
Browse files Browse the repository at this point in the history
  • Loading branch information
Evertt committed May 13, 2018
1 parent e4f6e08 commit fdb0e81
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 19 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,20 @@ So this policy says "I _do_ care if a user is authenticated and if s/he is not,

By the way, it's okay to have multiple `before` policies and multiple "normal" policies for the same object. The gate will just check them one by one and the first one that returns anything other than nil will decide what the user can and cannot do.

### Finally
### Give rights, or take rights

In the above examples the policies gave rights to the user. If you'd check an ability for which no policy exists (e.g. `user.can(.delete, user)`) then the gate defaults to `false`. However, that is only because right now the `Gate` is configured such that policies give rights. You can also configure `Gate` such that policies take rights away and in that case, if no relevant policy is defined for a particular use-case, `Gate` defaults to `true`.
In the above examples the policies gave rights to the user. If you'd check an ability for which no policy exists (e.g. `user.can(.delete, user)`) then the gate defaults to `false`. However, that is only because right now the `Gate` is configured such that policies give rights so the starting point is that no rights are given yet. You can also configure `Gate` such that policies take rights away and in that case, if no relevant policy is defined for a particular use-case, `Gate` defaults to all rights being given.

Here's how you define `Gate` such that policies take rights away.

```swift
let gate = Gate<Ability>(mode: .takeRights)
```

### Finally

By default `Gate` is configured to only listen to the first policy that returns a non-nil response. However, you can also configure `Gate` to listen to all policies and add up all the rights they give or take away (depending on which mode you're in).

```swift
let gate = Gate<Ability>(checkAllPolicies: true)
```
30 changes: 14 additions & 16 deletions Sources/Gate/Gate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ public class Gate<Ability: AbilitySet> {
}

let mode: Mode
let checkAllPolicies: Bool
var policies: [TypeTuple:[Any]] = [:]

public init(mode: Mode = .giveRights) {
public init(mode: Mode = .giveRights, checkAllPolicies: Bool = false) {
self.mode = mode
self.checkAllPolicies = checkAllPolicies
}
}

Expand Down Expand Up @@ -45,10 +47,10 @@ extension Gate {
if let generalAbilities = getAbilities(
from: policies[User.self, Any.self],
user: user, object: object
) {
), !checkAllPolicies {
return hasPermission(for: ability, given: generalAbilities)
}

if let specificAbilities = getAbilities(
from: policies[User.self, Object.self],
user: user, object: object
Expand All @@ -72,21 +74,17 @@ extension Gate {
}

private func getAbilities<User,Object>(from policies: [Policy<User,Object,Ability>], user: User?, object: Object?) -> Ability? {
for policy in policies {
if let abilities = policy.getAbilities(user, object) {
return abilities
return policies.reduce(nil) { result, policy in
if !checkAllPolicies && result != nil {
return result
}

guard let abilities = policy.getAbilities(user, object) else {
return result
}

return (result ?? []).union(abilities)
}

return nil

// return policies.reduce(Ability?.none) { result, policy in
// guard let abilities = policy.getAbilities(user, object) else {
// return result
// }
//
// return (result ?? []).union(abilities)
// }
}

private func hasPermission(for ability: Ability, given abilities: Ability) -> Bool {
Expand Down
21 changes: 20 additions & 1 deletion Tests/GateTests/GateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ struct Post {
}

extension User: Authorizable {
static let gate = Gate<Ability>()
static var gate = Gate<Ability>()
}

final class GateTests: XCTestCase {
func testExample() {
User.gate = Gate<Ability>()
let gate = User.gate

gate.before {
Expand Down Expand Up @@ -74,9 +75,27 @@ final class GateTests: XCTestCase {

XCTAssertThrowsError(try gate.ensure(jane, can: .update, johnsPost))
}

func testCheckingAllPolicies() {
let jane = User(name: "Jane", isSuperAdmin: false)

var gate = Gate<Ability>(checkAllPolicies: false)
setUpPolicies(on: gate)
XCTAssert(gate.check(jane, cannot: .read, Any.self))

gate = Gate<Ability>(checkAllPolicies: true)
setUpPolicies(on: gate)
XCTAssert(gate.check(jane, can: .read, Any.self))
}

func setUpPolicies(on gate: Gate<Ability>) {
gate.before { (user: User) in user.isSuperAdmin ? [.create, .read, .update, .delete] : [] }

gate.before { (user: User?) in user != nil ? .read : nil }
}

static var allTests = [
("testExample", testExample),
("testCheckingAllPolicies", testCheckingAllPolicies),
]
}

0 comments on commit fdb0e81

Please sign in to comment.