Changes profile import endpoint to use standard API token authentication: API Token Changes: - Add user_id to api_tokens table (tracks who created the token) - Update ApiTokens.verify_token/1 to return user along with org_id - Update ApiAuth plug to assign current_user from token Profile Import Changes: - Move endpoint from /api/v1/admin/profiles/import to /api/v1/profiles/import - Check user.is_superuser in controller instead of using RequireSuperuser plug - Use api_v1 pipeline (Bearer token auth) instead of browser session - Update documentation to show API token usage Security: - Only API tokens created by superusers can import profiles - Returns 403 Forbidden if token user is not a superuser - Logs import attempts with user email for audit trail This provides a consistent API experience using Bearer tokens instead of requiring browser session cookies.
94 lines
2.6 KiB
Elixir
94 lines
2.6 KiB
Elixir
defmodule ToweropsWeb.Api.V1.ProfilesController do
|
|
@moduledoc """
|
|
API controller for managing device SNMP profiles.
|
|
|
|
All endpoints require API token authentication.
|
|
"""
|
|
use ToweropsWeb, :controller
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
POST /api/v1/profiles/import
|
|
|
|
Imports device profiles from JSON data exported via `mix export_profiles`.
|
|
|
|
**Requires superuser API token** - Only API tokens created by superuser accounts can import profiles.
|
|
|
|
Request body:
|
|
[
|
|
{
|
|
"os": "epmp",
|
|
"detection": { ... }, # YAML detection data
|
|
"discovery": { ... } # YAML discovery data (optional)
|
|
},
|
|
...
|
|
]
|
|
|
|
Response:
|
|
{
|
|
"status": "queued",
|
|
"job_id": "uuid",
|
|
"profile_count": 15,
|
|
"message": "Import job queued successfully..."
|
|
}
|
|
|
|
Example:
|
|
curl -X POST https://towerops.net/api/v1/profiles/import \\
|
|
-H "Authorization: Bearer YOUR_SUPERUSER_TOKEN" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d @profiles.json
|
|
"""
|
|
def import_bulk(conn, %{"_json" => profiles}) when is_list(profiles) do
|
|
# Check if user is a superuser
|
|
user = conn.assigns[:current_user]
|
|
|
|
if !user || !user.is_superuser do
|
|
Logger.warning("Profile import attempted by non-superuser: #{inspect(user && user.email)}")
|
|
|
|
conn
|
|
|> put_status(:forbidden)
|
|
|> json(%{error: "Superuser access required. Only API tokens created by superusers can import profiles."})
|
|
else
|
|
Logger.info("Queueing import for #{length(profiles)} device profiles via API (user: #{user.email})")
|
|
|
|
do_import_bulk(conn, profiles)
|
|
end
|
|
end
|
|
|
|
def import_bulk(conn, _params) do
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Invalid request format. Expected array of profile objects."})
|
|
end
|
|
|
|
# Private functions
|
|
|
|
defp do_import_bulk(conn, profiles) do
|
|
# Generate unique job ID for tracking
|
|
job_id = Ecto.UUID.generate()
|
|
|
|
# Enqueue background job
|
|
case Exq.enqueue(Exq, "maintenance", Towerops.Workers.ProfileImportWorker, [
|
|
profiles,
|
|
job_id
|
|
]) do
|
|
{:ok, _job_jid} ->
|
|
Logger.info("Profile import job #{job_id} queued successfully")
|
|
|
|
json(conn, %{
|
|
status: "queued",
|
|
job_id: job_id,
|
|
profile_count: length(profiles),
|
|
message: "Import job queued successfully. #{length(profiles)} profiles will be imported in the background."
|
|
})
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Failed to queue profile import: #{inspect(reason)}")
|
|
|
|
conn
|
|
|> put_status(:internal_server_error)
|
|
|> json(%{error: "Failed to queue import job", reason: inspect(reason)})
|
|
end
|
|
end
|
|
end
|