Skip to content

Commit 6b829f2

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 c75a54e commit 6b829f2

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
37+
```elixir
38+
alias Edgehog.Auth.Policies.Check
39+
40+
# This can be used to authorize reads on single devices
41+
authorize_if {Check, rel: :can_view, obj: :device, obj_id: :device_id}
42+
```
43+
"""
44+
use Ash.Policy.SimpleCheck
45+
46+
alias Edgehog.Auth.FGAService
47+
48+
require Logger
49+
50+
@subject_type "user"
51+
52+
@impl Ash.Policy.SimpleCheck
53+
def match?(actor, %{subject: data} = context, opts) do
54+
dbg(actor: actor, context: context, opts: opts)
55+
obj_id = Keyword.get(opts, :obj_id, :id)
56+
57+
subj = actor.sub
58+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
59+
obj = opts |> Keyword.fetch!(:obj) |> to_string()
60+
61+
obj_id = nil
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+
case FGAService.check("#{@subject_type}:#{subj}", rel, "#{obj}:#{obj_id}") do
72+
:ok -> {:ok, true}
73+
:notok -> {:ok, false}
74+
error -> error
75+
end
76+
end
77+
78+
@impl Ash.Policy.Check
79+
def describe(opts) do
80+
rel = Keyword.fetch!(opts, :rel)
81+
obj = Keyword.fetch!(opts, :obj)
82+
obj_id = Keyword.get(opts, :obj_id, :id)
83+
84+
"Checking if the actor has relation #{inspect(rel)} on #{inspect(obj)} (using field #{inspect(obj_id)} for ID)"
85+
end
86+
end
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
authorize_if {Edgehog.Auth.Policies.Filter, rel: :can_view, obj: :device}
28+
```
29+
"""
30+
31+
use Ash.Policy.FilterCheck
32+
33+
alias Ash.Policy.FilterCheck
34+
alias Edgehog.Auth.FGAService
35+
36+
require Logger
37+
38+
@subject_type "user"
39+
40+
@impl FilterCheck
41+
def filter(actor, _context, opts) do
42+
subj = Map.get(actor, :sub, "anon")
43+
44+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
45+
obj_type = opts |> Keyword.fetch!(:obj) |> to_string()
46+
obj_id = Keyword.get(opts, :obj_id, :id)
47+
48+
log_context = [
49+
subj: subj,
50+
obj_type: obj_type,
51+
obj_id: obj_id
52+
]
53+
54+
subject = "#{@subject_type}:#{subj}"
55+
56+
subject
57+
|> FGAService.list_objects(rel, obj_type)
58+
|> to_ash_expr(obj_id, log_context)
59+
end
60+
61+
@impl FilterCheck
62+
def reject(actor, _context, opts) do
63+
subj = Map.get(actor, :sub, "anon")
64+
65+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
66+
obj_type = opts |> Keyword.fetch!(:obj) |> to_string()
67+
obj_id = Keyword.get(opts, :obj_id, :id)
68+
69+
log_context = [
70+
subj: subj,
71+
obj_type: obj_type,
72+
obj_id: obj_id
73+
]
74+
75+
subject = "#{@subject_type}:#{subj}"
76+
77+
subject
78+
|> FGAService.list_objects(rel, obj_type)
79+
|> to_ash_expr(obj_id, log_context)
80+
end
81+
82+
@impl Ash.Policy.Check
83+
def describe(opts) do
84+
rel = Keyword.fetch!(opts, :rel)
85+
obj = Keyword.fetch!(opts, :obj)
86+
"Filtering objects of type #{inspect(obj)} where actor has relation #{inspect(rel)}"
87+
end
88+
89+
defp to_ash_expr({:ok, :all}, id_attribute, _log_context), do: expr(true)
90+
91+
defp to_ash_expr(
92+
{:ok, %Openfga.V1.ListObjectsResponse{objects: ids}},
93+
id_attribute,
94+
_log_context
95+
) do
96+
expr(^ref(id_attribute) in ^ids)
97+
end
98+
99+
defp to_ash_expr({:error, error}, _id_attribute, log_context) do
100+
Logger.error("Error while filtering: #{inspect(error)}", log_context)
101+
102+
# We had an error while interrogating the provider. Poison everything.
103+
expr(nil)
104+
end
105+
end

0 commit comments

Comments
 (0)