Skip to content

Commit

Permalink
Support oidc and service account bindings in invites (#1171)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Jul 21, 2023
1 parent db99828 commit 4dae8ba
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 6 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ test/
priv/static/
creds/
www/
ai/
.github/
8 changes: 6 additions & 2 deletions apps/core/lib/core/schema/invite.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Core.Schema.Invite do
use Piazza.Ecto.Schema
alias Core.Schema.{Account, User, InviteGroup}
alias Core.Schema.{Account, User, InviteGroup, OIDCProvider}

@email_re ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-\.]+\.[a-zA-Z]{2,}$/

Expand All @@ -11,6 +11,8 @@ defmodule Core.Schema.Invite do

belongs_to :user, User
belongs_to :account, Account
belongs_to :service_account, User
belongs_to :oidc_provider, OIDCProvider

has_many :invite_groups, InviteGroup, on_replace: :delete
has_many :groups, through: [:invite_groups, :group]
Expand All @@ -27,14 +29,16 @@ defmodule Core.Schema.Invite do
from(i in query, where: i.account_id == ^aid)
end

@valid ~w(email account_id user_id admin)a
@valid ~w(email account_id user_id admin service_account_id oidc_provider_id)a

def changeset(model, attrs \\ %{}) do
model
|> cast(attrs, @valid)
|> cast_assoc(:invite_groups)
|> put_new_change(:secure_id, &gen_external_id/0)
|> foreign_key_constraint(:account_id)
|> foreign_key_constraint(:oidc_provider_id)
|> foreign_key_constraint(:service_account_id)
|> unique_constraint(:secure_id)
|> unique_constraint(:email)
|> validate_format(:email, @email_re)
Expand Down
34 changes: 33 additions & 1 deletion apps/core/lib/core/services/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Core.Services.Accounts do
Role,
IntegrationWebhook,
OAuthIntegration,
OIDCProvider,
DomainMapping,
RoleBinding,
ImpersonationPolicyBinding,
Expand Down Expand Up @@ -431,7 +432,7 @@ defmodule Core.Services.Accounts do
@spec realize_invite(map, binary) :: user_resp
def realize_invite(attributes, invite_id) do
invite = get_invite!(invite_id)
|> Core.Repo.preload([:groups, user: :account])
|> Core.Repo.preload([:groups, :oidc_provider, :service_account, user: :account])

start_transaction()
|> add_operation(:user, fn _ ->
Expand All @@ -453,6 +454,8 @@ defmodule Core.Services.Accounts do
end
end)
|> add_to_groups(invite)
|> add_bindings(:oidc, invite)
|> add_bindings(:sa, invite)
|> add_operation(:invite, fn _ -> Core.Repo.delete(invite) end)
|> add_operation(:account, fn
%{user: %{id: uid, account: %{root_user_id: uid} = account}} ->
Expand All @@ -475,6 +478,35 @@ defmodule Core.Services.Accounts do
end
defp add_to_groups(xact, _), do: xact

defp add_bindings(xact, :oidc, %Invite{oidc_provider: %OIDCProvider{} = provider}) do
add_operation(xact, :oidc, fn %{upsert: %{id: id}} ->
provider = Core.Repo.preload(provider, [:bindings])
bindings = add_binding(provider.bindings, id)

OIDCProvider.changeset(provider, %{bindings: bindings})
|> Core.Repo.update()
end)
end
defp add_bindings(xact, :sa, %Invite{service_account: %User{} = sa}) do
add_operation(xact, :sa, fn %{upsert: %{id: id}} ->
sa = Core.Repo.preload(sa, [impersonation_policy: [:bindings]])
bindings = add_binding(sa.impersonation_policy.bindings, id)

User.service_account_changeset(sa, %{impersonation_policy: %{bindings: bindings}})
|> Core.Repo.update()
end)
end
defp add_bindings(xact, _, _), do: xact

defp add_binding(bindings, user_id) do
Enum.map([%{user_id: user_id} | bindings], fn
%{user_id: uid} when is_binary(uid) -> %{user_id: uid}
%{group_id: gid} when is_binary(gid) -> %{group_id: gid}
end)
|> MapSet.new()
|> MapSet.to_list()
end

@doc """
Creates a group in the user's account
"""
Expand Down
10 changes: 10 additions & 0 deletions apps/core/priv/repo/migrations/20230720191356_invite_oidc.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Core.Repo.Migrations.InviteOidc do
use Ecto.Migration

def change do
alter table(:invites) do
add :oidc_provider_id, references(:oidc_providers, type: :uuid, on_delete: :delete_all)
add :service_account_id, references(:users, type: :uuid, on_delete: :delete_all)
end
end
end
28 changes: 28 additions & 0 deletions apps/core/test/services/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,34 @@ defmodule Core.Services.AccountsTest do
assert_receive {:event, %PubSub.UserCreated{item: ^user}}
end

test "it can bind users to service accounts/oidc providers", %{user: user, account: account} do
oidc = insert(:oidc_provider)
sa = insert(:user, service_account: true, account: account)
group = insert(:group, account: account)
insert(:oidc_provider_binding, provider: oidc, group: group)
policy = insert(:impersonation_policy, user: sa)
insert(:impersonation_policy_binding, policy: policy, group: group)

{:ok, invite} = Accounts.create_invite(%{
email: "[email protected]",
oidc_provider_id: oidc.id,
service_account_id: sa.id
}, user)

{:ok, new_user} = Accounts.realize_invite(%{
password: "some long password",
name: "Some User"
}, invite.secure_id)

%{bindings: bindings} = Core.Repo.preload(oidc, [:bindings])
assert Enum.find_value(bindings, & &1.group_id) == group.id
assert Enum.find_value(bindings, & &1.user_id) == new_user.id

%{impersonation_policy: %{bindings: bindings}} = Core.Repo.preload(sa, [impersonation_policy: :bindings])
assert Enum.find_value(bindings, & &1.group_id) == group.id
assert Enum.find_value(bindings, & &1.user_id) == new_user.id
end

test "it can bind users to groups when realizing an invite", %{user: user, account: account} do
groups = insert_list(2, :group, account: account)
{:ok, invite} = Accounts.create_invite(%{
Expand Down
8 changes: 5 additions & 3 deletions apps/graphql/lib/graphql/schema/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ defmodule GraphQl.Schema.Account do
end

input_object :invite_attributes do
field :email, :string
field :admin, :boolean
field :invite_groups, list_of(:binding_attributes)
field :email, :string
field :admin, :boolean
field :oidc_provider_id, :id
field :service_account_id, :id
field :invite_groups, list_of(:binding_attributes)
end

input_object :group_attributes do
Expand Down
2 changes: 2 additions & 0 deletions schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@ input FileAttributes {
input InviteAttributes {
email: String
admin: Boolean
oidcProviderId: ID
serviceAccountId: ID
inviteGroups: [BindingAttributes]
}

Expand Down
2 changes: 2 additions & 0 deletions www/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,8 @@ export type InviteAttributes = {
admin?: InputMaybe<Scalars['Boolean']['input']>;
email?: InputMaybe<Scalars['String']['input']>;
inviteGroups?: InputMaybe<Array<InputMaybe<BindingAttributes>>>;
oidcProviderId?: InputMaybe<Scalars['ID']['input']>;
serviceAccountId?: InputMaybe<Scalars['ID']['input']>;
};

export type InviteConnection = {
Expand Down

0 comments on commit 4dae8ba

Please sign in to comment.