Skip to content

Commit de3b6cf

Browse files
committed
chore(backend): init filter policy (#1388)
Adds a filter policy that can be used to filter based on the value of a specific field and the relation the actor has with such field. Signed-off-by: Luca Zaninotto <luca.zaninotto@secomind.com>
1 parent 6419ed1 commit de3b6cf

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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(nil, _context, _opts), do: expr(nil)
42+
43+
@impl FilterCheck
44+
def filter(actor, _context, opts) do
45+
subj = Map.get(actor, :sub, "anon")
46+
47+
rel = opts |> Keyword.fetch!(:rel) |> to_string()
48+
obj_type = opts |> Keyword.fetch!(:obj) |> to_string()
49+
obj_id = Keyword.get(opts, :obj_id, :id)
50+
51+
log_context = [
52+
subj: subj,
53+
obj_type: obj_type,
54+
obj_id: obj_id
55+
]
56+
57+
subject = "#{@subject_type}:#{subj}"
58+
59+
subject
60+
|> FGAService.list_objects(rel, obj_type)
61+
|> to_ash_expr(obj_id, log_context)
62+
end
63+
64+
@impl Ash.Policy.Check
65+
def describe(opts) do
66+
rel = Keyword.fetch!(opts, :rel)
67+
obj = Keyword.fetch!(opts, :obj)
68+
"Filtering objects of type #{inspect(obj)} where actor has relation #{inspect(rel)}"
69+
end
70+
71+
defp to_ash_expr({:ok, :all}, _id_attribute, _log_context), do: expr(true)
72+
73+
defp to_ash_expr(
74+
{:ok, %Openfga.V1.ListObjectsResponse{objects: ids}},
75+
id_attribute,
76+
_log_context
77+
) do
78+
expr(^ref(id_attribute) in ^ids)
79+
end
80+
81+
defp to_ash_expr({:error, error}, _id_attribute, log_context) do
82+
Logger.error("Error while filtering: #{inspect(error)}", log_context)
83+
84+
# We had an error while interrogating the provider. Poison everything.
85+
expr(nil)
86+
end
87+
end

0 commit comments

Comments
 (0)