Files
OliveTin/internal/executor/arguments.go
James Read de81ec00fd feature: mix of bits (#439)
* feature: mix of bits

* bugfix: codestyle
2024-10-15 07:47:51 +00:00

215 lines
4.9 KiB
Go

package executor
import (
config "github.com/OliveTin/OliveTin/internal/config"
sv "github.com/OliveTin/OliveTin/internal/stringvariables"
log "github.com/sirupsen/logrus"
"errors"
"net/mail"
"net/url"
"regexp"
"strings"
"time"
)
var (
typecheckRegex = map[string]string{
"very_dangerous_raw_string": "",
"int": "^[\\d]+$",
"unicode_identifier": "^[\\w\\/\\\\.\\_ \\d]+$",
"ascii": "^[a-zA-Z0-9]+$",
"ascii_identifier": "^[a-zA-Z0-9\\-\\.\\_]+$",
"ascii_sentence": "^[a-zA-Z0-9 \\,\\.]+$",
}
)
func parseCommandForReplacements(rawShellCommand string, values map[string]string) (string, map[string]string, error) {
r := regexp.MustCompile("{{ *?([a-zA-Z0-9_]+?) *?}}")
foundArgumentNames := r.FindAllStringSubmatch(rawShellCommand, -1)
usedArguments := make(map[string]string)
for _, match := range foundArgumentNames {
argName := match[1]
argValue, argProvided := values[argName]
if !argProvided {
return "", nil, errors.New("Required arg not provided: " + argName)
}
usedArguments[argName] = argValue
rawShellCommand = strings.ReplaceAll(rawShellCommand, match[0], argValue)
}
return rawShellCommand, usedArguments, nil
}
func parseActionArguments(rawShellCommand string, values map[string]string, action *config.Action, actionTitle string, entityPrefix string) (string, error) {
log.WithFields(log.Fields{
"actionTitle": actionTitle,
"cmd": rawShellCommand,
}).Infof("Action parse args - Before")
rawShellCommand, usedArgs, err := parseCommandForReplacements(rawShellCommand, values)
if err != nil {
return "", err
}
for argName, argValue := range usedArgs {
err := typecheckActionArgument(argName, argValue, action)
if err != nil {
return "", err
}
log.WithFields(log.Fields{
"name": argName,
"value": argValue,
}).Debugf("Arg assigned")
}
rawShellCommand = sv.ReplaceEntityVars(entityPrefix, rawShellCommand)
log.WithFields(log.Fields{
"actionTitle": actionTitle,
"cmd": rawShellCommand,
}).Infof("Action parse args - After")
return rawShellCommand, nil
}
func typecheckActionArgument(name string, value string, action *config.Action) error {
arg := action.FindArg(name)
if arg == nil {
return errors.New("Action arg not defined: " + name)
}
if value == "" {
return typecheckNull(arg)
}
if len(arg.Choices) > 0 {
return typecheckChoice(value, arg)
}
return TypeSafetyCheck(name, value, arg.Type)
}
func typecheckNull(arg *config.ActionArgument) error {
if arg.RejectNull {
return errors.New("Null values are not allowed")
}
return nil
}
func typecheckChoice(value string, arg *config.ActionArgument) error {
if arg.Entity != "" {
return typecheckChoiceEntity(value, arg)
}
for _, choice := range arg.Choices {
if value == choice.Value {
return nil
}
}
return errors.New("argument value is not one of the predefined choices")
}
func typecheckChoiceEntity(value string, arg *config.ActionArgument) error {
templateChoice := arg.Choices[0].Value
for _, ent := range sv.GetEntities(arg.Entity) {
choice := sv.ReplaceEntityVars(ent, templateChoice)
if value == choice {
return nil
}
}
return errors.New("argument value cannot be found in entities")
}
// TypeSafetyCheck checks argument values match a specific type. The types are
// defined in typecheckRegex, and, you guessed it, uses regex to check for allowed
// characters.
//
//gocyclo:ignore
func TypeSafetyCheck(name string, value string, argumentType string) error {
switch argumentType {
case "password":
return nil
case "email":
return typeSafetyCheckEmail(name, value)
case "url":
return typeSafetyCheckUrl(name, value)
case "datetime":
return typeSafetyCheckDatetime(name, value)
}
return typeSafetyCheckRegex(name, value, argumentType)
}
func typeSafetyCheckEmail(name string, value string) error {
_, err := mail.ParseAddress(value)
log.Errorf("Email check: %v, %v", err, value)
if err != nil {
return err
}
return nil
}
func typeSafetyCheckDatetime(name string, value string) error {
_, err := time.Parse("2006-01-02T15:04:05", value)
if err != nil {
return err
}
return nil
}
func typeSafetyCheckRegex(name string, value string, argumentType string) error {
pattern := ""
if strings.HasPrefix(argumentType, "regex:") {
pattern = strings.Replace(argumentType, "regex:", "", 1)
} else {
found := false
pattern, found = typecheckRegex[argumentType]
if !found {
return errors.New("argument type not implemented " + argumentType)
}
}
matches, _ := regexp.MatchString(pattern, value)
if !matches {
log.WithFields(log.Fields{
"name": name,
"value": value,
"type": argumentType,
"pattern": pattern,
}).Warn("Arg type check safety failure")
return errors.New("invalid argument, doesn't match " + argumentType)
}
return nil
}
func typeSafetyCheckUrl(name string, value string) error {
_, err := url.ParseRequestURI(value)
return err
}