-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #152 from choria-io/151
(#151) Support experimental flag and argument validation
- Loading branch information
Showing
8 changed files
with
289 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package validator | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strings" | ||
|
||
"github.com/antonmedv/expr" | ||
"github.com/choria-io/fisk" | ||
) | ||
|
||
// FiskValidator is a fisk.OptionValidator that compatible with Validator() on arguments and flags | ||
func FiskValidator(validation string) fisk.OptionValidator { | ||
return func(value string) error { | ||
ok, err := Validate(value, validation) | ||
if err != nil { | ||
return fmt.Errorf("validation using %q failed: %w", validation, err) | ||
} | ||
|
||
if !ok { | ||
return fmt.Errorf("validation using %q did not pass", validation) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// Validate validates value using the expr expression validation | ||
func Validate(value any, validation string) (bool, error) { | ||
var env any | ||
|
||
vs, ok := value.(string) | ||
if ok { | ||
env = map[string]any{ | ||
"value": vs, | ||
"Value": vs, | ||
} | ||
} else { | ||
env = value | ||
} | ||
|
||
program, err := expr.Compile(validation, expr.Env(env), expr.AsBool(), | ||
ShellSafeValidator(), | ||
IPv4Validator(), | ||
IPv6Validator(), | ||
IPvValidator(), | ||
) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
output, err := expr.Run(program, env) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return output.(bool), nil | ||
} | ||
|
||
func IPvValidator() expr.Option { | ||
return expr.Function( | ||
"is_ip", | ||
func(params ...any) (any, error) { | ||
val := params[0].(string) | ||
ip := net.ParseIP(val) | ||
|
||
if ip == nil { | ||
return false, fmt.Errorf("%s is not an IP address", val) | ||
} | ||
|
||
return true, nil | ||
}, | ||
new(func(string) (bool, error))) | ||
} | ||
|
||
func IPv4Validator() expr.Option { | ||
return expr.Function( | ||
"is_ipv4", | ||
func(params ...any) (any, error) { | ||
val := params[0].(string) | ||
ip := net.ParseIP(val).To4() | ||
|
||
if ip == nil { | ||
return false, fmt.Errorf("%s is not an IPv4 address", val) | ||
} | ||
|
||
return true, nil | ||
}, | ||
new(func(string) (bool, error))) | ||
} | ||
|
||
func IPv6Validator() expr.Option { | ||
return expr.Function( | ||
"is_ipv6", | ||
func(params ...any) (any, error) { | ||
val := params[0].(string) | ||
ip := net.ParseIP(val) | ||
|
||
if ip == nil { | ||
return false, fmt.Errorf("%s is not an IPv6 address", val) | ||
} | ||
|
||
if ip.To4() != nil { | ||
return false, fmt.Errorf("%s is not an IPv6 address", val) | ||
} | ||
|
||
return true, nil | ||
}, | ||
new(func(string) (bool, error))) | ||
} | ||
|
||
func ShellSafeValidator() expr.Option { | ||
return expr.Function( | ||
"is_shellsafe", | ||
func(params ...any) (any, error) { | ||
val := strings.TrimSpace(params[0].(string)) | ||
badchars := []string{"`", "$", ";", "|", "&&", ">", "<"} | ||
|
||
for _, c := range badchars { | ||
if strings.Contains(val, c) { | ||
return false, fmt.Errorf("may not contain '%s'", c) | ||
} | ||
} | ||
|
||
return true, nil | ||
}, | ||
new(func(string) (bool, error))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) 2023, R.I. Pienaar and the Choria Project contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package validator | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestBuilder(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "Validator") | ||
} | ||
|
||
var _ = Describe("Validator", func() { | ||
Describe("is_ip", func() { | ||
It("Should validate correctly", func() { | ||
ok, err := Validate("1.1.1.1", "is_ip(value)") | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(ok).To(BeTrue()) | ||
|
||
ok, err = Validate("2a00:1450:4002:405::20", "is_ip(value)") | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(ok).To(BeTrue()) | ||
|
||
ok, err = Validate("bob", "is_ip(value)") | ||
Expect(err.Error()).To(ContainSubstring("bob is not an IP address")) | ||
Expect(ok).To(BeFalse()) | ||
}) | ||
}) | ||
|
||
Describe("is_ipv4", func() { | ||
It("Should validate correctly", func() { | ||
ok, err := Validate("1.1.1.1", "is_ipv4(value)") | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(ok).To(BeTrue()) | ||
|
||
ok, err = Validate("2a00:1450:4002:405::20", "is_ipv4(value)") | ||
Expect(err.Error()).To(ContainSubstring("2a00:1450:4002:405::20 is not an IPv4 address")) | ||
Expect(ok).To(BeFalse()) | ||
}) | ||
}) | ||
|
||
Describe("is_ipv6", func() { | ||
It("Should validate correctly", func() { | ||
ok, err := Validate("2a00:1450:4002:405::20", "is_ipv6(value)") | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(ok).To(BeTrue()) | ||
|
||
ok, err = Validate("1.1.1.1", "is_ipv6(value)") | ||
Expect(err).To(HaveOccurred()) | ||
Expect(err.Error()).To(ContainSubstring("1.1.1.1 is not an IPv6 address")) | ||
Expect(ok).To(BeFalse()) | ||
}) | ||
}) | ||
|
||
Describe("shellsafe", func() { | ||
It("Should match bad strings", func() { | ||
badchars := []string{"`", "$", ";", "|", "&&", ">", "<"} | ||
|
||
for _, c := range badchars { | ||
ok, err := Validate(fmt.Sprintf("thing%sthing", c), "is_shellsafe(value)") | ||
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("may not contain '%s'", c))) | ||
Expect(ok).To(BeFalse()) | ||
} | ||
}) | ||
|
||
It("Should allow good things", func() { | ||
Expect(Validate("ok", "is_shellsafe(value)")).To(BeTrue()) | ||
Expect(Validate("", "is_shellsafe(value)")).To(BeTrue()) | ||
Expect(Validate("ok ok ok", "is_shellsafe(value)")).To(BeTrue()) | ||
}) | ||
}) | ||
}) |