Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ workflows:
- build:
name: "Check for security vulnerabilities"
execute:
- run: sudo -u lightning mix sobelow
- run: sudo -u lightning mix sobelow --threshold medium
- build:
name: "Check Elixir tests (codecov)"
execute:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: erlef/setup-elixir@v1
with:
otp-version: 26
Expand Down
2 changes: 1 addition & 1 deletion .sobelow-conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
format: "txt",
out: "",
threshold: "low",
ignore: ["Config.CSP"],
ignore: ["Config.CSP", "Config.HTTPS"],
ignore_files: [""]
]
53 changes: 34 additions & 19 deletions lib/lightning/auth_providers/oauth_http_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do

Returns structured error responses that integrate well with the audit system.
"""
use Tesla

alias LightningWeb.RouteHelpers

require Logger

defp adapter do
Application.get_env(:tesla, __MODULE__, [])[:adapter]
end

@doc """
Revokes an OAuth token.

Expand Down Expand Up @@ -77,10 +79,13 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do
redirect_uri: RouteHelpers.oidc_callback_url()
}

Tesla.client([
Tesla.Middleware.FormUrlencoded
])
|> post(client.token_endpoint, body)
Tesla.client(
[
Tesla.Middleware.FormUrlencoded
],
adapter()
)
|> Tesla.post(client.token_endpoint, body)
|> handle_response([200])
|> maybe_introspect(client)
end
Expand Down Expand Up @@ -114,10 +119,13 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do
grant_type: "refresh_token"
}

Tesla.client([
Tesla.Middleware.FormUrlencoded
])
|> post(client.token_endpoint, body)
Tesla.client(
[
Tesla.Middleware.FormUrlencoded
],
adapter()
)
|> Tesla.post(client.token_endpoint, body)
|> handle_response([200])
|> maybe_introspect(client)
|> case do
Expand Down Expand Up @@ -155,7 +163,8 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do
else
headers = [{"Authorization", "Bearer #{access_token}"}]

get(client.userinfo_endpoint, headers: headers)
Tesla.client([{Tesla.Middleware.Headers, headers}], adapter())
|> Tesla.get(client.userinfo_endpoint)
|> handle_response([200])
end
end
Expand Down Expand Up @@ -194,10 +203,13 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do
client_secret: client.client_secret
}

Tesla.client([
Tesla.Middleware.FormUrlencoded
])
|> post(client.revocation_endpoint, body)
Tesla.client(
[
Tesla.Middleware.FormUrlencoded
],
adapter()
)
|> Tesla.post(client.revocation_endpoint, body)
|> handle_response([200, 204])
|> case do
{:ok, _} ->
Expand Down Expand Up @@ -240,10 +252,13 @@ defmodule Lightning.AuthProviders.OauthHTTPClient do
token_type_hint: "access_token"
}

Tesla.client([
Tesla.Middleware.FormUrlencoded
])
|> post(client.introspection_endpoint, body)
Tesla.client(
[
Tesla.Middleware.FormUrlencoded
],
adapter()
)
|> Tesla.post(client.introspection_endpoint, body)
|> handle_response([200])
end

Expand Down
17 changes: 11 additions & 6 deletions lib/lightning/usage_tracking/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,35 @@ defmodule Lightning.UsageTracking.Client do


"""
use Tesla, only: [:head, :post], docs: false

alias Lightning.UsageTracking.ResponseProcessor

defp adapter do
Application.get_env(:tesla, __MODULE__, [])[:adapter]
end

def submit_metrics(metrics, host) do
response =
host
|> build_client()
|> post("/api/metrics", metrics)
|> Tesla.post("/api/metrics", metrics)

if ResponseProcessor.successful?(response), do: :ok, else: :error
end

def reachable?(host) do
build_head_client(host)
|> head("/")
|> Tesla.head("/")
|> ResponseProcessor.successful?()
end

defp build_client(host) do
Tesla.client([{Tesla.Middleware.BaseUrl, host}, Tesla.Middleware.JSON])
Tesla.client(
[{Tesla.Middleware.BaseUrl, host}, Tesla.Middleware.JSON],
adapter()
)
end

defp build_head_client(host) do
Tesla.client([{Tesla.Middleware.BaseUrl, host}])
Tesla.client([{Tesla.Middleware.BaseUrl, host}], adapter())
end
end
10 changes: 6 additions & 4 deletions lib/lightning/usage_tracking/github_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@ defmodule Lightning.UsageTracking.GithubClient do
@moduledoc """
A github client to make unauthenticated HTTP requests to Github.
"""
use Tesla, only: [:head], docs: false

alias Lightning.UsageTracking.ResponseProcessor

@host "https://github.com/"

defp adapter do
Application.get_env(:tesla, __MODULE__, [])[:adapter]
end

def open_fn_commit?(nil = _commit_sha), do: false
def open_fn_commit?("" = _commit_sha), do: false

def open_fn_commit?(commit_sha) do
response =
@host
|> build_client()
|> head("OpenFn/lightning/commit/#{commit_sha}")
|> Tesla.head("OpenFn/lightning/commit/#{commit_sha}")

ResponseProcessor.successful_200?(response)
end

def build_client(host) do
Tesla.client([{Tesla.Middleware.BaseUrl, host}])
Tesla.client([{Tesla.Middleware.BaseUrl, host}], adapter())
end
end
67 changes: 39 additions & 28 deletions lib/lightning/version_control/github_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,116 +3,123 @@ defmodule Lightning.VersionControl.GithubClient do
Tesla github http client we use this to make any network requests
to github from Lightning
"""
use Tesla

alias Lightning.VersionControl.GithubError
alias Lightning.VersionControl.GithubToken

require Logger

plug(Tesla.Middleware.BaseUrl, "https://api.github.com")
plug(Tesla.Middleware.JSON)
defp adapter do
Application.get_env(:tesla, __MODULE__, [])[:adapter]
end

def create_repo_dispatch_event(client, repo_name, body) do
client |> post("/repos/#{repo_name}/dispatches", body) |> handle_resp([204])
client
|> Tesla.post("/repos/#{repo_name}/dispatches", body)
|> handle_resp([204])
end

def create_workflow_dispatch_event(client, repo_name, workflow_id, body) do
client
|> post(
|> Tesla.post(
"repos/#{repo_name}/actions/workflows/#{workflow_id}/dispatches",
body
)
|> handle_resp([204])
end

def get_installations(client) do
client |> get("/user/installations") |> handle_resp([200])
client |> Tesla.get("/user/installations") |> handle_resp([200])
end

def get_installation_repos(client, query \\ [page: 1, per_page: 100]) do
client
|> get("/installation/repositories", query: query)
|> Tesla.get("/installation/repositories", query: query)
|> handle_resp([200])
end

def get_repo(client, repo_name) do
client |> get("/repos/#{repo_name}") |> handle_resp([200])
client |> Tesla.get("/repos/#{repo_name}") |> handle_resp([200])
end

def get_repo_branches(client, repo_name) do
client |> get("/repos/#{repo_name}/branches") |> handle_resp([200])
client |> Tesla.get("/repos/#{repo_name}/branches") |> handle_resp([200])
end

def get_repo_content(client, repo, path, ref) do
client
|> get("/repos/#{repo}/contents/#{path}", query: [ref: ref])
|> Tesla.get("/repos/#{repo}/contents/#{path}", query: [ref: ref])
|> handle_resp([200])
end

def delete_repo_content(client, repo, path, body) do
client
|> delete("/repos/#{repo}/contents/#{path}", body: body)
|> Tesla.delete("/repos/#{repo}/contents/#{path}", body: body)
|> handle_resp([200])
end

def create_blob(client, repo, body) do
client |> post("/repos/#{repo}/git/blobs", body) |> handle_resp([201])
client |> Tesla.post("/repos/#{repo}/git/blobs", body) |> handle_resp([201])
end

def create_tree(client, repo, body) do
client |> post("/repos/#{repo}/git/trees", body) |> handle_resp([201])
client |> Tesla.post("/repos/#{repo}/git/trees", body) |> handle_resp([201])
end

def get_commit(client, repo, ref) do
client |> get("/repos/#{repo}/commits/#{ref}") |> handle_resp([200])
client |> Tesla.get("/repos/#{repo}/commits/#{ref}") |> handle_resp([200])
end

def create_commit(client, repo, body) do
client |> post("/repos/#{repo}/git/commits", body) |> handle_resp([201])
client
|> Tesla.post("/repos/#{repo}/git/commits", body)
|> handle_resp([201])
end

def create_ref(client, repo, body) do
client |> post("/repos/#{repo}/git/refs", body) |> handle_resp([201])
client |> Tesla.post("/repos/#{repo}/git/refs", body) |> handle_resp([201])
end

def update_ref(client, repo, ref, body) do
client |> post("/repos/#{repo}/git/refs/#{ref}", body) |> handle_resp([200])
client
|> Tesla.post("/repos/#{repo}/git/refs/#{ref}", body)
|> handle_resp([200])
end

def delete_ref(client, repo, ref) do
client |> delete("/repos/#{repo}/git/refs/#{ref}") |> handle_resp([204])
client
|> Tesla.delete("/repos/#{repo}/git/refs/#{ref}")
|> handle_resp([204])
end

def delete_app_grant(client, app_client_id, token) do
client
|> delete("/applications/#{app_client_id}/grant",
|> Tesla.delete("/applications/#{app_client_id}/grant",
body: %{access_token: token}
)
|> handle_resp([204])
end

def get_repo_public_key(client, repo) do
client
|> get("/repos/#{repo}/actions/secrets/public-key")
|> Tesla.get("/repos/#{repo}/actions/secrets/public-key")
|> handle_resp([200])
end

def get_repo_secret(client, repo, secret_name) do
client
|> get("/repos/#{repo}/actions/secrets/#{secret_name}")
|> Tesla.get("/repos/#{repo}/actions/secrets/#{secret_name}")
|> handle_resp([200])
end

def create_repo_secret(client, repo, secret_name, body) do
client
|> put("/repos/#{repo}/actions/secrets/#{secret_name}", body)
|> Tesla.put("/repos/#{repo}/actions/secrets/#{secret_name}", body)
|> handle_resp([201, 204])
end

def delete_repo_secret(client, repo, secret_name) do
client
|> delete("/repos/#{repo}/actions/secrets/#{secret_name}")
|> Tesla.delete("/repos/#{repo}/actions/secrets/#{secret_name}")
|> handle_resp([204])
end

Expand All @@ -126,26 +133,30 @@ defmodule Lightning.VersionControl.GithubClient do
]}
]

{:ok, Tesla.client(middleware)}
{:ok, Tesla.client(middleware, adapter())}
end

def build_bearer_client(token) do
middleware = [
{Tesla.Middleware.BaseUrl, "https://api.github.com"},
Tesla.Middleware.JSON,
{Tesla.Middleware.Headers,
[
{"Authorization", "Bearer #{token}"}
]}
]

{:ok, Tesla.client(middleware)}
{:ok, Tesla.client(middleware, adapter())}
end

def build_basic_auth_client(username, password) do
middleware = [
{Tesla.Middleware.BaseUrl, "https://api.github.com"},
Tesla.Middleware.JSON,
{Tesla.Middleware.BasicAuth, [username: username, password: password]}
]

{:ok, Tesla.client(middleware)}
{:ok, Tesla.client(middleware, adapter())}
end

def build_installation_client(installation_id) do
Expand All @@ -155,7 +166,7 @@ defmodule Lightning.VersionControl.GithubClient do

with {:ok, auth_token, _} <- GithubToken.build(cert, app_id),
{:ok, client} <- build_bearer_client(auth_token) do
case post(
case Tesla.post(
client,
"/app/installations/#{installation_id}/access_tokens",
""
Expand Down
2 changes: 2 additions & 0 deletions lib/lightning_web/controllers/project_file_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule LightningWeb.ProjectFileController do
alias Lightning.Repo
alias Lightning.Storage.ProjectFileDefinition

# sobelow_skip ["Traversal.SendDownload"]
# Path is safe: generated by storage_path_for_exports/2 using system UUIDs only
def download(conn, %{"id" => id}) do
project_file = Repo.get!(Lightning.Projects.File, id)

Expand Down
Loading