Skip to content

Commit b9df3ed

Browse files
feat(rbac): jit saml group membership (#142)
## πŸ“ Description Handle group membership for jit saml users based on saml attributes renderedtext/tasks#7653 ## βœ… Checklist - [x] I have tested this change - [ ] This change requires documentation update
1 parent 179f067 commit b9df3ed

File tree

15 files changed

+172
-28
lines changed

15 files changed

+172
-28
lines changed

β€Žee/rbac/lib/rbac/grpc_servers/groups_server.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ defmodule Rbac.GrpcServers.GroupsServer do
2727
Rbac.TempSync.assign_org_member_role(created_group.id, org_id)
2828

2929
Enum.each(group.member_ids, fn member_id ->
30-
Rbac.Repo.GroupManagementRequest.create_new_request(member_id, created_group.id, "add")
30+
Rbac.Repo.GroupManagementRequest.create_new_request(member_id, created_group.id, :add)
3131
end)
3232

3333
%Groups.CreateGroupResponse{group: construct_grpc_group(created_group)}
@@ -73,8 +73,8 @@ defmodule Rbac.GrpcServers.GroupsServer do
7373
with {:ok, _} <- Group.fetch_group(req.group.id),
7474
{:ok, group} <-
7575
Group.modify_metadata(req.group.id, req.group.name, req.group.description) do
76-
GroupManagementRequest.create_new_request(req.members_to_remove, group.id, "remove")
77-
GroupManagementRequest.create_new_request(req.members_to_add, group.id, "add")
76+
GroupManagementRequest.create_new_request(req.members_to_remove, group.id, :remove)
77+
GroupManagementRequest.create_new_request(req.members_to_add, group.id, :add)
7878
%Groups.ModifyGroupResponse{group: construct_grpc_group(group)}
7979
else
8080
{:error, :not_found} ->

β€Žee/rbac/lib/rbac/okta/saml/jit_provisioner/add_user.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ defmodule Rbac.Okta.Saml.JitProvisioner.AddUser do
4040
{:ok, saml_jit_user} <- SamlJitUser.connect_user(saml_jit_user, user.id),
4141
{:ok, role_id} <- fetch_role_to_be_assigned(saml_jit_user),
4242
:ok <- assign_role(user.id, saml_jit_user.org_id, role_id),
43+
{:ok, group_ids} <- fetch_groups_to_be_assigned(saml_jit_user),
44+
:ok <- assign_groups(user.id, group_ids),
4345
{:ok, saml_jit_user} <- SamlJitUser.mark_as_processed(saml_jit_user)
4446
) do
4547
info("Provisioning #{saml_jit_user.id} done.")
@@ -125,6 +127,29 @@ defmodule Rbac.Okta.Saml.JitProvisioner.AddUser do
125127
end
126128
end
127129

130+
defp fetch_groups_to_be_assigned(jit_user) do
131+
member_of = jit_user.attributes["member"] || []
132+
info("[Saml JIT Provisioner] User member groups: #{inspect(member_of)}")
133+
134+
case Rbac.Okta.IdpGroupMapping.map_groups(jit_user.org_id, member_of) do
135+
{:ok, groups, _default_role_id} when is_list(groups) ->
136+
info("[Saml JIT Provisioner] Mapped Semaphore groups: #{inspect(groups)}")
137+
{:ok, groups}
138+
139+
e ->
140+
info("[Saml JIT Provisioner] Response from IdpGroupMapping.map_groups #{inspect(e)}")
141+
{:ok, []}
142+
end
143+
end
144+
145+
defp assign_groups(user_id, group_ids) when is_list(group_ids) do
146+
Enum.each(group_ids, fn group_id ->
147+
Rbac.Repo.GroupManagementRequest.create_new_request(user_id, group_id, :add)
148+
end)
149+
150+
:ok
151+
end
152+
128153
defp log_provisioning_error(okta_user, err) do
129154
inspects = "okta user: #{inspect(okta_user)} error: #{inspect(err)}"
130155

β€Žee/rbac/lib/rbac/okta/saml/payload_parser.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,17 @@ defmodule Rbac.Okta.Saml.PayloadParser do
4444

4545
defp construct_attributes_map(attributes) do
4646
Enum.reduce(attributes, %{}, fn {name, value}, acc ->
47-
<<first::utf8, rest::binary>> = name |> Atom.to_string() |> String.trim("/")
48-
name = <<String.downcase(<<first::utf8>>)::binary, rest::binary>>
49-
50-
value = to_string(value)
47+
name = name |> Atom.to_string() |> sanitize_string()
48+
value = to_string(value) |> sanitize_string()
5149
Map.update(acc, name, [value], &(&1 ++ [value]))
5250
end)
5351
end
5452

53+
defp sanitize_string(string) do
54+
<<first::utf8, rest::binary>> = string |> String.trim("/")
55+
<<String.downcase(<<first::utf8>>)::binary, rest::binary>>
56+
end
57+
5558
defp validate_assertion(saml, sp) do
5659
:esaml_sp.validate_assertion(saml, sp)
5760
end

β€Žee/rbac/lib/rbac/repo/group_management_request.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Rbac.Repo.GroupManagementRequest do
88
field(:state, Ecto.Enum, values: [:pending, :processing, :done, :failed], default: :pending)
99
field(:user_id, :binary_id)
1010
field(:group_id, :binary_id)
11-
field(:action, :string)
11+
field(:action, Ecto.Enum, values: [:add, :remove])
1212
field(:retries, :integer, default: 0)
1313

1414
timestamps()

β€Žee/rbac/lib/rbac/repo/okta_user.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ defmodule Rbac.Repo.OktaUser do
7171
end
7272
end
7373

74+
def find_by_user_id(user_id) do
75+
__MODULE__
76+
|> where([u], u.user_id == ^user_id)
77+
|> Repo.all()
78+
end
79+
7480
@spec list(Rbac.Repo.OktaIntegration.t(), integer(), integer(), [Rbac.Okta.SCIM.Filter.t()]) ::
7581
any()
7682
def list(integration, start_index, count, filters) do
@@ -157,6 +163,10 @@ defmodule Rbac.Repo.OktaUser do
157163
changeset(okta_user, %{user_id: user_id}) |> Repo.update()
158164
end
159165

166+
def disconnect_user(okta_user) do
167+
changeset(okta_user, %{user_id: nil}) |> Repo.update()
168+
end
169+
160170
def reload_with_lock_and_transaction(okta_user_id, fun) do
161171
Rbac.Repo.transaction(
162172
fn ->

β€Žee/rbac/lib/rbac/repo/saml_jit_user.ex

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,18 @@ defmodule Rbac.Repo.SamlJitUser do
4949
end
5050
end
5151

52-
def connect_user(%__MODULE__{} = user, user_id) do
53-
changeset(user, %{user_id: user_id}) |> Repo.update()
52+
def find_by_user_id(user_id) do
53+
__MODULE__
54+
|> where([u], u.user_id == ^user_id)
55+
|> Repo.all()
56+
end
57+
58+
def delete(%__MODULE__{} = saml_jit_user) do
59+
Repo.delete(saml_jit_user)
60+
end
61+
62+
def connect_user(%__MODULE__{} = saml_jit_user, user_id) do
63+
changeset(saml_jit_user, %{user_id: user_id}) |> Repo.update()
5464
end
5565

5666
def construct_name(%__MODULE__{} = user) do

β€Žee/rbac/lib/rbac/services/user_deleted.ex

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ defmodule Rbac.Services.UserDeleted do
1313

1414
log(event.user_id, "Processing started")
1515

16-
if Rbac.OIDC.enabled?() do
17-
handle_oidc_sync(event.user_id)
18-
end
16+
delete_oidc_user(event.user_id)
17+
disconnect_okta_user(event.user_id)
18+
delete_saml_jit_user(event.user_id)
1919

2020
Rbac.Store.RbacUser.delete(event.user_id)
2121

2222
log(event.user_id, "Processing finished")
2323
end)
2424
end
2525

26-
defp handle_oidc_sync(user_id) do
26+
defp delete_oidc_user(user_id) do
2727
case Rbac.Store.OIDCUser.fetch_by_user_id(user_id) do
2828
{:ok, oidc_user} ->
2929
log(user_id, "OIDC user exists, deleting")
@@ -35,6 +35,30 @@ defmodule Rbac.Services.UserDeleted do
3535
end
3636
end
3737

38+
defp disconnect_okta_user(user_id) do
39+
okta_users = Rbac.Repo.OktaUser.find_by_user_id(user_id)
40+
41+
if Enum.empty?(okta_users) do
42+
log(user_id, "No Okta users found, not syncing")
43+
else
44+
log(user_id, "Found #{length(okta_users)} Okta users, disconnecting")
45+
46+
Enum.each(okta_users, fn okta_user ->
47+
Rbac.Repo.OktaUser.disconnect_user(okta_user)
48+
end)
49+
end
50+
end
51+
52+
defp delete_saml_jit_user(user_id) do
53+
saml_users = Rbac.Repo.SamlJitUser.find_by_user_id(user_id)
54+
55+
log(user_id, "Found #{length(saml_users)} SAML JIT users, deleting")
56+
57+
Enum.each(saml_users, fn saml_user ->
58+
Rbac.Repo.SamlJitUser.delete(saml_user)
59+
end)
60+
end
61+
3862
defp log(level \\ :info, user_id, message) do
3963
message = "[User Deleted] #{user_id}: #{message}"
4064

β€Žee/rbac/lib/rbac/store/group.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ defmodule Rbac.Store.Group do
115115
|> where([_, g], g.org_id == ^org_id)
116116
|> select([ugb, _], ugb.group_id)
117117
|> Rbac.Repo.all()
118-
|> Enum.each(&Rbac.Repo.GroupManagementRequest.create_new_request(member_id, &1, "remove"))
118+
|> Enum.each(&Rbac.Repo.GroupManagementRequest.create_new_request(member_id, &1, :remove))
119119
end
120120

121121
def modify_metadata(group_id, "", ""), do: fetch_group(group_id)

β€Žee/rbac/lib/rbac/workers/group_management.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ defmodule Rbac.Workers.GroupManagement do
2626
{:ok, group} = Rbac.Store.Group.fetch_group(req.group_id)
2727

2828
case req.action do
29-
"add" ->
29+
:add ->
3030
:ok = Rbac.Store.Group.add_to_group(group, req.user_id)
3131

32-
"remove" ->
32+
:remove ->
3333
:ok = Rbac.Store.Group.remove_from_group(group, req.user_id)
3434

3535
other ->

β€Žee/rbac/test/rbac/grpc_servers/groups_server_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ defmodule Rbac.GrpcServers.GroupsServer.Test do
248248
}
249249

250250
{:ok, _} = state.grpc_channel |> Stub.modify_group(request)
251-
assert_request_created(user1.id, state.group.id, "add")
252-
assert_request_created(user2.id, state.group.id, "add")
253-
assert_request_created(user3.id, state.group.id, "remove")
251+
assert_request_created(user1.id, state.group.id, :add)
252+
assert_request_created(user2.id, state.group.id, :add)
253+
assert_request_created(user3.id, state.group.id, :remove)
254254
assert Rbac.Repo.GroupManagementRequest |> Rbac.Repo.aggregate(:count, :id) == 3
255255
end
256256
end

0 commit comments

Comments
Β (0)