Skip to content

Commit d18674d

Browse files
committed
chore(backend): init check and filter policies
Filter and check policies can be used in various parts of the code. These policies are meant to trigger authorization based on the given provider. Signed-off-by: Luca Zaninotto <luca.zaninotto@secomind.com>
1 parent 309380a commit d18674d

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#
2+
# This file is part of Edgehog.
3+
#
4+
# Copyright 2026 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
#
20+
21+
defmodule Edgehog.Auth.Policies.Check do
22+
@moduledoc """
23+
An `Ash.Policy.SimpleCheck` check to query the provider for a specific `relation` with `object`
24+
25+
To use it, call it this way in a policy
26+
```elixir
27+
{Edgehog.Auth.Policies.Check, id: :id_attribute, rel: :can_view, obj: "deployment"}
28+
```
29+
30+
The following opts are available:
31+
- `rel` (required) :: the relationship you want to check for.
32+
- `obj` (required) :: type object you want to check for.
33+
- `obj_id` :: the id of the resource. This is used to build the value `type:id` for the object in the tuple.
34+
35+
Example: for devices
36+
```elixir
37+
# alias Edgehog.Auth.Policies.Check
38+
# This can be used to authorize reads on single devices
39+
authorize_if {Check, rel: :can_view, obj: :device, obj_id: :device_id}
40+
```
41+
"""
42+
use Ash.Policy.SimpleCheck
43+
44+
require Logger
45+
46+
@subject_type "user"
47+
48+
@impl Ash.Policy.SimpleCheck
49+
def match?(actor, %{subject: changeset}, opts) do
50+
obj_id = Keyword.get(opts, :obj_id, :id)
51+
52+
subj = actor.sub
53+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
54+
obj = opts |> Keyword.fetch!(:obj) |> to_string()
55+
56+
obj_id =
57+
changeset
58+
|> Map.get(:data)
59+
|> Ash.load!(obj_id)
60+
|> Map.get(obj_id, "*")
61+
|> to_string()
62+
63+
Logger.debug("Authorizing a tuple",
64+
subject: @subject_type,
65+
subject_id: subj,
66+
relationship: rel,
67+
object: obj,
68+
object_id: obj
69+
)
70+
71+
Edgehog.Auth.FGAService.check("#{@subject_type}:#{subj}", rel, "#{obj}:#{obj_id}")
72+
end
73+
74+
@impl Ash.Policy.Check
75+
def describe(opts) do
76+
rel = Keyword.fetch!(opts, :rel)
77+
obj = Keyword.fetch!(opts, :obj)
78+
obj_id = Keyword.get(opts, :obj_id, :id)
79+
80+
"Checking if the actor has relation #{inspect(rel)} on #{inspect(obj)} (using field #{inspect(obj_id)} for ID)"
81+
end
82+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#
2+
# This file is part of Edgehog.
3+
#
4+
# Copyright 2026 SECO Mind Srl
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# SPDX-License-Identifier: Apache-2.0
19+
#
20+
21+
defmodule Edgehog.Auth.Policies.Filter do
22+
@moduledoc """
23+
An `Ash.Policy.FilterCheck`, filtering the object of type `obj` available to the user `subj`.
24+
25+
To use it in a policy:
26+
```elixir
27+
filter_by {Edgehog.Auth.Policies.Filter, rel: :can_view, obj: :device}
28+
```
29+
"""
30+
use Ash.Policy.FilterCheck
31+
32+
@impl Ash.Policy.FilterCheck
33+
def filter(actor, opts, _context) do
34+
subj = actor.sub
35+
36+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
37+
obj_type = opts |> Keyword.fetch!(:obj) |> to_string()
38+
39+
case Edgehog.Auth.FGAService.stream_list_objects(subj, rel, obj_type) do
40+
{:ok, ids} ->
41+
expr(id in ^ids)
42+
43+
{:error, _reason} ->
44+
false
45+
end
46+
end
47+
48+
@impl Ash.Policy.FilterCheck
49+
def reject(actor, opts, _context) do
50+
subj = actor.sub
51+
52+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
53+
obj_type = opts |> Keyword.fetch!(:obj) |> to_string()
54+
end
55+
56+
@impl Ash.Policy.Check
57+
def describe(opts) do
58+
rel = Keyword.fetch!(opts, :rel)
59+
obj = Keyword.fetch!(opts, :obj)
60+
"Filtering objects of type #{inspect(obj)} where actor has relation #{inspect(rel)}"
61+
end
62+
end

0 commit comments

Comments
 (0)