feat(API): update and delete secret for managing organization secrets (#26660)
- Add `UpdateSecret` function to modify org or user repo secret - Add `DeleteSecret` function to delete secret from an organization - Add `UpdateSecretOption` struct for updating secret options - Add `UpdateOrgSecret` function to update a secret in an organization - Add `DeleteOrgSecret` function to delete a secret in an organization GitHub API 1. Update Org Secret: https://docs.github.com/en/rest/actions/secrets?apiVersion=2022-11-28#create-or-update-an-organization-secret 2. Delete Org Secret: https://docs.github.com/en/rest/actions/secrets?apiVersion=2022-11-28#delete-an-organization-secret --------- Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
parent
7e30986667
commit
b62c8e7765
|
@ -6,12 +6,14 @@ package secret
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
secret_module "code.gitea.io/gitea/modules/secret"
|
secret_module "code.gitea.io/gitea/modules/secret"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +28,25 @@ type Secret struct {
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrSecretNotFound represents a "secret not found" error.
|
||||||
|
type ErrSecretNotFound struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrSecretNotFound checks if an error is a ErrSecretNotFound.
|
||||||
|
func IsErrSecretNotFound(err error) bool {
|
||||||
|
_, ok := err.(ErrSecretNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrSecretNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("secret was not found [name: %s]", err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrSecretNotFound) Unwrap() error {
|
||||||
|
return util.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
// newSecret Creates a new already encrypted secret
|
// newSecret Creates a new already encrypted secret
|
||||||
func newSecret(ownerID, repoID int64, name, data string) *Secret {
|
func newSecret(ownerID, repoID int64, name, data string) *Secret {
|
||||||
return &Secret{
|
return &Secret{
|
||||||
|
@ -93,3 +114,49 @@ func FindSecrets(ctx context.Context, opts FindSecretsOptions) ([]*Secret, error
|
||||||
func CountSecrets(ctx context.Context, opts *FindSecretsOptions) (int64, error) {
|
func CountSecrets(ctx context.Context, opts *FindSecretsOptions) (int64, error) {
|
||||||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(Secret))
|
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(Secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSecret changes org or user reop secret.
|
||||||
|
func UpdateSecret(ctx context.Context, orgID, repoID int64, name, data string) error {
|
||||||
|
sc := new(Secret)
|
||||||
|
name = strings.ToUpper(name)
|
||||||
|
has, err := db.GetEngine(ctx).
|
||||||
|
Where("owner_id=?", orgID).
|
||||||
|
And("repo_id=?", repoID).
|
||||||
|
And("name=?", name).
|
||||||
|
Get(sc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return ErrSecretNotFound{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.Data = encrypted
|
||||||
|
_, err = db.GetEngine(ctx).ID(sc.ID).Cols("data").Update(sc)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSecret deletes secret from an organization.
|
||||||
|
func DeleteSecret(ctx context.Context, orgID, repoID int64, name string) error {
|
||||||
|
sc := new(Secret)
|
||||||
|
has, err := db.GetEngine(ctx).
|
||||||
|
Where("owner_id=?", orgID).
|
||||||
|
And("repo_id=?", repoID).
|
||||||
|
And("name=?", strings.ToUpper(name)).
|
||||||
|
Get(sc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return ErrSecretNotFound{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.GetEngine(ctx).ID(sc.ID).Delete(new(Secret)); err != nil {
|
||||||
|
return fmt.Errorf("Delete: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -25,3 +25,12 @@ type CreateSecretOption struct {
|
||||||
// Data of the secret to create
|
// Data of the secret to create
|
||||||
Data string `json:"data" binding:"Required"`
|
Data string `json:"data" binding:"Required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSecretOption options when updating secret
|
||||||
|
// swagger:model
|
||||||
|
type UpdateSecretOption struct {
|
||||||
|
// Data of the secret to update
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Data string `json:"data" binding:"Required"`
|
||||||
|
}
|
||||||
|
|
|
@ -1301,6 +1301,9 @@ func Routes() *web.Route {
|
||||||
m.Group("/actions/secrets", func() {
|
m.Group("/actions/secrets", func() {
|
||||||
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
|
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
|
||||||
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateSecretOption{}), org.CreateOrgSecret)
|
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateSecretOption{}), org.CreateOrgSecret)
|
||||||
|
m.Combo("/{secretname}").
|
||||||
|
Put(reqToken(), reqOrgOwnership(), bind(api.UpdateSecretOption{}), org.UpdateOrgSecret).
|
||||||
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteOrgSecret)
|
||||||
})
|
})
|
||||||
m.Group("/public_members", func() {
|
m.Group("/public_members", func() {
|
||||||
m.Get("", org.ListPublicMembers)
|
m.Get("", org.ListPublicMembers)
|
||||||
|
|
|
@ -103,6 +103,10 @@ func CreateOrgSecret(ctx *context.APIContext) {
|
||||||
// "403":
|
// "403":
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
opt := web.GetForm(ctx).(*api.CreateSecretOption)
|
opt := web.GetForm(ctx).(*api.CreateSecretOption)
|
||||||
|
if err := actions.NameRegexMatch(opt.Name); err != nil {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateOrgSecret", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
s, err := secret_model.InsertEncryptedSecret(
|
s, err := secret_model.InsertEncryptedSecret(
|
||||||
ctx, ctx.Org.Organization.ID, 0, opt.Name, actions.ReserveLineBreakForTextarea(opt.Data),
|
ctx, ctx.Org.Organization.ID, 0, opt.Name, actions.ReserveLineBreakForTextarea(opt.Data),
|
||||||
)
|
)
|
||||||
|
@ -113,3 +117,90 @@ func CreateOrgSecret(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, convert.ToSecret(s))
|
ctx.JSON(http.StatusCreated, convert.ToSecret(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateOrgSecret update one secret of the organization
|
||||||
|
func UpdateOrgSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Update a secret value in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/UpdateSecretOption"
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: update one secret of the organization
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
secretName := ctx.Params(":secretname")
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateSecretOption)
|
||||||
|
err := secret_model.UpdateSecret(
|
||||||
|
ctx, ctx.Org.Organization.ID, 0, secretName, opt.Data,
|
||||||
|
)
|
||||||
|
if secret_model.IsErrSecretNotFound(err) {
|
||||||
|
ctx.NotFound(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateSecret", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOrgSecret delete one secret of the organization
|
||||||
|
func DeleteOrgSecret(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
|
||||||
|
// ---
|
||||||
|
// summary: Delete a secret in an organization
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: secretname
|
||||||
|
// in: path
|
||||||
|
// description: name of the secret
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: delete one secret of the organization
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
secretName := ctx.Params(":secretname")
|
||||||
|
err := secret_model.DeleteSecret(
|
||||||
|
ctx, ctx.Org.Organization.ID, 0, secretName,
|
||||||
|
)
|
||||||
|
if secret_model.IsErrSecretNotFound(err) {
|
||||||
|
ctx.NotFound(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
|
@ -190,4 +190,7 @@ type swaggerParameterBodies struct {
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateSecretOption api.CreateSecretOption
|
CreateSecretOption api.CreateSecretOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
UpdateSecretOption api.UpdateSecretOption
|
||||||
}
|
}
|
||||||
|
|
|
@ -1631,6 +1631,89 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/actions/secrets/{secretname}": {
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Update a secret value in an organization",
|
||||||
|
"operationId": "updateOrgSecret",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the secret",
|
||||||
|
"name": "secretname",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateSecretOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "update one secret of the organization"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Delete a secret in an organization",
|
||||||
|
"operationId": "deleteOrgSecret",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the secret",
|
||||||
|
"name": "secretname",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "delete one secret of the organization"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/activities/feeds": {
|
"/orgs/{org}/activities/feeds": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -21891,6 +21974,21 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"UpdateSecretOption": {
|
||||||
|
"description": "UpdateSecretOption options when updating secret",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"description": "Data of the secret to update",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"UpdateUserAvatarOption": {
|
"UpdateUserAvatarOption": {
|
||||||
"description": "UpdateUserAvatarUserOption options when updating the user avatar",
|
"description": "UpdateUserAvatarUserOption options when updating the user avatar",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -23207,7 +23305,7 @@
|
||||||
"parameterBodies": {
|
"parameterBodies": {
|
||||||
"description": "parameterBodies",
|
"description": "parameterBodies",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/CreateSecretOption"
|
"$ref": "#/definitions/UpdateSecretOption"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
|
|
Loading…
Reference in New Issue