diff --git a/README.md b/README.md index 2c7311d..ef5ef1c 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ The package can be installed by adding `rajska` to your list of dependencies in ```elixir def deps do [ - {:rajska, "~> 0.8.1"}, + {:rajska, "~> 0.9.0"}, ] end ``` ## Usage -Create your Authorization module, which will implement the [Rajska Authorization](https://hexdocs.pm/rajska/Rajska.Authorization.html) behaviour and contain the logic to validate user permissions and will be called by Rajska middlewares. Rajska provides some helper functions by default, such as [role_authorized?/2](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:role_authorized?/2), [has_user_access?/4](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/4) and [field_authorized?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:field_authorized?/3), but you can override them with your application needs. +Create your Authorization module, which will implement the [Rajska Authorization](https://hexdocs.pm/rajska/Rajska.Authorization.html) behaviour and contain the logic to validate user permissions and will be called by Rajska middlewares. Rajska provides some helper functions by default, such as [role_authorized?/2](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:role_authorized?/2), [has_user_access?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/3) and [field_authorized?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:field_authorized?/3), but you can override them with your application needs. ```elixir defmodule Authorization do @@ -127,14 +127,14 @@ In the above example, `:all` and `:admin` (`super_role`) permissions don't requi ## Options -All the following options are sent to [has_user_access?/4](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/4): +All the following options are sent to [has_user_access?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/3): * `:scope` - `false`: disables scoping - - `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. It must define a struct. + - `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/3`. It must define a struct. * `:args` - `%{user_id: [:params, :id]}`: where `user_id` is the scoped field and `id` is an argument nested inside the `params` argument. - - `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to [has_user_access?/4](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/4) + - `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to [has_user_access?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/3) - `[:code, :user_group_id]`: this is the same as `%{code: :code, user_group_id: :user_group_id}`, where `code` and `user_group_id` are both query arguments and scoped fields. * `:optional` (optional) - when set to true the arguments are optional, so if no argument is provided, the query will be authorized. Defaults to false. * `:rule` (optional) - allows the same struct to have different rules. See `Rajska.Authorization` for `rule` default settings. @@ -225,7 +225,7 @@ object :wallet do end ``` -To define custom rules for the scoping, use [has_user_access?/4](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/4). For example: +To define custom rules for the scoping, use [has_user_access?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/3). For example: ```elixir defmodule Authorization do @@ -234,13 +234,13 @@ defmodule Authorization do super_role: :admin @impl true - def has_user_access?(%{role: :admin}, User, _field, _rule), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, _rule) when user_id === id, do: true - def has_user_access?(_current_user, User, _field, _rule), do: false + def has_user_access?(%{role: :admin}, %User{}, _rule), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, _rule) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, _rule), do: false end ``` -Keep in mind that the `field_value` provided to `has_user_access?/4` can be `nil`. This case can be handled as you wish. +Keep in mind that the `field_value` provided to `has_user_access?/3` can be `nil`. This case can be handled as you wish. For example, to not raise any authorization errors and just return `nil`: ```elixir @@ -250,11 +250,9 @@ defmodule Authorization do super_role: :admin @impl true - def has_user_access?(_user, _scope, {_field, nil}, _rule), do: true - - def has_user_access?(%{role: :admin}, User, _field, _rule), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, _rule) when user_id === id, do: true - def has_user_access?(_current_user, User, _field, _rule), do: false + def has_user_access?(%{role: :admin}, %User{}, _rule), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, _rule) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, _rule), do: false end ``` diff --git a/lib/authorization.ex b/lib/authorization.ex index 6033f52..a0dc226 100644 --- a/lib/authorization.ex +++ b/lib/authorization.ex @@ -21,8 +21,7 @@ defmodule Rajska.Authorization do @callback has_user_access?( current_user, - scope :: module(), - {field :: any(), field_value :: any()}, + scoped_struct :: struct(), rule :: any() ) :: boolean() @@ -33,6 +32,6 @@ defmodule Rajska.Authorization do not_scoped_roles: 0, role_authorized?: 2, field_authorized?: 3, - has_user_access?: 4, + has_user_access?: 3, unauthorized_msg: 1 end diff --git a/lib/middlewares/object_scope_authorization.ex b/lib/middlewares/object_scope_authorization.ex index 11c3611..2447352 100644 --- a/lib/middlewares/object_scope_authorization.ex +++ b/lib/middlewares/object_scope_authorization.ex @@ -39,7 +39,7 @@ defmodule Rajska.ObjectScopeAuthorization do end ``` - To define custom rules for the scoping, use `c:Rajska.Authorization.has_user_access?/4`. For example: + To define custom rules for the scoping, use `c:Rajska.Authorization.has_user_access?/3`. For example: ```elixir defmodule Authorization do @@ -47,13 +47,13 @@ defmodule Rajska.ObjectScopeAuthorization do valid_roles: [:user, :admin] @impl true - def has_user_access?(%{role: :admin}, _struct, {_field, _field_value}, _rule), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, _rule) when user_id === id, do: true - def has_user_access?(_current_user, User, {_field, _field_value}, _rule), do: false + def has_user_access?(%{role: :admin}, _, _scoped_struct, _rule), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, _rule) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, _rule), do: false end ``` - Keep in mind that the `field_value` provided to `has_user_access?/4` can be `nil`. This case can be handled as you wish. + Keep in mind that the `field_value` provided to `has_user_access?/3` can be `nil`. This case can be handled as you wish. For example, to not raise any authorization errors and just return `nil`: ```elixir @@ -62,15 +62,13 @@ defmodule Rajska.ObjectScopeAuthorization do valid_roles: [:user, :admin] @impl true - def has_user_access?(_user, _scope, {_field, nil}, _rule), do: true - - def has_user_access?(%{role: :admin}, User, {_field, _field_value}, _rule), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, _rule) when user_id === id, do: true - def has_user_access?(_current_user, User, {_field, _field_value}, _rule), do: false + def has_user_access?(%User{role: :admin}, _scoped_struct, _rule), do: true + def has_user_access?(%User{id: user_id}, %User{id: id}, _rule) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, _rule), do: false end ``` - The `rule` keyword is not mandatory and will be pattern matched in `has_user_access?/4`: + The `rule` keyword is not mandatory and will be pattern matched in `has_user_access?/3`: ```elixir defmodule Authorization do @@ -78,8 +76,8 @@ defmodule Rajska.ObjectScopeAuthorization do valid_roles: [:user, :admin] @impl true - def has_user_access?(%{id: user_id}, Wallet, {_field, _field_value}, :read_only), do: true - def has_user_access?(%{id: user_id}, Wallet, {_field, _field_value}, :default), do: false + def has_user_access?(%{id: user_id}, %Wallet{}, :read_only), do: true + def has_user_access?(%{id: user_id}, %Wallet{}, :default), do: false end ``` diff --git a/lib/middlewares/query_scope_authorization.ex b/lib/middlewares/query_scope_authorization.ex index ab18bb3..efaf907 100644 --- a/lib/middlewares/query_scope_authorization.ex +++ b/lib/middlewares/query_scope_authorization.ex @@ -43,14 +43,14 @@ defmodule Rajska.QueryScopeAuthorization do ## Options - All the following options are sent to `c:Rajska.Authorization.has_user_access?/4`: + All the following options are sent to `c:Rajska.Authorization.has_user_access?/3`: * `:scope` - `false`: disables scoping - - `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. It must define a struct. + - `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/3`. It must define a struct. * `:args` - `%{user_id: [:params, :id]}`: where `user_id` is the scoped field and `id` is an argument nested inside the `params` argument. - - `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to `c:Rajska.Authorization.has_user_access?/4` + - `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to `c:Rajska.Authorization.has_user_access?/3` - `[:code, :user_group_id]`: this is the same as `%{code: :code, user_group_id: :user_group_id}`, where `code` and `user_group_id` are both query arguments and scoped fields. * `:optional` (optional) - when set to true the arguments are optional, so if no argument is provided, the query will be authorized. Defaults to false. * `:rule` (optional) - allows the same struct to have different rules. See `Rajska.Authorization` for `rule` default settings. diff --git a/lib/rajska.ex b/lib/rajska.ex index 075ad29..b2cfb79 100644 --- a/lib/rajska.ex +++ b/lib/rajska.ex @@ -16,14 +16,14 @@ defmodule Rajska do ```elixir def deps do [ - {:rajska, "~> 0.8.1"}, + {:rajska, "~> 0.9.0"}, ] end ``` ## Usage - Create your Authorization module, which will implement the `Rajska.Authorization` behaviour and contain the logic to validate user permissions and will be called by Rajska middlewares. Rajska provides some helper functions by default, such as `c:Rajska.Authorization.role_authorized?/2`, `c:Rajska.Authorization.has_user_access?/4` and `c:Rajska.Authorization.field_authorized?/3`, but you can override them with your application needs. + Create your Authorization module, which will implement the `Rajska.Authorization` behaviour and contain the logic to validate user permissions and will be called by Rajska middlewares. Rajska provides some helper functions by default, such as `c:Rajska.Authorization.role_authorized?/2`, `c:Rajska.Authorization.has_user_access?/3` and `c:Rajska.Authorization.field_authorized?/3`, but you can override them with your application needs. ```elixir defmodule Authorization do @@ -100,12 +100,9 @@ defmodule Rajska do def role_authorized?(user_role, allowed_role) when is_atom(allowed_role), do: user_role === allowed_role def role_authorized?(user_role, allowed_roles) when is_list(allowed_roles), do: user_role in allowed_roles - def has_user_access?(%user_struct{id: user_id} = current_user, scope, {field, field_value}, unquote(default_rule)) do + def has_user_access?(%user_struct{id: user_id} = current_user, %scope{} = struct, unquote(default_rule)) do super_user? = current_user |> get_user_role() |> super_role?() - owner? = - (user_struct === scope) - && (field === :id) - && (user_id === field_value) + owner? = (user_struct === scope) && (user_id === struct.id) super_user? || owner? end @@ -129,7 +126,7 @@ defmodule Rajska do def has_context_access?(context, scope, {scope_field, field_value}, rule) do context |> get_current_user() - |> has_user_access?(scope, {scope_field, field_value}, rule) + |> has_user_access?(scope.__struct__([{scope_field, field_value}]), rule) end defoverridable Authorization diff --git a/lib/schema.ex b/lib/schema.ex index c056f92..ad04e32 100644 --- a/lib/schema.ex +++ b/lib/schema.ex @@ -3,11 +3,8 @@ defmodule Rajska.Schema do Concatenates Rajska middlewares with Absinthe middlewares and validates Query Authorization configuration. """ - alias Absinthe.Type.{ - Field, - Middleware, - Object - } + alias Absinthe.Middleware + alias Absinthe.Type.{Field, Object} alias Rajska.{ FieldAuthorization, diff --git a/mix.exs b/mix.exs index 8421d63..7fbf4f5 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Rajska.MixProject do def project do [ app: :rajska, - version: "0.8.1", + version: "0.9.0", elixir: "~> 1.8", start_permanent: Mix.env() == :prod, deps: deps(), @@ -13,12 +13,14 @@ defmodule Rajska.MixProject do description: "Rajska is an authorization library for Absinthe.", package: package(), elixirc_paths: elixirc_paths(Mix.env()), + aliases: aliases(), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, - "coveralls.html": :test + "coveralls.html": :test, + "test.all": :test ] ] end @@ -51,4 +53,13 @@ defmodule Rajska.MixProject do {:excoveralls, "~> 0.11", only: :test}, ] end + + defp aliases do + [ + "test.all": [ + "credo --strict", + "test" + ] + ] + end end diff --git a/test/middlewares/field_authorization_test.exs b/test/middlewares/field_authorization_test.exs index c9fff0c..87ee38b 100644 --- a/test/middlewares/field_authorization_test.exs +++ b/test/middlewares/field_authorization_test.exs @@ -17,10 +17,10 @@ defmodule Rajska.FieldAuthorizationTest do valid_roles: [:user, :admin], super_role: :admin - def has_user_access?(_current_user, User, _field, :private), do: false - def has_user_access?(%{role: :admin}, User, _field, :default), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, :default) when user_id === id, do: true - def has_user_access?(_current_user, User, _field, :default), do: false + def has_user_access?(_current_user, %User{}, :private), do: false + def has_user_access?(%{role: :admin}, %User{}, :default), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, :default) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, :default), do: false end defmodule Schema do diff --git a/test/middlewares/object_scope_authorization_test.exs b/test/middlewares/object_scope_authorization_test.exs index 7b62109..9c61d25 100644 --- a/test/middlewares/object_scope_authorization_test.exs +++ b/test/middlewares/object_scope_authorization_test.exs @@ -5,6 +5,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do defstruct [ id: 1, total: 10, + user_id: nil ] end @@ -39,18 +40,18 @@ defmodule Rajska.ObjectScopeAuthorizationTest do valid_roles: [:user, :admin], super_role: :admin - def has_user_access?(%{role: :admin}, User, _field, :default), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, :default) when user_id === id, do: true - def has_user_access?(_current_user, User, _field, :default), do: false - def has_user_access?(_current_user, User, _field, :object), do: false + def has_user_access?(%{role: :admin}, %User{}, :default), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, :default) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, :default), do: false + def has_user_access?(_current_user, %User{}, :object), do: false - def has_user_access?(%{role: :admin}, Company, _field, :default), do: true - def has_user_access?(%{id: user_id}, Company, {:user_id, company_user_id}, :default) when user_id === company_user_id, do: true - def has_user_access?(_current_user, Company, _field, :default), do: false + def has_user_access?(%{role: :admin}, %Company{}, :default), do: true + def has_user_access?(%{id: user_id}, %Company{user_id: company_user_id}, :default) when user_id === company_user_id, do: true + def has_user_access?(_current_user, %Company{}, :default), do: false - def has_user_access?(%{role: :admin}, Wallet, _field, :default), do: true - def has_user_access?(%{id: user_id}, Wallet, {:user_id, id}, :default) when user_id === id, do: true - def has_user_access?(_current_user, Wallet, _field, :default), do: false + def has_user_access?(%{role: :admin}, %Wallet{}, :default), do: true + def has_user_access?(%{id: user_id}, %Wallet{user_id: id}, :default) when user_id === id, do: true + def has_user_access?(_current_user, %Wallet{}, :default), do: false end defmodule Schema do diff --git a/test/middlewares/query_scope_authorization_test.exs b/test/middlewares/query_scope_authorization_test.exs index bf003da..4e88c18 100644 --- a/test/middlewares/query_scope_authorization_test.exs +++ b/test/middlewares/query_scope_authorization_test.exs @@ -21,12 +21,12 @@ defmodule Rajska.QueryScopeAuthorizationTest do valid_roles: [:user, :admin], super_role: :admin - def has_user_access?(%{role: :admin}, User, _field, :default), do: true - def has_user_access?(%{id: user_id}, User, {:id, id}, :default) when user_id === id, do: true - def has_user_access?(_current_user, User, _field, :default), do: false + def has_user_access?(%{role: :admin}, %User{}, :default), do: true + def has_user_access?(%{id: user_id}, %User{id: id}, :default) when user_id === id, do: true + def has_user_access?(_current_user, %User{}, :default), do: false - def has_user_access?(_current_user, BankAccount, _field, :edit), do: false - def has_user_access?(_current_user, BankAccount, _field, :read_only), do: true + def has_user_access?(_current_user, %BankAccount{}, :edit), do: false + def has_user_access?(_current_user, %BankAccount{}, :read_only), do: true end defmodule Schema do