Authorization
Masonite also provides a simple way to authorize user actions against a given resource. This is achieved with two concepts: gates and policies. Gates are as the name suggests an authorization check that you will be able to invoke to verify user access. Policies are a way to groupe authorization logic around a model.
Gates are simple callable that will define if a user is authorized to perform a given action. A handy
Gate
facade is available to easily manipulate gates.Gates receive a user instance as their first argument and may receive additionals arguments such as a Model instance. You needs to define
Gates
in boot()
method of your service provider.In the following example we are adding gates to verify if a user can create and update posts. Users can create posts if they are administrators and can update posts they have created only.
from masonite.facades import Gate
class MyAppProvider(Provider):
def boot(self):
Gate.define("create-post", lambda user: user.is_admin)
Gate.define("update-post", lambda user, post: post.user_id == user.id)
You can then check if a gate exists or has been registed by using
has()
which is returning a boolean:Gate.has("create-post")
If a unknown gate is used a
GateDoesNotExist
exception will be raised.Then anywhere (often in a controller) in your code you can use those gates to check if the current authenticated user is authorized to perform the given action defined by the gate.
Gates exposes different methods to perform verification:
allows()
, denies()
, none()
, any()
, authorize()
inspect()
.allows()
, denies()
, none()
and any()
return a boolean indicating if user is authorizedif not Gate.allows("create-post"):
return response.redirect("/")
# ...
post = Post.find(request.input("id"))
if Gate.denies("update-post", post):
return response.redirect("/")
# ...
Gate.any(["delete-post", "update-post"], post)
# ...
Gate.none(["force-delete-post", "restore-post"], post)
authorize()
does not return a boolean but will raise an AuthorizationException
exception instead that will be rendered as an HTTP response with a 403 status code.Gate.authorize("update-post", post)
# if we reach this part user is authorized
# else an exception has been raised and be rendered as a 403 with content "Action not authorized".
Finally for better control over the authorization check you can analyse the response with
inspect()
:response = Gate.inspect("update-post", post)
if response.allowed():
# do something
else:
# not authorized and we can access message
Session.flash("errors", response.message())
Authorizes
class can be added to your User model to allow quick permission checks:from masonite.authentication import Authenticates
from masonite.authorization import Authorizes
class User(Model, Authenticates, Authorizes):
#..
A fluent authorization api will now be available on
User
instances:user.can("delete-post", post)
user.cannot("access-admin")
user.can_any(["delete-post", "force-delete-post"], post)
All of those methods receive the gate name as first argument and then some additional arguments if required.
You can use the
for_user()
method on the Gate facade to make the verification against a given user instead of the authenticated user.from masonite.facades import Gate
user = User.find(1)
Gate.for_user(user).allows("create-post")
During the gate authorization process,
before
and after
hooks can be triggered.A
before
hook can be added like this:# here admin users will always be authorized based on the boolean value of this response
Gate.before(lambda user, permission : user.role == "admin")
The
after
hook works the same way:Gate.after(lambda user, permission, result : user.role == "admin")
If the
after
callback is returning a value it will take priority over the gate result check.Policies are classes that organize authorization logic around a specific model.
You can run the craft command:
$ python craft policy AccessAdmin
You can also create a policy with a set of predefined gates by using the
--model
flag:$ python craft policy Post --model
A model policy comes with common actions that we can perform on a model:
from masonite.authorization import Policy
class PostPolicy(Policy):
def create(self, user):
return False
def view_any(self, user):
return False
def view(self, user, instance):
return False
def update(self, user, instance):
return False
def delete(self, user, instance):
return False
def force_delete(self, user, instance):
return False
def restore(self, user, instance):
return False
You are free to add any other methods on your policies:
from masonite.authorization import Policy
class PostPolicy(Policy):
#..
def publish(self, user):
return user.email == "[email protected]"
Then in your service provider (as for defining gates) you should register the policies and bind them with a model:
from masonite.facades import Gate
from app.models.Post import Post
from app.models.User import User
from app.policies.PostPolicy import PostPolicy
from app.policies.UserPolicy import UserPolicy
class MyAppProvider(Provider):
#..
def register(self):
Gate.register_policies(
[(Post, PostPolicy), (User, UserPolicy)],
)
An example policy for the Post model may look like this:
from masonite.authorization import Policy
class PostPolicy(Policy):
def create(self, user):
return user.email == "[email protected]"
def view(self, user, instance):
return True
def update(self, user, instance):
return user.id == instance.user_id
If an unknown policy is used then a
PolicyDoesNotExist
exception will be raised.You can then use the
Gate
facade methods to authorize actions defined in your policies. With the previously defined PostPolicy
we could make the following calls:from masonite.facades import Gate
post = Post.find(1)
Gate.allows("update", post)
Gate.denies("view", post)
Gate.allows("force_delete", post)
The
create()
or view_any()
methods do not take a model instance, that is why the model class should be provided so that Gate mechanism can infer from which policy those methods are belonging to.Gate.allows("create", Post)
Gate.allows("view_any", Post)
Last modified 1yr ago