76 lines
2.6 KiB
Go
76 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"agent-system-backend/internal/store"
|
|
"agent-system-backend/pkg/extension"
|
|
)
|
|
|
|
// explainTargetParams would select another actor; the endpoint refuses them
|
|
// outright so the response never depends on whether that actor exists.
|
|
var explainTargetParams = []string{"owner", "userId", "user", "actor"}
|
|
|
|
func (s *Server) explainCapability(w http.ResponseWriter, r *http.Request) {
|
|
if SessionScope(r) == store.SessionScopeSetPasswordOnly {
|
|
http.Error(w, "capability explain denied", http.StatusForbidden)
|
|
return
|
|
}
|
|
query := r.URL.Query()
|
|
for _, key := range explainTargetParams {
|
|
if query.Has(key) {
|
|
http.Error(w, "capability explain is limited to the current actor", http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
explanation, err := s.authorizer.ExplainCapabilities(s.owner(r))
|
|
if errors.Is(err, store.ErrAccountForbidden) {
|
|
http.Error(w, "capability explain denied", http.StatusForbidden)
|
|
return
|
|
}
|
|
if err != nil {
|
|
http.Error(w, "capability explain unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, explanation)
|
|
}
|
|
|
|
// categorizeWorkFailure maps Backend and provider errors for operation into
|
|
// one WorkFailure category. Uncategorized errors, notably not-found, keep the
|
|
// existing handling so a refusal never reveals another project or account.
|
|
func categorizeWorkFailure(operation string, err error) (extension.WorkFailure, bool) {
|
|
var failure extension.WorkFailure
|
|
switch {
|
|
case err == nil:
|
|
return extension.WorkFailure{}, false
|
|
case errors.As(err, &failure) && failure.StatusCode() != 0:
|
|
return extension.CategorizeWorkFailure(operation, err)
|
|
case errors.Is(err, store.ErrAccountForbidden):
|
|
return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailurePolicyRejected}, true
|
|
case operation != extension.WorkOperationConversation && errors.Is(err, store.ErrConflict):
|
|
return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailureDuplicateName}, true
|
|
case errors.Is(err, sql.ErrConnDone), errors.Is(err, driver.ErrBadConn):
|
|
return extension.WorkFailure{Operation: operation, Reason: extension.WorkFailureServiceUnavailable}, true
|
|
}
|
|
return extension.CategorizeWorkFailure(operation, err)
|
|
}
|
|
|
|
func writeWorkFailure(w http.ResponseWriter, operation string, err error) {
|
|
if failure, ok := categorizeWorkFailure(operation, err); ok {
|
|
http.Error(w, failure.Error(), failure.StatusCode())
|
|
return
|
|
}
|
|
writeProjectErr(w, err)
|
|
}
|
|
|
|
func writeWorkJSON(w http.ResponseWriter, operation string, v any, err error) {
|
|
if err != nil {
|
|
writeWorkFailure(w, operation, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, v)
|
|
}
|