633 lines
19 KiB
Go
633 lines
19 KiB
Go
package handlers
|
|
|
|
import "database/sql"
|
|
import "encoding/json"
|
|
import "errors"
|
|
import "fmt"
|
|
import "net/http"
|
|
import "strings"
|
|
|
|
import "codit/internal/db"
|
|
import "codit/internal/gpg"
|
|
import "codit/internal/middleware"
|
|
import "codit/internal/models"
|
|
|
|
const personalGPGKeyUploadMaxBytes int64 = 256 * 1024
|
|
|
|
// gpgActor returns the acting user from the request context.
|
|
func (api *API) gpgActor(r *http.Request) (models.User, bool) {
|
|
return middleware.UserFromContext(r.Context())
|
|
}
|
|
|
|
// canManageGPGKey reports whether the user may modify or delete the key.
|
|
// Admins may manage any key; a regular user may manage only their own
|
|
// personal keys.
|
|
func (api *API) canManageGPGKey(r *http.Request, keyID string, user models.User) bool {
|
|
var owned bool
|
|
var err error
|
|
if user.IsAdmin {
|
|
return true
|
|
}
|
|
owned, err = api.store(r).GPGKeyPersonalOwner(keyID, user.ID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return owned
|
|
}
|
|
|
|
func (api *API) canManageProjectSigningKeys(r *http.Request, projectID string) bool {
|
|
var user models.User
|
|
var principal models.ServicePrincipal
|
|
var ok bool
|
|
var role string
|
|
var err error
|
|
|
|
user, ok = middleware.UserFromContext(r.Context())
|
|
if ok {
|
|
if user.IsAdmin { return true }
|
|
role, err = api.store(r).GetProjectRoleForUser(projectID, user.ID)
|
|
if err != nil { return false }
|
|
return roleAllows(role, models.RoleAdmin)
|
|
}
|
|
|
|
principal, ok = middleware.PrincipalFromContext(r.Context())
|
|
if !ok || principal.Disabled { return false }
|
|
if principal.IsAdmin { return true }
|
|
|
|
role, err = api.store(r).GetPrincipalProjectRole(principal.ID, projectID)
|
|
if err != nil { return false }
|
|
return roleAllows(role, models.RoleAdmin)
|
|
}
|
|
|
|
// ListMyGPGKeys returns the caller's registered personal (public-only) keys,
|
|
// used for commit/tag signature verification.
|
|
func (api *API) ListMyGPGKeys(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var ok bool
|
|
var keys []models.GPGKey
|
|
var err error
|
|
var i int
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
keys, err = api.store(r).ListPersonalGPGKeys(user.ID)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if keys == nil {
|
|
keys = []models.GPGKey{}
|
|
}
|
|
for i = range keys {
|
|
keys[i].CanManage = true
|
|
}
|
|
WriteJSON(w, http.StatusOK, keys)
|
|
}
|
|
|
|
// AddMyGPGKey registers a PUBLIC key (pasted by the user) for the caller. The
|
|
// private half is never sent or stored — the user keeps it locally to sign.
|
|
func (api *API) AddMyGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var ok bool
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
PublicKey string `json:"public_key"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var key models.GPGKey
|
|
var stored models.GPGKey
|
|
var err error
|
|
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, personalGPGKeyUploadMaxBytes)
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(req.PublicKey) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "public key is required")
|
|
return
|
|
}
|
|
|
|
km, err = gpg.ParsePublic(req.PublicKey)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid public key: "+err.Error())
|
|
return
|
|
}
|
|
|
|
key = api.buildPersonalKey(user.ID, req.Name, km)
|
|
|
|
// it persists a public-only personal key, rejecting a
|
|
// duplicate fingerprint already registered by any user.
|
|
stored, err = api.store(r).CreateGPGKey(r.Context(), key, user.ID, "")
|
|
if err != nil {
|
|
if errors.Is(err, db.ErrGPGKeyAlreadyRegistered) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, "this key is already registered")
|
|
return
|
|
}
|
|
if errors.Is(err, db.ErrPersonalGPGKeyLimitReached) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, "personal gpg key limit reached")
|
|
return
|
|
}
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
stored.CanManage = true
|
|
WriteJSON(w, http.StatusOK, stored)
|
|
}
|
|
|
|
// GenerateMyGPGKey is a convenience: it generates a keypair, registers the
|
|
// PUBLIC half for the caller, and returns the PRIVATE key ONCE for the user to
|
|
// download and import locally. The private key is never stored server-side.
|
|
func (api *API) GenerateMyGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var ok bool
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var key models.GPGKey
|
|
var stored models.GPGKey
|
|
var err error
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
km, err = gpg.Generate(req.Name, req.Email)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
key = api.buildPersonalKey(user.ID, req.Name, km)
|
|
stored, err = api.store(r).CreateGPGKey(r.Context(), key, user.ID, "")
|
|
if err != nil {
|
|
if errors.Is(err, db.ErrGPGKeyAlreadyRegistered) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, "this key is already registered")
|
|
return
|
|
}
|
|
if errors.Is(err, db.ErrPersonalGPGKeyLimitReached) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, "personal gpg key limit reached")
|
|
return
|
|
}
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
stored.CanManage = true
|
|
// Return the private key once, for the user to save and import locally.
|
|
WriteJSON(w, http.StatusOK, map[string]any{
|
|
"key": stored,
|
|
"private_key": km.PrivateArmored,
|
|
"public_key": km.PublicArmored,
|
|
})
|
|
}
|
|
|
|
// DeleteMyGPGKey removes one of the caller's registered personal keys.
|
|
func (api *API) DeleteMyGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var ok bool
|
|
var owned bool
|
|
var err error
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
owned, err = api.store(r).GPGKeyPersonalOwner(params["id"], user.ID)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if !owned {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "not permitted")
|
|
return
|
|
}
|
|
err = api.store(r).DeleteGPGKey(params["id"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|
|
|
|
// buildPersonalKey assembles a public-only personal GPGKey from key material.
|
|
func (api *API) buildPersonalKey(_ string, name string, km gpg.KeyMaterial) models.GPGKey {
|
|
var key models.GPGKey
|
|
key.Scope = models.GPGKeyScopePersonal
|
|
key.Name = strings.TrimSpace(name)
|
|
if key.Name == "" {
|
|
key.Name = km.KeyID
|
|
}
|
|
key.Fingerprint = km.Fingerprint
|
|
key.KeyID = km.KeyID
|
|
key.PublicKey = km.PublicArmored
|
|
key.PrivateKey = "" // public-only: never store the private half
|
|
return key
|
|
}
|
|
|
|
// ListAllGPGKeys returns every key for admin oversight (admins may manage any).
|
|
func (api *API) ListAllGPGKeys(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var keys []models.GPGKey
|
|
var err error
|
|
var i int
|
|
if !api.requireAdmin(w, r) {
|
|
return
|
|
}
|
|
keys, err = api.store(r).ListGPGKeys()
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if keys == nil {
|
|
keys = []models.GPGKey{}
|
|
}
|
|
for i = range keys {
|
|
keys[i].CanManage = true
|
|
}
|
|
WriteJSON(w, http.StatusOK, keys)
|
|
}
|
|
|
|
func (api *API) GetGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var ok bool
|
|
var key models.GPGKey
|
|
var err error
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
key, err = api.store(r).GetGPGKey(params["id"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
// A user may view their own personal key; admins may view any key.
|
|
if !api.canManageGPGKey(r, key.ID, user) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "not permitted")
|
|
return
|
|
}
|
|
key.CanManage = true
|
|
WriteJSON(w, http.StatusOK, key)
|
|
}
|
|
|
|
// createGlobalGPGKey persists an organization-wide (global) signing key.
|
|
// Personal keys are public-only and registered via /api/me/gpg-keys instead.
|
|
func (api *API) createGlobalGPGKey(w http.ResponseWriter, r *http.Request, name string, km gpg.KeyMaterial) {
|
|
var key models.GPGKey
|
|
var err error
|
|
key.Scope = models.GPGKeyScopeGlobal
|
|
key.Name = strings.TrimSpace(name)
|
|
key.Fingerprint = km.Fingerprint
|
|
key.KeyID = km.KeyID
|
|
key.PublicKey = km.PublicArmored
|
|
key.PrivateKey = km.PrivateArmored
|
|
|
|
key, err = api.store(r).CreateGPGKey(r.Context(), key, "", "")
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
key.CanManage = true
|
|
WriteJSON(w, http.StatusOK, key)
|
|
}
|
|
|
|
func (api *API) GenerateGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var err error
|
|
if !api.requireAdmin(w, r) {
|
|
return
|
|
}
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
km, err = gpg.Generate(req.Name, req.Email)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
api.createGlobalGPGKey(w, r, req.Name, km)
|
|
}
|
|
|
|
func (api *API) ImportGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
PrivateKey string `json:"private_key"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var err error
|
|
if !api.requireAdmin(w, r) {
|
|
return
|
|
}
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.PrivateKey) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "private key is required")
|
|
return
|
|
}
|
|
km, err = gpg.Parse(req.PrivateKey)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid private key: "+err.Error())
|
|
return
|
|
}
|
|
api.createGlobalGPGKey(w, r, req.Name, km)
|
|
}
|
|
|
|
// ListProjectSigningKeys returns the signing keys owned by the project. Any
|
|
// project viewer may list them.
|
|
func (api *API) ListProjectSigningKeys(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var keys []models.GPGKey
|
|
var err error
|
|
var i int
|
|
var canManage bool
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleViewer) {
|
|
return
|
|
}
|
|
canManage = api.canManageProjectSigningKeys(r, params["projectId"])
|
|
keys, err = api.store(r).ListProjectGPGKeys(params["projectId"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if keys == nil {
|
|
keys = []models.GPGKey{}
|
|
}
|
|
for i = range keys {
|
|
keys[i].CanManage = canManage
|
|
}
|
|
WriteJSON(w, http.StatusOK, keys)
|
|
}
|
|
|
|
func (api *API) GetProjectSigningKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var key models.GPGKey
|
|
var belongs bool
|
|
var err error
|
|
var canManage bool
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleViewer) {
|
|
return
|
|
}
|
|
canManage = api.canManageProjectSigningKeys(r, params["projectId"])
|
|
belongs, err = api.store(r).GPGKeyBelongsToProject(params["keyId"], params["projectId"])
|
|
if err != nil || !belongs {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
key, err = api.store(r).GetGPGKey(params["keyId"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
key.CanManage = canManage
|
|
WriteJSON(w, http.StatusOK, key)
|
|
}
|
|
|
|
// ListSelectableSigningKeys returns the keys a project may sign with: the
|
|
// project's own keys plus all org keys. Used to populate a repo's signing-key
|
|
// selector.
|
|
func (api *API) ListSelectableSigningKeys(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var keys []models.GPGKey
|
|
var err error
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleWriter) {
|
|
return
|
|
}
|
|
keys, err = api.store(r).ListSelectableGPGKeys(params["projectId"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if keys == nil {
|
|
keys = []models.GPGKey{}
|
|
}
|
|
WriteJSON(w, http.StatusOK, keys)
|
|
}
|
|
|
|
// createProjectSigningKey persists a project-scoped key after the request has
|
|
// been authorized as a project admin.
|
|
func (api *API) createProjectSigningKey(w http.ResponseWriter, r *http.Request, projectID string, name string, km gpg.KeyMaterial) {
|
|
var key models.GPGKey
|
|
var err error
|
|
|
|
key = models.GPGKey{
|
|
Name: strings.TrimSpace(name),
|
|
Fingerprint: km.Fingerprint,
|
|
KeyID: km.KeyID,
|
|
PublicKey: km.PublicArmored,
|
|
PrivateKey: km.PrivateArmored,
|
|
Scope: models.GPGKeyScopeProject,
|
|
}
|
|
|
|
key, err = api.store(r).CreateGPGKey(r.Context(), key, "", projectID)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
key.CanManage = true
|
|
WriteJSON(w, http.StatusOK, key)
|
|
}
|
|
|
|
func (api *API) GenerateProjectSigningKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var err error
|
|
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleAdmin) { return }
|
|
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
km, err = gpg.Generate(req.Name, req.Email)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
api.createProjectSigningKey(w, r, params["projectId"], req.Name, km)
|
|
}
|
|
|
|
func (api *API) ImportProjectSigningKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
PrivateKey string `json:"private_key"`
|
|
}
|
|
var km gpg.KeyMaterial
|
|
var err error
|
|
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleAdmin) { return }
|
|
|
|
err = json.NewDecoder(r.Body).Decode(&req)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Name) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "name is required")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.PrivateKey) == "" {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "private key is required")
|
|
return
|
|
}
|
|
km, err = gpg.Parse(req.PrivateKey)
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "invalid private key: "+err.Error())
|
|
return
|
|
}
|
|
api.createProjectSigningKey(w, r, params["projectId"], req.Name, km)
|
|
}
|
|
|
|
func (api *API) DeleteProjectSigningKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var belongs bool
|
|
var usage []models.GPGKeyRepoUsage
|
|
var err error
|
|
|
|
if !api.requireProjectRole(w, r, params["projectId"], models.RoleAdmin) { return }
|
|
|
|
belongs, err = api.store(r).GPGKeyBelongsToProject(params["keyId"], params["projectId"])
|
|
if err != nil || !belongs {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
usage, err = api.store(r).ListReposUsingGPGKey(params["keyId"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if len(usage) > 0 {
|
|
var noun string
|
|
noun = "repository"
|
|
if len(usage) != 1 { noun = "repositories" }
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, fmt.Sprintf("signing key is in use by %d %s; remove it from them first", len(usage), noun))
|
|
return
|
|
}
|
|
err = api.store(r).DeleteGPGKey(params["keyId"])
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|
|
|
|
func (api *API) DeleteGPGKey(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var user models.User
|
|
var usage []models.GPGKeyRepoUsage
|
|
var ok bool
|
|
var err error
|
|
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
if !api.canManageGPGKey(r, params["id"], user) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "not permitted")
|
|
return
|
|
}
|
|
usage, err = api.store(r).ListReposUsingGPGKey(params["id"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if len(usage) > 0 {
|
|
var noun string
|
|
noun = "repository"
|
|
if len(usage) != 1 { noun = "repositories" }
|
|
WriteJSONWithErrorReason(w, r, http.StatusConflict, fmt.Sprintf("signing key is in use by %d %s; remove it from them first", len(usage), noun))
|
|
return
|
|
}
|
|
err = api.store(r).DeleteGPGKey(params["id"])
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|
|
|
|
// GPGKeyUsage returns the repositories that reference this key as their signing
|
|
// key, so the admin/owner can see who's using it before deleting.
|
|
func (api *API) GPGKeyUsage(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
|
var key models.GPGKey
|
|
var user models.User
|
|
var ok bool
|
|
var usage []models.GPGKeyRepoUsage
|
|
var err error
|
|
|
|
user, ok = api.gpgActor(r)
|
|
if !ok {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "authentication required")
|
|
return
|
|
}
|
|
key, err = api.store(r).GetGPGKey(params["id"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusNotFound, "gpg key not found")
|
|
return
|
|
}
|
|
if !api.canManageGPGKey(r, params["id"], user) {
|
|
if key.Scope != models.GPGKeyScopeProject || key.OwnerProjectPublicID == "" || !api.canManageProjectSigningKeys(r, key.OwnerProjectPublicID) {
|
|
WriteJSONWithErrorReason(w, r, http.StatusForbidden, "not permitted")
|
|
return
|
|
}
|
|
}
|
|
usage, err = api.store(r).ListReposUsingGPGKey(params["id"])
|
|
if err != nil {
|
|
WriteJSONWithErrorReason(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if usage == nil {
|
|
usage = []models.GPGKeyRepoUsage{}
|
|
}
|
|
WriteJSON(w, http.StatusOK, usage)
|
|
}
|