Skip to content

Commit

Permalink
Merge 0365bc9 into ab73806
Browse files Browse the repository at this point in the history
  • Loading branch information
rschef committed Nov 7, 2019
2 parents ab73806 + 0365bc9 commit e70d9cd
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 68 deletions.
28 changes: 13 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
```

Expand Down
5 changes: 2 additions & 3 deletions lib/authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
24 changes: 11 additions & 13 deletions lib/middlewares/object_scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ 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
use Rajska,
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
Expand All @@ -62,24 +62,22 @@ 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
use Rajska,
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
```
Expand Down
6 changes: 3 additions & 3 deletions lib/middlewares/query_scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 5 additions & 8 deletions lib/rajska.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 2 additions & 5 deletions lib/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 13 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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
Expand Down Expand Up @@ -51,4 +53,13 @@ defmodule Rajska.MixProject do
{:excoveralls, "~> 0.11", only: :test},
]
end

defp aliases do
[
"test.all": [
"credo --strict",
"test"
]
]
end
end
8 changes: 4 additions & 4 deletions test/middlewares/field_authorization_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 11 additions & 10 deletions test/middlewares/object_scope_authorization_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
defstruct [
id: 1,
total: 10,
user_id: nil
]
end

Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions test/middlewares/query_scope_authorization_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e70d9cd

Please sign in to comment.