014003775b
Add UpdateEnabled methods on CredentialRepository, ConnectionRepository, and ScheduleRepository that toggle the is_enabled flag while preserving updated_utc. Expose corresponding Enable/Disable handlers and wire POST /{id}/enable and POST /{id}/disable routes under /api/v1/credentials, /api/v1/ad-connections, and /api/v1/schedules. Each toggle emits an audit event. Also finalizes CredentialService.Test and TestCredentialInput so the existing /credentials/{id}/test handler compiles and runs against an LDAP host using the stored username and decrypted secret.
260 lines
9.4 KiB
Go
260 lines
9.4 KiB
Go
// Package api - Credentials handlers
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/audit"
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/logging"
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/models"
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/services"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// CredentialsHandler handles credential API endpoints
|
|
type CredentialsHandler struct {
|
|
service *services.CredentialService
|
|
auditService *audit.Service
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewCredentialsHandler creates a new CredentialsHandler
|
|
func NewCredentialsHandler(service *services.CredentialService, auditService *audit.Service, logger *logging.Logger) *CredentialsHandler {
|
|
return &CredentialsHandler{
|
|
service: service,
|
|
auditService: auditService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CredentialRequest represents a credential create/update request
|
|
type CredentialRequest struct {
|
|
Name string `json:"name"`
|
|
Description *string `json:"description,omitempty"`
|
|
CredentialType string `json:"credentialType"`
|
|
Username *string `json:"username,omitempty"`
|
|
Password *string `json:"password,omitempty"`
|
|
IsEnabled *bool `json:"isEnabled,omitempty"`
|
|
}
|
|
|
|
// CredentialResponse represents a credential in responses
|
|
type CredentialResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description *string `json:"description,omitempty"`
|
|
CredentialType string `json:"credentialType"`
|
|
Username *string `json:"username,omitempty"`
|
|
IsEnabled bool `json:"isEnabled"`
|
|
LastTestedAt *string `json:"lastTestedAt,omitempty"`
|
|
LastTestResult *string `json:"lastTestResult,omitempty"`
|
|
CreatedAt string `json:"createdAt"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
}
|
|
|
|
// List handles GET /api/v1/credentials
|
|
func (h *CredentialsHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
p := ParsePagination(r)
|
|
creds, total, err := h.service.List(p.Page, p.PageSize)
|
|
if err != nil {
|
|
h.logger.Error("CredentialsHandler", "Failed to list credentials: %v", err)
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to list credentials")
|
|
return
|
|
}
|
|
|
|
var response []CredentialResponse
|
|
for _, c := range creds {
|
|
response = append(response, credentialToResponse(&c))
|
|
}
|
|
|
|
WriteList(w, response, p.Page, p.PageSize, total)
|
|
}
|
|
|
|
// Get handles GET /api/v1/credentials/{id}
|
|
func (h *CredentialsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
cred, err := h.service.GetByID(id)
|
|
if err != nil {
|
|
h.logger.Error("CredentialsHandler", "Failed to get credential: %v", err)
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to get credential")
|
|
return
|
|
}
|
|
if cred == nil {
|
|
WriteError(w, http.StatusNotFound, ErrCodeNotFound, "Credential not found")
|
|
return
|
|
}
|
|
|
|
WriteJSON(w, http.StatusOK, credentialToResponse(cred))
|
|
}
|
|
|
|
// Create handles POST /api/v1/credentials
|
|
func (h *CredentialsHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var req CredentialRequest
|
|
if err := DecodeJSON(r, &req); err != nil {
|
|
WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Name == "" {
|
|
WriteError(w, http.StatusBadRequest, ErrCodeValidation, "Name is required")
|
|
return
|
|
}
|
|
|
|
cred, err := h.service.Create(services.CreateCredentialInput{
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
CredentialType: req.CredentialType,
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("CredentialsHandler", "Failed to create credential: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventCreate, "Credential", "", "Create", false,
|
|
map[string]any{"name": req.Name}, err.Error())
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to create credential")
|
|
return
|
|
}
|
|
|
|
emitAudit(h.auditService, r, audit.EventCreate, "Credential", cred.ID, "Create", true,
|
|
map[string]any{"name": cred.Name, "type": cred.CredentialType}, "")
|
|
WriteJSON(w, http.StatusCreated, credentialToResponse(cred))
|
|
}
|
|
|
|
// Update handles PUT /api/v1/credentials/{id}
|
|
func (h *CredentialsHandler) Update(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
var req CredentialRequest
|
|
if err := DecodeJSON(r, &req); err != nil {
|
|
WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
|
|
cred, err := h.service.Update(services.UpdateCredentialInput{
|
|
ID: id,
|
|
Name: &req.Name,
|
|
Description: req.Description,
|
|
CredentialType: &req.CredentialType,
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
IsEnabled: req.IsEnabled,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("CredentialsHandler", "Failed to update credential: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", id, "Update", false, nil, err.Error())
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to update credential")
|
|
return
|
|
}
|
|
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", cred.ID, "Update", true,
|
|
map[string]any{"name": cred.Name}, "")
|
|
WriteJSON(w, http.StatusOK, credentialToResponse(cred))
|
|
}
|
|
|
|
// CredentialTestRequest represents a credential test request body
|
|
type CredentialTestRequest struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port,omitempty"`
|
|
UseTLS bool `json:"useTLS,omitempty"`
|
|
UseStartTLS bool `json:"useStartTLS,omitempty"`
|
|
AllowInvalidCerts bool `json:"allowInvalidCerts,omitempty"`
|
|
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
|
}
|
|
|
|
// Test handles POST /api/v1/credentials/{id}/test
|
|
func (h *CredentialsHandler) Test(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
var req CredentialTestRequest
|
|
if err := DecodeJSON(r, &req); err != nil {
|
|
WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body")
|
|
return
|
|
}
|
|
if req.Host == "" {
|
|
WriteError(w, http.StatusBadRequest, ErrCodeValidation, "host is required")
|
|
return
|
|
}
|
|
|
|
result, err := h.service.Test(id, services.TestCredentialInput{
|
|
Host: req.Host,
|
|
Port: req.Port,
|
|
UseTLS: req.UseTLS,
|
|
UseStartTLS: req.UseStartTLS,
|
|
AllowInvalidCerts: req.AllowInvalidCerts,
|
|
TimeoutSeconds: req.TimeoutSeconds,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("CredentialsHandler", "Test failed: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventTest, "Credential", id, "Test", false,
|
|
map[string]any{"host": req.Host}, err.Error())
|
|
status := http.StatusInternalServerError
|
|
code := ErrCodeInternalError
|
|
if err.Error() == "credential not found" {
|
|
status = http.StatusNotFound
|
|
code = ErrCodeNotFound
|
|
}
|
|
WriteError(w, status, code, err.Error())
|
|
return
|
|
}
|
|
emitAudit(h.auditService, r, audit.EventTest, "Credential", id, "Test", result.Success,
|
|
map[string]any{"host": req.Host}, "")
|
|
WriteJSON(w, http.StatusOK, result)
|
|
}
|
|
|
|
// Delete handles DELETE /api/v1/credentials/{id}
|
|
func (h *CredentialsHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if err := h.service.Delete(id); err != nil {
|
|
h.logger.Error("CredentialsHandler", "Failed to delete credential: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventDelete, "Credential", id, "Delete", false, nil, err.Error())
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, err.Error())
|
|
return
|
|
}
|
|
|
|
emitAudit(h.auditService, r, audit.EventDelete, "Credential", id, "Delete", true, nil, "")
|
|
WriteJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
|
}
|
|
|
|
func credentialToResponse(c *models.Credential) CredentialResponse {
|
|
resp := CredentialResponse{
|
|
ID: c.ID,
|
|
Name: c.Name,
|
|
Description: c.Description,
|
|
CredentialType: c.CredentialType,
|
|
Username: c.Username,
|
|
IsEnabled: c.IsEnabled,
|
|
LastTestResult: c.LastTestResult,
|
|
CreatedAt: c.CreatedUTC.Format(time.RFC3339),
|
|
UpdatedAt: c.UpdatedUTC.Format(time.RFC3339),
|
|
}
|
|
if c.LastTestedUTC != nil {
|
|
s := c.LastTestedUTC.Format(time.RFC3339)
|
|
resp.LastTestedAt = &s
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// Enable handles POST /api/v1/credentials/{id}/enable
|
|
func (h *CredentialsHandler) Enable(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if err := h.service.SetEnabled(id, true); err != nil {
|
|
h.logger.Error("CredentialsHandler", "Enable failed: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", id, "Enable", false, nil, err.Error())
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to enable credential")
|
|
return
|
|
}
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", id, "Enable", true, map[string]any{"isEnabled": true}, "")
|
|
WriteJSON(w, http.StatusOK, map[string]bool{"enabled": true})
|
|
}
|
|
|
|
// Disable handles POST /api/v1/credentials/{id}/disable
|
|
func (h *CredentialsHandler) Disable(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if err := h.service.SetEnabled(id, false); err != nil {
|
|
h.logger.Error("CredentialsHandler", "Disable failed: %v", err)
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", id, "Disable", false, nil, err.Error())
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to disable credential")
|
|
return
|
|
}
|
|
emitAudit(h.auditService, r, audit.EventUpdate, "Credential", id, "Disable", true, map[string]any{"isEnabled": false}, "")
|
|
WriteJSON(w, http.StatusOK, map[string]bool{"enabled": false})
|
|
}
|