Skip to content

Commit

Permalink
feat(API): update and delete secret for managing organization secrets (
Browse files Browse the repository at this point in the history
…#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>
  • Loading branch information
appleboy authored Aug 24, 2023
1 parent 7e30986 commit b62c8e7
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 1 deletion.
67 changes: 67 additions & 0 deletions models/secret/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ package secret
import (
"context"
"errors"
"fmt"
"strings"

"code.gitea.io/gitea/models/db"
secret_module "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)
Expand All @@ -26,6 +28,25 @@ type Secret struct {
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
func newSecret(ownerID, repoID int64, name, data string) *Secret {
return &Secret{
Expand Down Expand Up @@ -93,3 +114,49 @@ func FindSecrets(ctx context.Context, opts FindSecretsOptions) ([]*Secret, error
func CountSecrets(ctx context.Context, opts *FindSecretsOptions) (int64, error) {
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
}
9 changes: 9 additions & 0 deletions modules/structs/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ type CreateSecretOption struct {
// Data of the secret to create
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"`
}
3 changes: 3 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,9 @@ func Routes() *web.Route {
m.Group("/actions/secrets", func() {
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
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.Get("", org.ListPublicMembers)
Expand Down
91 changes: 91 additions & 0 deletions routers/api/v1/org/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func CreateOrgSecret(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
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(
ctx, ctx.Org.Organization.ID, 0, opt.Name, actions.ReserveLineBreakForTextarea(opt.Data),
)
Expand All @@ -113,3 +117,90 @@ func CreateOrgSecret(ctx *context.APIContext) {

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)
}
3 changes: 3 additions & 0 deletions routers/api/v1/swagger/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,7 @@ type swaggerParameterBodies struct {

// in:body
CreateSecretOption api.CreateSecretOption

// in:body
UpdateSecretOption api.UpdateSecretOption
}
100 changes: 99 additions & 1 deletion templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b62c8e7

Please sign in to comment.