f77bcc53c4
* This package is new for 1.5 so this is not a breaking change. * This is being moved because this code was originally intended to be used within plugins, however the design of password policies has changed such that this is no longer needed. Thus, this code doesn't need to be in the public SDK.
579 lines
12 KiB
Go
579 lines
12 KiB
Go
package random
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestParsePolicy(t *testing.T) {
|
|
type testCase struct {
|
|
rawConfig string
|
|
expected StringGenerator
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"unrecognized rule": {
|
|
rawConfig: `
|
|
length = 20
|
|
rule "testrule" {
|
|
string = "teststring"
|
|
int = 123
|
|
}`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
|
|
"charset restrictions": {
|
|
rawConfig: `
|
|
length = 20
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
min-chars = 2
|
|
}`,
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: []rune("abcde"),
|
|
Rules: []Rule{
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
actual, err := ParsePolicy(test.rawConfig)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, test.expected) {
|
|
t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParser_ParsePolicy(t *testing.T) {
|
|
type testCase struct {
|
|
registry map[string]ruleConstructor
|
|
|
|
rawConfig string
|
|
expected StringGenerator
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"empty config": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: "",
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
"bogus config": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: "asdf",
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
"config with only length": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
length = 20`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
"config with zero length": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
length = 0
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
}`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
"config with negative length": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
length = -2
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
}`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
"charset restrictions": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
length = 20
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
min-chars = 2
|
|
}`,
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: []rune("abcde"),
|
|
Rules: []Rule{
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
"test rule": {
|
|
registry: map[string]ruleConstructor{
|
|
"testrule": newTestRule,
|
|
},
|
|
rawConfig: `
|
|
length = 20
|
|
rule "testrule" {
|
|
string = "teststring"
|
|
int = 123
|
|
}`,
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: deduplicateRunes([]rune("teststring")),
|
|
Rules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
"test rule and charset restrictions": {
|
|
registry: map[string]ruleConstructor{
|
|
"testrule": newTestRule,
|
|
"charset": ParseCharset,
|
|
},
|
|
rawConfig: `
|
|
length = 20
|
|
rule "testrule" {
|
|
string = "teststring"
|
|
int = 123
|
|
}
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
min-chars = 2
|
|
}`,
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: deduplicateRunes([]rune("abcdeteststring")),
|
|
Rules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
"unrecognized rule": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
length = 20
|
|
rule "testrule" {
|
|
string = "teststring"
|
|
int = 123
|
|
}`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
|
|
// /////////////////////////////////////////////////
|
|
// JSON data
|
|
"manually JSONified HCL": {
|
|
registry: map[string]ruleConstructor{
|
|
"testrule": newTestRule,
|
|
"charset": ParseCharset,
|
|
},
|
|
rawConfig: `
|
|
{
|
|
"charset": "abcde",
|
|
"length": 20,
|
|
"rule": [
|
|
{
|
|
"testrule": [
|
|
{
|
|
"string": "teststring",
|
|
"int": 123
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"charset": [
|
|
{
|
|
"charset": "abcde",
|
|
"min-chars": 2
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`,
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: deduplicateRunes([]rune("abcdeteststring")),
|
|
Rules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
"JSONified HCL": {
|
|
registry: map[string]ruleConstructor{
|
|
"testrule": newTestRule,
|
|
"charset": ParseCharset,
|
|
},
|
|
rawConfig: toJSON(t, StringGenerator{
|
|
Length: 20,
|
|
Rules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
}),
|
|
expected: StringGenerator{
|
|
Length: 20,
|
|
charset: deduplicateRunes([]rune("abcdeteststring")),
|
|
Rules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
CharsetRule{
|
|
Charset: []rune("abcde"),
|
|
MinChars: 2,
|
|
},
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
"JSON unrecognized rule": {
|
|
registry: defaultRuleNameMapping,
|
|
rawConfig: `
|
|
{
|
|
"charset": "abcde",
|
|
"length": 20,
|
|
"rule": [
|
|
{
|
|
"testrule": [
|
|
{
|
|
"string": "teststring",
|
|
"int": 123
|
|
}
|
|
],
|
|
}
|
|
]
|
|
}`,
|
|
expected: StringGenerator{},
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
parser := PolicyParser{
|
|
RuleRegistry: Registry{
|
|
Rules: test.registry,
|
|
},
|
|
}
|
|
|
|
actual, err := parser.ParsePolicy(test.rawConfig)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, test.expected) {
|
|
t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseRules(t *testing.T) {
|
|
type testCase struct {
|
|
registry map[string]ruleConstructor
|
|
|
|
rawRules []map[string]interface{}
|
|
expectedRules []Rule
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"nil rule data": {
|
|
registry: defaultRuleNameMapping,
|
|
rawRules: nil,
|
|
expectedRules: nil,
|
|
expectErr: false,
|
|
},
|
|
"empty rule data": {
|
|
registry: defaultRuleNameMapping,
|
|
rawRules: []map[string]interface{}{},
|
|
expectedRules: nil,
|
|
expectErr: false,
|
|
},
|
|
"invalid rule data": {
|
|
registry: defaultRuleNameMapping,
|
|
rawRules: []map[string]interface{}{
|
|
{
|
|
"testrule": map[string]interface{}{
|
|
"string": "teststring",
|
|
},
|
|
},
|
|
},
|
|
expectedRules: nil,
|
|
expectErr: true,
|
|
},
|
|
"unrecognized rule data": {
|
|
registry: defaultRuleNameMapping,
|
|
rawRules: []map[string]interface{}{
|
|
{
|
|
"testrule": []map[string]interface{}{
|
|
{
|
|
"string": "teststring",
|
|
"int": 123,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedRules: nil,
|
|
expectErr: true,
|
|
},
|
|
"recognized rule": {
|
|
registry: map[string]ruleConstructor{
|
|
"testrule": newTestRule,
|
|
},
|
|
rawRules: []map[string]interface{}{
|
|
{
|
|
"testrule": []map[string]interface{}{
|
|
{
|
|
"string": "teststring",
|
|
"int": 123,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedRules: []Rule{
|
|
testCharsetRule{
|
|
String: "teststring",
|
|
Integer: 123,
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
registry := Registry{
|
|
Rules: test.registry,
|
|
}
|
|
|
|
actualRules, err := parseRules(registry, test.rawRules)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actualRules, test.expectedRules) {
|
|
t.Fatalf("Actual: %#v\nExpected:%#v", actualRules, test.expectedRules)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMapSlice(t *testing.T) {
|
|
type testCase struct {
|
|
input map[string]interface{}
|
|
key string
|
|
expectedSlice []map[string]interface{}
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"nil map": {
|
|
input: nil,
|
|
key: "testkey",
|
|
expectedSlice: nil,
|
|
expectErr: false,
|
|
},
|
|
"empty map": {
|
|
input: map[string]interface{}{},
|
|
key: "testkey",
|
|
expectedSlice: nil,
|
|
expectErr: false,
|
|
},
|
|
"ignored keys": {
|
|
input: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
key: "testkey",
|
|
expectedSlice: nil,
|
|
expectErr: false,
|
|
},
|
|
"key has wrong type": {
|
|
input: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
key: "foo",
|
|
expectedSlice: nil,
|
|
expectErr: true,
|
|
},
|
|
"good data": {
|
|
input: map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
{
|
|
"sub-foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
key: "foo",
|
|
expectedSlice: []map[string]interface{}{
|
|
{
|
|
"sub-foo": "bar",
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
actualSlice, err := getMapSlice(test.input, test.key)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actualSlice, test.expectedSlice) {
|
|
t.Fatalf("Actual: %#v\nExpected:%#v", actualSlice, test.expectedSlice)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRuleInfo(t *testing.T) {
|
|
type testCase struct {
|
|
rule map[string]interface{}
|
|
expectedInfo ruleInfo
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"nil rule": {
|
|
rule: nil,
|
|
expectedInfo: ruleInfo{},
|
|
expectErr: true,
|
|
},
|
|
"empty rule": {
|
|
rule: map[string]interface{}{},
|
|
expectedInfo: ruleInfo{},
|
|
expectErr: true,
|
|
},
|
|
"rule with invalid type": {
|
|
rule: map[string]interface{}{
|
|
"TestRuleType": "wrong type",
|
|
},
|
|
expectedInfo: ruleInfo{},
|
|
expectErr: true,
|
|
},
|
|
"rule with good data": {
|
|
rule: map[string]interface{}{
|
|
"TestRuleType": []map[string]interface{}{
|
|
{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
},
|
|
expectedInfo: ruleInfo{
|
|
ruleType: "TestRuleType",
|
|
data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
actualInfo, err := getRuleInfo(test.rule)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actualInfo, test.expectedInfo) {
|
|
t.Fatalf("Actual: %#v\nExpected:%#v", actualInfo, test.expectedInfo)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkParser_Parse(b *testing.B) {
|
|
config := `length = 20
|
|
rule "charset" {
|
|
charset = "abcde"
|
|
min-chars = 2
|
|
}`
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
parser := PolicyParser{
|
|
RuleRegistry: Registry{
|
|
Rules: defaultRuleNameMapping,
|
|
},
|
|
}
|
|
_, err := parser.ParsePolicy(config)
|
|
if err != nil {
|
|
b.Fatalf("Failed to parse: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func toJSON(t *testing.T, val interface{}) string {
|
|
t.Helper()
|
|
b, err := json.Marshal(val)
|
|
if err != nil {
|
|
t.Fatalf("unable to marshal to JSON: %s", err)
|
|
}
|
|
return string(b)
|
|
}
|