Skip to content

Commit 0e5858d

Browse files
committed
chore: init writes on authorization providers (#1410)
Auth providers should be updated with the state of the application. This can happen after each transaction, effectively writing the `tenant` relationship also in the authorization level Signed-off-by: Luca Zaninotto <luca.zaninotto@secomind.com>
1 parent 90ec874 commit 0e5858d

5 files changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.Changes.WriteTenant do
22+
@moduledoc """
23+
Generic tuple writer.
24+
"""
25+
26+
use Ash.Resource.Change
27+
28+
alias Edgehog.Auth.FGAService
29+
30+
require Logger
31+
32+
@impl Ash.Resource.Change
33+
def init(opts) do
34+
errors =
35+
if Keyword.has_key?(opts, :obj),
36+
do: [],
37+
else: [:obj_missing]
38+
39+
errors =
40+
if Keyword.has_key?(opts, :obj_id),
41+
do: errors,
42+
else: [:obj_id_missing | errors]
43+
44+
if Enum.empty?(errors),
45+
do: {:ok, opts},
46+
else: {:error, errors}
47+
end
48+
49+
@impl Ash.Resource.Change
50+
def change(changeset, opts, context) do
51+
Ash.Changeset.after_transaction(changeset, &write_tenant_tuple(&1, &2, opts, context))
52+
end
53+
54+
defp write_tenant_tuple(_changeset, {:ok, result}, opts, %{tenant: tenant}) do
55+
# Writes the tuple
56+
# {"tenant:slug", "tenant", "obj:obj_id"}
57+
58+
obj = opts[:obj] |> to_string()
59+
60+
obj_id = opts[:obj_id]
61+
62+
obj_id =
63+
result
64+
|> Ash.load!(obj_id)
65+
|> Map.get(obj_id)
66+
|> to_string()
67+
68+
obj = "#{obj}:#{obj_id}"
69+
70+
rel = "tenant"
71+
72+
slug = tenant.slug
73+
subj = "tenant:#{slug}"
74+
75+
with {:ok, _res} <- FGAService.write(subj, rel, obj) do
76+
{:ok, result}
77+
end
78+
end
79+
80+
defp write_tenant_tuple(_changeset, error, opts, context) do
81+
Logger.debug("Error while executing DB transaction. Skipping writing tuple on the provider.",
82+
error: error,
83+
opts: opts,
84+
context: context
85+
)
86+
87+
error
88+
end
89+
end

backend/lib/edgehog/auth/fga_service.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ defmodule Edgehog.Auth.FGAService do
3838
end
3939
end
4040

41+
def write(subj, rel, obj) do
42+
tuple = {subj, rel, obj}
43+
44+
provider = Keyword.fetch!(Config.authz_config!(), :provider)
45+
config = Keyword.fetch!(Config.authz_config!(), :config)
46+
47+
with {:ok, context} <- provider.init_context(config) do
48+
provider.write(tuple, context)
49+
end
50+
end
51+
4152
def list_objects(subj, rel, type) do
4253
tuple = {subj, rel, type}
4354

backend/lib/edgehog/auth/providers/behaviour.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ defmodule Edgehog.Auth.Providers.Behaviour do
3535
"""
3636
@callback init_context(args :: Keyword.t()) :: {:ok, context()} | {:error, term()}
3737

38+
@doc """
39+
Writes a tuple to the provider.
40+
41+
- tuple :: the fga tuple, composed of `subject`, `relationship` and `object`
42+
- context :: the context from `init_context`
43+
44+
A write can return
45+
- {:ok, result} :: if the write was successful
46+
- {:error, error} :: if some error was encountered while writing
47+
"""
48+
@callback write(tuple :: fga_tuple(), context :: context()) :: {:ok, term()} | {:error, term()}
49+
3850
@doc """
3951
A check call checks a tuple against the provider
4052

backend/lib/edgehog/auth/providers/none.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,12 @@ defmodule Edgehog.Auth.Providers.None do
5555

5656
{:ok, :all}
5757
end
58+
59+
@impl Behaviour
60+
def write(tuple, _context) do
61+
Logger.debug("Writing tuple onto the service", tuple: tuple)
62+
63+
# Result actually not important here
64+
{:ok, nil}
65+
end
5866
end

backend/lib/edgehog/auth/providers/openfga.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,25 @@ defmodule Edgehog.Auth.Providers.OpenFGA do
8484

8585
Stub.streamed_list_objects(channel, request)
8686
end
87+
88+
@impl Behaviour
89+
def write({subj, rel, obj}, %{channel: channel, store_id: store_id}) do
90+
tuple = %Openfga.V1.TupleKey{
91+
user: subj,
92+
relation: rel,
93+
object: obj
94+
}
95+
96+
to_write = %Openfga.V1.WriteRequestWrites{
97+
tuple_keys: [tuple],
98+
on_duplicate: "ignore"
99+
}
100+
101+
request = %Openfga.V1.WriteRequest{
102+
store_id: store_id,
103+
writes: to_write
104+
}
105+
106+
Stub.write(channel, request)
107+
end
87108
end

0 commit comments

Comments
 (0)