Skip to content

Commit 866c2bb

Browse files
authored
Update the code (#87)
1 parent ae03a75 commit 866c2bb

File tree

9 files changed

+1366
-879
lines changed

9 files changed

+1366
-879
lines changed

.formatter.exs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
import_deps: [:plug, :phoenix, :phoenix_live_view],
3+
inputs: [
4+
"lib/**/*.ex",
5+
"config/*.exs",
6+
"test/**/*.exs",
7+
"mix.exs"
8+
]
9+
]

.github/workflows/elixir.yml

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
branches:
7+
- master
8+
9+
jobs:
10+
mix_test:
11+
name: mix test (OTP ${{matrix.otp}} | Elixir ${{matrix.elixir}})
12+
13+
strategy:
14+
matrix:
15+
include:
16+
- elixir: "1.18"
17+
otp: "27.2"
18+
- elixir: "1.14"
19+
otp: "25.3"
20+
21+
runs-on: ubuntu-20.04
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- name: Install Erlang and Elixir
27+
uses: erlef/setup-beam@v1
28+
with:
29+
otp-version: ${{ matrix.otp }}
30+
elixir-version: ${{ matrix.elixir }}
31+
32+
- name: Restore deps and _build cache
33+
uses: actions/cache@v4
34+
with:
35+
path: |
36+
deps
37+
_build
38+
key: deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}
39+
restore-keys: |
40+
deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}
41+
42+
- name: Install dependencies
43+
run: mix deps.get --only test
44+
45+
- name: Remove compiled application files
46+
run: mix clean
47+
48+
- name: Compile & lint dependencies
49+
run: mix compile --warnings-as-errors
50+
env:
51+
MIX_ENV: test
52+
53+
- name: Run tests
54+
run: mix test

.travis.yml

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
language: elixir
22
elixir:
3+
- 1.8
4+
- 1.7
5+
- 1.6
6+
- 1.5
37
- 1.4
4-
- 1.3
8+
59
otp_release:
6-
- 20.0
10+
- 21.1
11+
- 20.3
712
- 19.3
813
- 18.3
14+
915
matrix:
1016
exclude:
17+
- elixir: 1.8
18+
otp_release: 19.3
19+
- elixir: 1.8
20+
otp_release: 18.3
21+
- elixir: 1.7
22+
otp_release: 18.3
23+
- elixir: 1.6
24+
otp_release: 18.3
25+
- elixir: 1.5
26+
otp_release: 21.1
27+
- elixir: 1.4
28+
otp_release: 21.1
1129
- elixir: 1.3
12-
otp_release: 20.0
30+
otp_release: 21.1
31+
- elixir: 1.3
32+
otp_release: 20.3
1333
script: mix test && mix credo

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Canary
22
======
3-
[![Build Status](https://travis-ci.org/cpjk/canary.svg?branch=master)](https://travis-ci.org/cpjk/canary)
3+
[![Actions Status](https://github.com/cpjk/canary/workflows/CI/badge.svg)](https://github.com/runhyve/canary/actions?query=workflow%3ACI)
44
[![Hex pm](https://img.shields.io/hexpm/v/canary.svg?style=flat)](https://hex.pm/packages/canary)
55

66
An authorization library in Elixir for Plug applications that restricts what resources
@@ -16,7 +16,7 @@ For the latest master:
1616

1717
```elixir
1818
defp deps do
19-
{:canary, github: "cpjk/canary"}
19+
{:canary, github: "runhyve/canary"}
2020
end
2121
```
2222

config/config.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This file is responsible for configuring your application
22
# and its dependencies with the aid of the Mix.Config module.
3-
use Mix.Config
3+
import Config
44

55
# This configuration is loaded before any dependency and is restricted
66
# to this project. If another project depends on this project, this

lib/canary/plugs.ex

+61-26
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ defmodule Canary.Plugs do
7070
* `:id_field` - Specifies the name of the ID field in the database for searching :id_name value, defaults to "id".
7171
* `:persisted` - Specifies the resource should always be loaded from the database, defaults to false
7272
* `:not_found_handler` - Specify a handler function to be called if the resource is not found
73+
* `:required` - Same as `:persisted` but with not found handler - even for :index, :new or :create action
7374
7475
Examples:
7576
```
@@ -104,10 +105,13 @@ defmodule Canary.Plugs do
104105
cond do
105106
is_persisted ->
106107
fetch_resource(conn, opts)
108+
107109
action == :index ->
108110
fetch_all(conn, opts)
111+
109112
action in [:new, :create] ->
110113
nil
114+
111115
true ->
112116
fetch_resource(conn, opts)
113117
end
@@ -162,9 +166,12 @@ defmodule Canary.Plugs do
162166

163167
defp do_authorize_controller(conn, opts) do
164168
controller = conn.assigns[:canary_controller] || conn.private[:phoenix_controller]
165-
current_user_name = opts[:current_user] ||
166-
Application.get_env(:canary, :current_user, :current_user)
167-
current_user = Map.fetch! conn.assigns, current_user_name
169+
170+
current_user_name =
171+
opts[:current_user] ||
172+
Application.get_env(:canary, :current_user, :current_user)
173+
174+
current_user = Map.fetch!(conn.assigns, current_user_name)
168175
action = get_action(conn)
169176

170177
Plug.Conn.assign(conn, :authorized, can?(current_user, action, controller))
@@ -252,25 +259,31 @@ defmodule Canary.Plugs do
252259
end
253260

254261
defp do_authorize_resource(conn, opts) do
255-
current_user_name = opts[:current_user] || Application.get_env(:canary, :current_user, :current_user)
256-
current_user = Map.fetch! conn.assigns, current_user_name
262+
current_user_name =
263+
opts[:current_user] || Application.get_env(:canary, :current_user, :current_user)
264+
265+
current_user = Map.fetch!(conn.assigns, current_user_name)
257266
action = get_action(conn)
258267
is_persisted = persisted?(opts)
268+
259269
non_id_actions =
260270
if opts[:non_id_actions] do
261271
Enum.concat([:index, :new, :create], opts[:non_id_actions])
262272
else
263273
[:index, :new, :create]
264274
end
265275

266-
resource = cond do
267-
is_persisted ->
268-
fetch_resource(conn, opts)
269-
action in non_id_actions ->
270-
opts[:model]
271-
true ->
272-
fetch_resource(conn, opts)
273-
end
276+
resource =
277+
cond do
278+
is_persisted ->
279+
fetch_resource(conn, opts)
280+
281+
action in non_id_actions ->
282+
opts[:model]
283+
284+
true ->
285+
fetch_resource(conn, opts)
286+
end
274287

275288
Plug.Conn.assign(conn, :authorized, can?(current_user, action, resource))
276289
end
@@ -329,9 +342,11 @@ defmodule Canary.Plugs do
329342

330343
defp do_load_and_authorize_resource(conn, opts) do
331344
conn
332-
|> Map.put(:skip_canary_handler, true) # skip not_found_handler so auth handler can catch first if needed
345+
# skip not_found_handler so auth handler can catch first if needed
346+
|> Map.put(:skip_canary_handler, true)
333347
|> load_resource(opts)
334-
|> Map.delete(:skip_canary_handler) # allow auth handling
348+
# allow auth handling
349+
|> Map.delete(:skip_canary_handler)
335350
|> authorize_resource(opts)
336351
|> maybe_handle_not_found(opts)
337352
|> purge_resource_if_unauthorized(opts)
@@ -343,6 +358,7 @@ defmodule Canary.Plugs do
343358

344359
defp purge_resource_if_unauthorized(%{assigns: %{authorized: true}} = conn, _opts),
345360
do: conn
361+
346362
defp purge_resource_if_unauthorized(%{assigns: %{authorized: false}} = conn, opts),
347363
do: Plug.Conn.assign(conn, get_resource_name(conn, opts), nil)
348364

@@ -357,12 +373,15 @@ defmodule Canary.Plugs do
357373
:error ->
358374
repo.get_by(opts[:model], get_map_args)
359375
|> preload_if_needed(repo, opts)
376+
360377
{:ok, nil} ->
361378
repo.get_by(opts[:model], get_map_args)
362379
|> preload_if_needed(repo, opts)
380+
363381
{:ok, resource} ->
364382
if resource.__struct__ == opts[:model] do
365-
resource # A resource of the type passed as opts[:model] is already loaded; do not clobber it
383+
# A resource of the type passed as opts[:model] is already loaded; do not clobber it
384+
resource
366385
else
367386
opts[:model]
368387
|> repo.get_by(get_map_args)
@@ -376,9 +395,11 @@ defmodule Canary.Plugs do
376395

377396
resource_name = get_resource_name(conn, opts)
378397

379-
case Map.fetch(conn.assigns, resource_name) do # check if a resource is already loaded at the key
398+
# check if a resource is already loaded at the key
399+
case Map.fetch(conn.assigns, resource_name) do
380400
:error ->
381401
from(m in opts[:model]) |> select([m], m) |> repo.all |> preload_if_needed(repo, opts)
402+
382403
{:ok, resources} ->
383404
if Enum.at(resources, 0).__struct__ == opts[:model] do
384405
resources
@@ -392,6 +413,7 @@ defmodule Canary.Plugs do
392413
case opts[:id_name] do
393414
nil ->
394415
conn.params["id"]
416+
395417
id_name ->
396418
conn.params[id_name]
397419
end
@@ -400,7 +422,7 @@ defmodule Canary.Plugs do
400422
defp get_action(conn) do
401423
case Map.fetch(conn.assigns, :canary_action) do
402424
{:ok, action} -> action
403-
_ -> conn.private.phoenix_action
425+
_ -> conn.private.phoenix_action
404426
end
405427
end
406428

@@ -428,17 +450,24 @@ defmodule Canary.Plugs do
428450
cond do
429451
has_key?(opts, :except) && has_key?(opts, :only) ->
430452
false
453+
431454
has_key?(opts, :except) ->
432455
!action_exempt?(conn, opts)
456+
433457
has_key?(opts, :only) ->
434458
action_included?(conn, opts)
459+
435460
true ->
436461
true
437462
end
438463
end
439464

440465
defp persisted?(opts) do
441-
!!Keyword.get(opts, :persisted, false)
466+
!!Keyword.get(opts, :persisted, false) || !!Keyword.get(opts, :required, false)
467+
end
468+
469+
defp required?(opts) do
470+
!!Keyword.get(opts, :required, false)
442471
end
443472

444473
defp get_resource_name(conn, opts) do
@@ -450,7 +479,9 @@ defmodule Canary.Plugs do
450479
|> Macro.underscore()
451480
|> pluralize_if_needed(conn, opts)
452481
|> String.to_atom()
453-
as -> as
482+
483+
as ->
484+
as
454485
end
455486
end
456487

@@ -470,15 +501,15 @@ defmodule Canary.Plugs do
470501
case opts[:preload] do
471502
nil ->
472503
records
504+
473505
models ->
474506
repo.preload(records, models)
475507
end
476508
end
477509

478-
defp handle_unauthorized(%{skip_canary_handler: true} = conn, _opts),
479-
do: conn
480510
defp handle_unauthorized(%{assigns: %{authorized: true}} = conn, _opts),
481511
do: conn
512+
482513
defp handle_unauthorized(%{assigns: %{authorized: false}} = conn, opts),
483514
do: apply_error_handler(conn, :unauthorized_handler, opts)
484515

@@ -488,28 +519,32 @@ defmodule Canary.Plugs do
488519

489520
defp handle_not_found(conn, opts) do
490521
action = get_action(conn)
522+
491523
non_id_actions =
492524
if opts[:non_id_actions] do
493525
Enum.concat([:index, :new, :create], opts[:non_id_actions])
494526
else
495527
[:index, :new, :create]
496528
end
529+
530+
is_required = required?(opts)
497531
resource_name = Map.get(conn.assigns, get_resource_name(conn, opts))
498532

499-
if is_nil(resource_name) and not action in non_id_actions do
533+
if is_nil(resource_name) and (is_required or action not in non_id_actions) do
500534
apply_error_handler(conn, :not_found_handler, opts)
501535
else
502536
conn
503537
end
504538
end
505539

506540
defp apply_error_handler(conn, handler_key, opts) do
507-
handler = Keyword.get(opts, handler_key)
508-
|| Application.get_env(:canary, handler_key)
541+
handler =
542+
Keyword.get(opts, handler_key) ||
543+
Application.get_env(:canary, handler_key)
509544

510545
case handler do
511546
{mod, fun} -> apply(mod, fun, [conn])
512-
nil -> conn
547+
nil -> conn
513548
end
514549
end
515550
end

0 commit comments

Comments
 (0)