Gate
This package offers basic authorization.
Documentation
First you need to initialize a shared instance somewhere in your code. So that you can be sure that your whole project uses the same policy rules.
let gate = Gate(for: User.self)
Defining policies
There are two types of policies. Policies for general types and policies that check specific objects of those types.
General type policies
To create a policy for just a general type, you write something like this:
gate.addPolicy(to: .create, a: Post.self) { user in
return user != nil // only authenticated users may create posts
}
Specific object policies
To create a policy where you need to check the specific object, you write:
gate.addPolicy(to: .update, a: Post.self) { user, post in
return user == post.author // users may only update their own posts
}
You see the only difference is which arguments you ask for in your closure.
Policies return an optional boolean. Returning nil
means "undecided, keep checking other policies". When you check the policy for a specific Post
, it will first try the specific-object policies and if they don't return an answer then it will try the general-type policies.
Actions you can create policies for
Besides .create
and .update
, you can also define policies for .delete
, .list
(which is used for index-pages) and .inspect
(which is used for view-pages).
Checking policies
There are several ways to check policies. You can check policies "softly", by only asking a boolean back.
func update(user: User, post: Post) throws {
if try gate.check(if: user, can: .update, this: post) {
// save updated post
} else {
// manually abort
}
}
Or you can make Gate
enforce its policy like so:
func update(user: User, post: Post) throws {
// if the user is not authorized then this will
// automatically throw an unauthorized error
try gate.ensure(that: user, can: .update, this: post)
// Save your updated post in the database here...
}
Of course in some cases you don't have a specific object to check against, that's why we created those general-type policies. You can use those like so:
func create(user: User, request: Request) throws {
try gate.ensure(that: user, can: .create, a: Post.self)
// Create your post here from the request data...
}
Since some types start with a vowel, you can also write "an" instead of "a"
func create(user: User, request: Request) throws {
try gate.ensure(that: user, can: .create, an: Image.self)
// Create your image here from the request data...
}
Convenience methods
Finally, if you make your User
conform to the Authorizable
protocol, it gets a few convenience methods:
if try user.can(.update, this: post) { ... }
if try user.can(.create, a: Post.self) { ... }
if try user.can(.create, an: Image.self) { ... }
if try user.cannot(.update, this: post) { ... }
if try user.cannot(.create, a: Post.self) { ... }
if try user.cannot(.create, an: Image.self) { ... }