From 1e20c170e45ef1b6f7f5151e894ac7ebd4527ab7 Mon Sep 17 00:00:00 2001 From: hamir-suspect Date: Thu, 9 Oct 2025 10:52:47 +0200 Subject: [PATCH 1/3] chore(repository_hub): refresh protos --- repository_hub/lib/internal_api/user.pb.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/repository_hub/lib/internal_api/user.pb.ex b/repository_hub/lib/internal_api/user.pb.ex index 8d1dae0be..e74ce9da5 100644 --- a/repository_hub/lib/internal_api/user.pb.ex +++ b/repository_hub/lib/internal_api/user.pb.ex @@ -56,6 +56,7 @@ defmodule InternalApi.User.User.CreationSource do field :NOT_SET, 0 field :OKTA, 1 + field :SERVICE_ACCOUNT, 2 end defmodule InternalApi.User.ListFavoritesRequest do From 29e2e2ffba642723997380542ef1063c50d46f5a Mon Sep 17 00:00:00 2001 From: hamir-suspect Date: Thu, 9 Oct 2025 10:54:24 +0200 Subject: [PATCH 2/3] fix: allow service accounts to create projects with github app integration --- .../adapters/bitbucket_adapter.ex | 15 ++++++++++--- .../adapters/github/create_action.ex | 22 ++++++++++++++----- .../repository_hub/adapters/github_adapter.ex | 13 +++++++++-- .../repository_hub/adapters/gitlab_adapter.ex | 15 ++++++++++--- 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/repository_hub/lib/repository_hub/adapters/bitbucket_adapter.ex b/repository_hub/lib/repository_hub/adapters/bitbucket_adapter.ex index f132bc08c..84ff12b9e 100644 --- a/repository_hub/lib/repository_hub/adapters/bitbucket_adapter.ex +++ b/repository_hub/lib/repository_hub/adapters/bitbucket_adapter.ex @@ -81,12 +81,21 @@ defmodule RepositoryHub.BitbucketAdapter do end def fetch_token(user_id) do - [integration_type] = integration_types() + with {:ok, user} <- UserClient.describe(user_id), + :ok <- validate_not_service_account(user, "Bitbucket") do + [integration_type] = integration_types() - integration_type - |> UserClient.get_repository_token(user_id) + integration_type + |> UserClient.get_repository_token(user_id) + end + end + + defp validate_not_service_account(%{user: %{creation_source: :SERVICE_ACCOUNT}}, provider_name) do + error("Service accounts cannot use #{provider_name} OAuth tokens.") end + defp validate_not_service_account(_user, _provider_name), do: :ok + def context(_adapter, repository_id, stream \\ nil) do with {:ok, context} <- UniversalAdapter.context(repository_id, stream), {:ok, bitbucket_token} <- BitbucketAdapter.fetch_token(context.project.metadata.owner_id) do diff --git a/repository_hub/lib/repository_hub/adapters/github/create_action.ex b/repository_hub/lib/repository_hub/adapters/github/create_action.ex index 34f22e4fb..9cf61a54c 100644 --- a/repository_hub/lib/repository_hub/adapters/github/create_action.ex +++ b/repository_hub/lib/repository_hub/adapters/github/create_action.ex @@ -20,10 +20,11 @@ defimpl RepositoryHub.Server.CreateAction, for: RepositoryHub.GithubAdapter do import Toolkit @impl true def execute(adapter, request) do - with {:ok, git_repository} <- GitRepository.from_github(request.repository_url), + with {:ok, user} <- UserClient.describe(request.user_id), + {:ok, git_repository} <- GitRepository.from_github(request.repository_url), {:ok, github_token} <- GithubAdapter.token(adapter, request.user_id, git_repository), {:ok, github_repository} <- get_github_repository(git_repository, github_token), - {:ok, permissions} <- get_permissions(adapter, github_repository, request.user_id, github_token), + {:ok, permissions} <- get_permissions(adapter, github_repository, user, github_token), {:ok, _} <- valid?(adapter, github_repository, request.only_public, permissions), {:ok, repository} <- insert_repository(adapter, request, git_repository, github_repository), grpc_repository <- Repositories.to_grpc_model(repository) do @@ -98,11 +99,22 @@ defimpl RepositoryHub.Server.CreateAction, for: RepositoryHub.GithubAdapter do |> wrap() end - defp get_permissions(%{integration_type: "github_oauth_token"}, repo, _, _), + defp get_permissions(%{integration_type: "github_oauth_token"}, repo, _user, _github_token), do: repo.permissions |> wrap - defp get_permissions(%{integration_type: "github_app"}, repo, user_id, github_token) do - {:ok, [username | _]} = UserClient.get_repository_provider_logins(:GITHUB, user_id) + defp get_permissions( + %{integration_type: "github_app"}, + _repo, + %{user: %{creation_source: :SERVICE_ACCOUNT}}, + _github_token + ) do + + %{"admin" => true, "push" => true} + |> wrap() + end + + defp get_permissions(%{integration_type: "github_app"}, repo, user, github_token) do + {:ok, [username | _]} = UserClient.get_repository_provider_logins(:GITHUB, user.user_id) GithubClient.repository_permissions( %{ diff --git a/repository_hub/lib/repository_hub/adapters/github_adapter.ex b/repository_hub/lib/repository_hub/adapters/github_adapter.ex index f7ed38411..899372b73 100644 --- a/repository_hub/lib/repository_hub/adapters/github_adapter.ex +++ b/repository_hub/lib/repository_hub/adapters/github_adapter.ex @@ -70,10 +70,19 @@ defmodule RepositoryHub.GithubAdapter do end def fetch_token_by_user_id(adapter, user_id) do - adapter.integration_type - |> UserClient.get_repository_token(user_id) + with {:ok, user} <- UserClient.describe(user_id), + :ok <- validate_not_service_account(user, "GitHub") do + adapter.integration_type + |> UserClient.get_repository_token(user_id) + end end + defp validate_not_service_account(%{user: %{creation_source: :SERVICE_ACCOUNT}}, provider_name) do + error("Service accounts cannot use #{provider_name} OAuth tokens. Please use the appropriate integration type.") + end + + defp validate_not_service_account(_user, _provider_name), do: :ok + def fetch_token_by_slug(adapter, slug) do adapter.integration_type |> to_integration_type diff --git a/repository_hub/lib/repository_hub/adapters/gitlab_adapter.ex b/repository_hub/lib/repository_hub/adapters/gitlab_adapter.ex index 88e2fe40e..570024d3a 100644 --- a/repository_hub/lib/repository_hub/adapters/gitlab_adapter.ex +++ b/repository_hub/lib/repository_hub/adapters/gitlab_adapter.ex @@ -52,12 +52,21 @@ defmodule RepositoryHub.GitlabAdapter do end def fetch_token(user_id) do - [integration_type] = integration_types() + with {:ok, user} <- UserClient.describe(user_id), + :ok <- validate_not_service_account(user, "GitLab") do + [integration_type] = integration_types() - integration_type - |> UserClient.get_repository_token(user_id) + integration_type + |> UserClient.get_repository_token(user_id) + end + end + + defp validate_not_service_account(%{user: %{creation_source: :SERVICE_ACCOUNT}}, provider_name) do + error("Service accounts cannot use #{provider_name} OAuth tokens.") end + defp validate_not_service_account(_user, _provider_name), do: :ok + def context(_adapter, repository_id, stream \\ nil) do with {:ok, context} <- UniversalAdapter.context(repository_id, stream), {:ok, gitlab_token} <- GitlabAdapter.fetch_token(context.project.metadata.owner_id) do From 255d60c46b063aa90ae55a225dc0ea06a0e0fb06 Mon Sep 17 00:00:00 2001 From: hamir-suspect Date: Thu, 9 Oct 2025 11:26:01 +0200 Subject: [PATCH 3/3] fix: formatting --- .../adapters/github/create_action.ex | 1 - .../adapters/github/create_action_test.exs | 44 ++++++++++++++++++- ...st_accessible_repositories_action_test.exs | 7 +++ .../git_clients/bitbucket_client_factory.ex | 13 ++++++ .../git_clients/gitlab_client_factory.ex | 13 ++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/repository_hub/lib/repository_hub/adapters/github/create_action.ex b/repository_hub/lib/repository_hub/adapters/github/create_action.ex index 9cf61a54c..6ecb38b82 100644 --- a/repository_hub/lib/repository_hub/adapters/github/create_action.ex +++ b/repository_hub/lib/repository_hub/adapters/github/create_action.ex @@ -108,7 +108,6 @@ defimpl RepositoryHub.Server.CreateAction, for: RepositoryHub.GithubAdapter do %{user: %{creation_source: :SERVICE_ACCOUNT}}, _github_token ) do - %{"admin" => true, "push" => true} |> wrap() end diff --git a/repository_hub/test/repository_hub/adapters/github/create_action_test.exs b/repository_hub/test/repository_hub/adapters/github/create_action_test.exs index e0fc1f112..59b94ac2a 100644 --- a/repository_hub/test/repository_hub/adapters/github/create_action_test.exs +++ b/repository_hub/test/repository_hub/adapters/github/create_action_test.exs @@ -17,7 +17,26 @@ defmodule RepositoryHub.Server.Github.CreateActionTest do [ {RepositoryHub.UserClient, [:passthrough], [ - get_repository_provider_logins: fn _, _ -> {:ok, ["radwo"]} end + describe: fn user_id -> + # Return regular user by default, service account for specific test user + if user_id == "service-account-user-id" do + {:ok, + %{ + user_id: user_id, + user: %{creation_source: :SERVICE_ACCOUNT} + }} + else + {:ok, + %{ + user_id: user_id, + user: %{creation_source: :NOT_SET} + }} + end + end, + get_repository_provider_logins: fn _, _ -> {:ok, ["radwo"]} end, + get_repository_token: fn _integration_type, _user_id -> + {:ok, "mock-oauth-token"} + end ]} ] ) do @@ -104,5 +123,28 @@ defmodule RepositoryHub.Server.Github.CreateActionTest do ) end end + + test "should succeed for service account with github_app integration", %{github_app_adapter: adapter} do + request = + InternalApiFactory.create_request( + integration_type: :GITHUB_APP, + user_id: "service-account-user-id" + ) + + assert {:ok, %CreateResponse{repository: _repository}} = CreateAction.execute(adapter, request) + end + + test "should fail with clear error for service account with github_oauth_token", %{ + github_oauth_adapter: adapter + } do + request = + InternalApiFactory.create_request( + integration_type: :GITHUB_OAUTH_TOKEN, + user_id: "service-account-user-id" + ) + + assert {:error, error_message} = CreateAction.execute(adapter, request) + assert error_message =~ "Service accounts cannot use GitHub OAuth tokens" + end end end diff --git a/repository_hub/test/repository_hub/adapters/github/list_accessible_repositories_action_test.exs b/repository_hub/test/repository_hub/adapters/github/list_accessible_repositories_action_test.exs index 357ae8b69..4e2f30f51 100644 --- a/repository_hub/test/repository_hub/adapters/github/list_accessible_repositories_action_test.exs +++ b/repository_hub/test/repository_hub/adapters/github/list_accessible_repositories_action_test.exs @@ -14,6 +14,13 @@ defmodule RepositoryHub.Server.Github.ListAccessibleRepositoriesActionTest do setup_with_mocks([ {RepositoryHub.UserClient, [], [ + describe: fn user_id -> + {:ok, + %{ + user_id: user_id, + user: %{creation_source: :NOT_SET} + }} + end, get_repository_token: fn integration_type, user_id -> token = "#{user_id}-#{integration_type}-token" expires_at = %Google.Protobuf.Timestamp{seconds: 0, nanos: 0} diff --git a/repository_hub/test/support/factories/git_clients/bitbucket_client_factory.ex b/repository_hub/test/support/factories/git_clients/bitbucket_client_factory.ex index 61b302ca7..0f7f7c2f0 100644 --- a/repository_hub/test/support/factories/git_clients/bitbucket_client_factory.ex +++ b/repository_hub/test/support/factories/git_clients/bitbucket_client_factory.ex @@ -20,6 +20,19 @@ defmodule RepositoryHub.BitbucketClientFactory do remove_deploy_key: &remove_deploy_key_mock/2, create_webhook: &create_webhook_mock/2, remove_webhook: &remove_webhook_mock/2 + ]}, + {RepositoryHub.UserClient, [:passthrough], + [ + describe: fn user_id -> + {:ok, + %{ + user_id: user_id, + user: %{creation_source: :NOT_SET} + }} + end, + get_repository_token: fn _integration_type, _user_id -> + {:ok, "mock-bitbucket-token"} + end ]} ] end diff --git a/repository_hub/test/support/factories/git_clients/gitlab_client_factory.ex b/repository_hub/test/support/factories/git_clients/gitlab_client_factory.ex index 4e2f28351..dc9f95a06 100644 --- a/repository_hub/test/support/factories/git_clients/gitlab_client_factory.ex +++ b/repository_hub/test/support/factories/git_clients/gitlab_client_factory.ex @@ -26,6 +26,19 @@ defmodule RepositoryHub.GitlabClientFactory do find_webhook: &find_webhook_mock/2, find_user: &find_user_mock/2, fork: &fork_mock/2 + ]}, + {RepositoryHub.UserClient, [:passthrough], + [ + describe: fn user_id -> + {:ok, + %{ + user_id: user_id, + user: %{creation_source: :NOT_SET} + }} + end, + get_repository_token: fn _integration_type, _user_id -> + {:ok, "mock-gitlab-token"} + end ]} ] end