aboutsummaryrefslogtreecommitdiffstats
path: root/routers/web/shared/actions/variables.go
blob: 8d1516c91ceb3f99bb7aad60e1a0ea92a74c0a2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
	"errors"
	"regexp"
	"strings"

	actions_model "code.gitea.io/gitea/models/actions"
	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/forms"
)

func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
	variables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{
		OwnerID: ownerID,
		RepoID:  repoID,
	})
	if err != nil {
		ctx.ServerError("FindVariables", err)
		return
	}
	ctx.Data["Variables"] = variables
}

// some regular expression of `variables` and `secrets`
// reference to:
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
var (
	nameRx            = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
	forbiddenPrefixRx = regexp.MustCompile("(?i)^GIT(EA|HUB)_")

	forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
)

func NameRegexMatch(name string) error {
	if !nameRx.MatchString(name) || forbiddenPrefixRx.MatchString(name) {
		log.Error("Name %s, regex match error", name)
		return errors.New("name has invalid character")
	}
	return nil
}

func envNameCIRegexMatch(name string) error {
	if forbiddenEnvNameCIRx.MatchString(name) {
		log.Error("Env Name cannot be ci")
		return errors.New("env name cannot be ci")
	}
	return nil
}

func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
	form := web.GetForm(ctx).(*forms.EditVariableForm)

	if err := NameRegexMatch(form.Name); err != nil {
		ctx.JSONError(err.Error())
		return
	}

	if err := envNameCIRegexMatch(form.Name); err != nil {
		ctx.JSONError(err.Error())
		return
	}

	v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data))
	if err != nil {
		log.Error("InsertVariable error: %v", err)
		ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
		return
	}
	ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
	ctx.JSONRedirect(redirectURL)
}

func UpdateVariable(ctx *context.Context, redirectURL string) {
	id := ctx.ParamsInt64(":variable_id")
	form := web.GetForm(ctx).(*forms.EditVariableForm)

	if err := NameRegexMatch(form.Name); err != nil {
		ctx.JSONError(err.Error())
		return
	}

	if err := envNameCIRegexMatch(form.Name); err != nil {
		ctx.JSONError(err.Error())
		return
	}

	ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{
		ID:   id,
		Name: strings.ToUpper(form.Name),
		Data: ReserveLineBreakForTextarea(form.Data),
	})
	if err != nil || !ok {
		log.Error("UpdateVariable error: %v", err)
		ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
		return
	}
	ctx.Flash.Success(ctx.Tr("actions.variables.update.success"))
	ctx.JSONRedirect(redirectURL)
}

func DeleteVariable(ctx *context.Context, redirectURL string) {
	id := ctx.ParamsInt64(":variable_id")

	if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil {
		log.Error("Delete variable [%d] failed: %v", id, err)
		ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
		return
	}
	ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
	ctx.JSONRedirect(redirectURL)
}

func ReserveLineBreakForTextarea(input string) string {
	// Since the content is from a form which is a textarea, the line endings are \r\n.
	// It's a standard behavior of HTML.
	// But we want to store them as \n like what GitHub does.
	// And users are unlikely to really need to keep the \r.
	// Other than this, we should respect the original content, even leading or trailing spaces.
	return strings.ReplaceAll(input, "\r\n", "\n")
}