Skip to content

Commit

Permalink
2668 GitHub connection auditing (#2742)
Browse files Browse the repository at this point in the history
* Trivial doc changes

* Audit repo creation

* Audit repo removals

* tidy up

* Update CHANGELOG

* Fix typo

* Update CHANGELOG

* Move auditing to VersionControl.Audit

* Fix
  • Loading branch information
rorymckinley authored Jan 8, 2025
1 parent 8e082b2 commit a278d52
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ and this project adheres to

- Handle errors from the AI Assistant more gracefully
[#2741](https://github.com/OpenFn/lightning/issues/2741)
- Audit the creation and removal of Github repo connections.
[#2668](https://github.com/OpenFn/lightning/issues/2668)

### Changed

Expand Down
6 changes: 3 additions & 3 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ Note that for secure deployments, it's recommended to use a combination of
JSON plug and may (in future) limit the size of dataclips that can be stored
as run_results via the websocket connection from a worker.

### Github
### GitHub

Lightning enables connection to github via Github Apps. The following github
permissions are needed for the github app:
Lightning enables connection to GitHub via GitHub Apps. The following GitHub
repository permissions are needed for the GitHub app:

| **Resource** | **Access** |
| ------------ | -------------- |
Expand Down
57 changes: 57 additions & 0 deletions lib/lightning/version_control/audit.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule Lightning.VersionControl.Audit do
@moduledoc """
Generate Audit changesets for changes related to VersionControl.
"""
use Lightning.Auditing.Audit,
repo: Lightning.Repo,
item: "project",
events: [
"repo_connection_created",
"repo_connection_removed"
]

@spec repo_connection(
Lightning.VersionControl.ProjectRepoConnection.t(),
:created | :removed,
Lightning.Accounts.User.t()
| Lightning.VersionControl.ProjectRepoConnection.t()
| Lightning.Workflows.Trigger.t()
) :: Ecto.Changeset.t()
def repo_connection(repo_connection, action, actor) do
%{
branch: branch,
config_path: config_path,
project_id: project_id,
repo: repo
} = repo_connection

changes =
%{
branch: branch,
repo: repo
}
|> then(fn connection_properties ->
if config_path do
Map.put(connection_properties, :config_path, config_path)
else
connection_properties
end
end)
|> then(fn connection_properties ->
if action == :created do
%{
after:
Map.put(
connection_properties,
:sync_direction,
repo_connection.sync_direction
)
}
else
%{before: connection_properties}
end
end)

event("repo_connection_#{action}", project_id, actor, changes)
end
end
17 changes: 14 additions & 3 deletions lib/lightning/version_control/version_control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ defmodule Lightning.VersionControl do

import Ecto.Query, warn: false

alias Ecto.Multi
alias Lightning.Accounts.User
alias Lightning.Extensions.UsageLimiting
alias Lightning.Repo
alias Lightning.VersionControl.Audit
alias Lightning.VersionControl.Events
alias Lightning.VersionControl.GithubClient
alias Lightning.VersionControl.GithubError
Expand All @@ -35,6 +37,10 @@ defmodule Lightning.VersionControl do

Repo.transact(fn ->
with {:ok, repo_connection} <- Repo.insert(changeset),
{:ok, _audit} <-
repo_connection
|> Audit.repo_connection(:created, user)
|> Repo.insert(),
:ok <-
VersionControlUsageLimiter.limit_github_sync(
repo_connection.project_id
Expand Down Expand Up @@ -103,10 +109,15 @@ defmodule Lightning.VersionControl do
Deletes a github connection
"""
def remove_github_connection(repo_connection, user) do
repo_connection
|> Repo.delete()
Multi.new()
|> Multi.delete(:delete_repo_connection, repo_connection)
|> Multi.insert(
:audit,
Audit.repo_connection(repo_connection, :removed, user)
)
|> Repo.transaction()
|> tap(fn
{:ok, repo_connection} ->
{:ok, %{delete_repo_connection: repo_connection}} ->
undo_repo_actions(
repo_connection,
user
Expand Down
2 changes: 1 addition & 1 deletion test/lightning/usage_tracking/day_worker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule Lightning.UsageTracking.DayWorkerTest do

%{tracking_enabled_at: enabled_at} = Repo.one(DailyReportConfiguration)

assert DateTime.diff(DateTime.utc_now(), enabled_at, :second) < 2
assert DateTime.diff(DateTime.utc_now(), enabled_at, :second) < 3
end

test "does not enqueue more jobs than the batch size" do
Expand Down
140 changes: 140 additions & 0 deletions test/lightning/version_control/audit_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
defmodule Lightning.VersionControl.AuditTest do
use Lightning.DataCase, async: true

alias Lightning.VersionControl.Audit

describe "repo_connection/3 - created" do
test "returns a changeset including the config_path" do
%{
branch: branch,
config_path: config_path,
project_id: project_id,
repo: repo,
sync_direction: sync_direction
} =
repo_connection =
insert(:project_repo_connection, config_path: "config_path")

%{id: user_id} = user = insert(:user)

changeset =
Audit.repo_connection(repo_connection, :created, user)

assert %{
changes: %{
event: "repo_connection_created",
item_type: "project",
item_id: ^project_id,
actor_id: ^user_id,
changes: %{
changes: audit_changes
}
},
valid?: true
} = changeset

assert %{
after: %{
branch: branch,
config_path: config_path,
repo: repo,
sync_direction: sync_direction
}
} == audit_changes
end

test "excludes the config_path from the changeset if it is nil" do
%{
branch: branch,
repo: repo,
sync_direction: sync_direction
} =
repo_connection =
insert(:project_repo_connection, config_path: nil)

user = insert(:user)

changeset =
Audit.repo_connection(repo_connection, :created, user)

%{changes: %{changes: %{changes: audit_changes}}} = changeset

assert %{
after: %{
branch: branch,
repo: repo,
sync_direction: sync_direction
}
} == audit_changes
end
end

describe "repo_connection/3 - :removed" do
test "returns a changeset including the config_path" do
%{
branch: branch,
config_path: config_path,
project_id: project_id,
repo: repo
} =
repo_connection =
insert(:project_repo_connection, config_path: "config_path")

%{id: user_id} = user = insert(:user)

changeset =
Audit.repo_connection(repo_connection, :removed, user)

assert %{
changes: %{
event: "repo_connection_removed",
item_type: "project",
item_id: ^project_id,
actor_id: ^user_id,
changes: %{
changes: audit_changes
}
},
valid?: true
} = changeset

assert %{
before: %{
branch: branch,
config_path: config_path,
repo: repo
}
} == audit_changes
end

test "excludes the config_path if it is nil" do
%{
branch: branch,
repo: repo
} =
repo_connection =
insert(:project_repo_connection, config_path: nil)

user = insert(:user)

changeset =
Audit.repo_connection(repo_connection, :removed, user)

assert %{
changes: %{
changes: %{
changes: audit_changes
}
},
valid?: true
} = changeset

assert %{
before: %{
branch: branch,
repo: repo
}
} == audit_changes
end
end
end
Loading

0 comments on commit a278d52

Please sign in to comment.