msteinert-go-pam/cmd/pam-moduler/tests/integration-tester-module/integration-tester-module_t...

1281 lines
34 KiB
Go

package main
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
"git.cloudyne.io/go/msteinert-go-pam/cmd/pam-moduler/tests/internal/utils"
)
func (r *Request) check(res *Result, expectedResults []interface{}) error {
switch res.Action {
case "return":
case "error":
return fmt.Errorf("module error: %v", res.ActionArgs...)
default:
return fmt.Errorf("unexpected action %v", res.Action)
}
if !reflect.DeepEqual(res.ActionArgs, expectedResults) {
return fmt.Errorf("unexpected return values %#v vs %#v",
res.ActionArgs, expectedResults)
}
return nil
}
func (r *Request) checkRemote(listener *Listener, expectedResults []interface{}) error {
res, err := listener.DoRequest(r)
if err != nil {
return err
}
return res.check(res, expectedResults)
}
type checkedRequest struct {
r Request
exp []interface{}
compareWithTestState bool
}
func (cr *checkedRequest) checkRemote(listener *Listener) error {
return cr.r.checkRemote(listener, cr.exp)
}
func (cr *checkedRequest) check(res *Result) error {
return cr.r.check(res, cr.exp)
}
func ensureUser(tx *pam.Transaction, expected string) error {
item := pam.User
if value, err := tx.GetItem(item); err != nil {
return err
} else if value != expected {
return fmt.Errorf("invalid item %v value: %s vs %v", item, value, expected)
}
return nil
}
func ensureEnv(tx *pam.Transaction, variable string, expected string) error {
if env := tx.GetEnv(variable); env != expected {
return fmt.Errorf("unexpected env %s value: %s vs %s", variable, env, expected)
}
return nil
}
func (r *Request) toBytes(t *testing.T) []byte {
t.Helper()
bytes, err := r.GOB()
if err != nil {
t.Fatalf("error: %v", err)
return nil
}
return bytes
}
func (r *Request) toTransactionData(t *testing.T) []byte {
t.Helper()
return utils.TestBinaryDataEncoder(r.toBytes(t))
}
func Test_Moduler_IntegrationTesterModule(t *testing.T) {
t.Parallel()
if !pam.CheckPamHasStartConfdir() {
t.Skip("this requires PAM with Conf dir support")
}
ts := utils.NewTestSetup(t, utils.WithWorkDir())
modulePath := ts.GenerateModuleDefault(ts.GetCurrentFileDir())
type testState = map[string]interface{}
tests := map[string]struct {
expectedError error
user string
credentials pam.ConversationHandler
checkedRequests []checkedRequest
setup func(*pam.Transaction, *Listener, testState) error
finish func(*pam.Transaction, *Listener, testState) error
}{
"success": {
expectedError: nil,
},
"get-item-Service": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.Service),
exp: []interface{}{"get-item-service", nil},
}},
},
"get-item-User-empty": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"", nil},
}},
},
"get-item-User-preset": {
user: "test-user",
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"test-user", nil},
}},
},
"get-item-Authtok-empty": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.Authtok),
exp: []interface{}{"", nil},
}},
},
"get-item-Oldauthtok-empty": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.Oldauthtok),
exp: []interface{}{"", nil},
}},
},
"get-item-UserPrompt-empty": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.UserPrompt),
exp: []interface{}{"", nil},
}},
},
"set-item-Service": {
checkedRequests: []checkedRequest{
{
r: NewRequest("SetItem", pam.Service, "foo-service"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetItem", pam.Service),
exp: []interface{}{"foo-service", nil},
},
},
},
"set-item-User-empty": {
checkedRequests: []checkedRequest{
{
r: NewRequest("SetItem", pam.User, "an-user"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"an-user", nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "an-user")
},
},
"set-item-User-preset": {
user: "test-user",
checkedRequests: []checkedRequest{
{
r: NewRequest("SetItem", pam.User, "an-user"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"an-user", nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "an-user")
},
},
"set-get-item-User-empty": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.SetItem(pam.User, "setup-user")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"setup-user", nil},
}},
},
"set-get-item-User-preset": {
user: "test-user",
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.SetItem(pam.User, "setup-user")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"setup-user", nil},
}},
},
"get-env-unset": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnv", "_PAM_GO_HOPEFULLY_NOT_SET"),
exp: []interface{}{""},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_HOPEFULLY_NOT_SET", "")
},
},
"get-env-preset": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.PutEnv("_PAM_GO_ENV_SET_VAR=foobar")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{"foobar"},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "foobar")
},
},
"get-env-preset-empty": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
if err := tx.PutEnv("_PAM_GO_ENV_SET_VAR=value"); err != nil {
return err
}
return tx.PutEnv("_PAM_GO_ENV_SET_VAR=")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "")
},
},
"get-env-preset-unset": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
if err := tx.PutEnv("_PAM_GO_ENV_SET_VAR=value"); err != nil {
return err
}
return tx.PutEnv("_PAM_GO_ENV_SET_VAR")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "")
},
},
"put-env-not-preset": {
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR=a value"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{"a value"},
},
},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "a value")
},
},
"put-env-preset": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.PutEnv("_PAM_GO_ENV_SET_VAR=foobar")
},
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR=another value"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{"another value"},
},
},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "another value")
},
},
"put-env-resets-not-preset": {
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR=a value"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{"a value"},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR="),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
},
},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "")
},
},
"put-env-resets-preset": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.PutEnv("_PAM_GO_ENV_SET_VAR=foobar")
},
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR=a value"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{"a value"},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR="),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{""},
},
},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureEnv(tx, "_PAM_GO_ENV_SET_VAR", "")
},
},
"put-env-unsets-not-set": {
expectedError: pam.ErrBadItem,
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR_NEVER_SET"),
exp: []interface{}{pam.ErrBadItem},
},
},
},
"put-env-unsets-empty-value": {
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR="),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnvList"),
exp: []interface{}{
map[string]string{"_PAM_GO_ENV_SET_VAR": ""}, nil,
},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnvList"),
exp: []interface{}{map[string]string{}, nil},
},
},
},
"put-env-invalid-syntax": {
expectedError: pam.ErrBadItem,
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "="),
exp: []interface{}{pam.ErrBadItem},
},
{
r: NewRequest("PutEnv", "=bar"),
exp: []interface{}{pam.ErrBadItem},
},
{
r: NewRequest("PutEnv", "with spaces"),
exp: []interface{}{pam.ErrBadItem},
},
},
},
"get-env-list-empty": {
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnvList"),
exp: []interface{}{map[string]string{}, nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return nil
},
},
"get-env-list-preset": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
expected := map[string]string{
"_PAM_GO_ENV_SET_VAR1": "value1",
"_PAM_GO_ENV_SET_VAR2": "value due",
"_PAM_GO_ENV_SET_VAR3": "3",
"_PAM_GO_ENV_SET_VAR_EMPTY": "",
"_PAM_GO_ENV WITH SPACES": "yes works",
}
for env, value := range expected {
if err := tx.PutEnv(fmt.Sprintf("%s=%s", env, value)); err != nil {
return err
}
}
ts["expected"] = expected
ts["expectedResults"] = [][]interface{}{{expected, nil}}
return nil
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetEnvList"),
compareWithTestState: true,
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
if list, err := tx.GetEnvList(); err != nil {
return err
} else if !reflect.DeepEqual(list, ts["expected"]) {
return fmt.Errorf("Unexpected return values %#v vs %#v",
list, ts["expected"])
}
return nil
},
},
"get-env-list-module-set": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
expected := map[string]string{
"_PAM_GO_ENV_SET_VAR1": "value1",
"_PAM_GO_ENV_SET_VAR2": "value due",
"_PAM_GO_ENV_SET_VAR3": "3",
"_PAM_GO_ENV_SET_VAR_EMPTY": "",
"_PAM_GO_ENV WITH SPACES": "yes works",
}
ts["expected"] = expected
ts["expectedResults"] = [][]interface{}{
nil, nil, nil, nil, nil, nil, nil, {expected, nil},
}
return nil
},
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR1=value1"),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR2=value due"),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR3=3"),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR_EMPTY="),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR_TO_UNSET=unset"),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV_SET_VAR_TO_UNSET"),
exp: []interface{}{nil},
},
{
r: NewRequest("PutEnv", "_PAM_GO_ENV WITH SPACES=yes works"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetEnvList"),
compareWithTestState: true,
},
},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
if list, err := tx.GetEnvList(); err != nil {
return err
} else if !reflect.DeepEqual(list, ts["expected"]) {
return fmt.Errorf("unexpected return values %#v vs %#v",
list, ts["expected"])
}
return nil
},
},
"get-user-empty-no-conv-set": {
expectedError: pam.ErrConv,
checkedRequests: []checkedRequest{{
r: NewRequest("GetUser", "who are you? "),
exp: []interface{}{"", pam.ErrConv},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "")
},
},
"get-user-empty-with-conv": {
credentials: utils.Credentials{
User: "replying-user",
ExpectedMessage: "who are you? ",
ExpectedStyle: pam.PromptEchoOn,
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetUser", "who are you? "),
exp: []interface{}{"replying-user", nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "replying-user")
},
},
"get-user-preset-without-conv": {
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.SetItem(pam.User, "setup-user")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetUser", "who are you? "),
exp: []interface{}{"setup-user", nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "setup-user")
},
},
"get-user-preset-with-conv": {
credentials: utils.Credentials{
User: "replying-user",
ExpectedMessage: "No message should have been shown!",
ExpectedStyle: pam.PromptEchoOn,
},
setup: func(tx *pam.Transaction, l *Listener, ts testState) error {
return tx.SetItem(pam.User, "setup-user")
},
checkedRequests: []checkedRequest{{
r: NewRequest("GetUser", "who are you? "),
exp: []interface{}{"setup-user", nil},
}},
finish: func(tx *pam.Transaction, l *Listener, ts testState) error {
return ensureUser(tx, "setup-user")
},
},
"get-data-not-available": {
expectedError: pam.ErrNoModuleData,
checkedRequests: []checkedRequest{{
r: NewRequest("GetData", "some-data"),
exp: []interface{}{nil, pam.ErrNoModuleData},
}},
},
"set-data-empty-nil": {
expectedError: pam.ErrNoModuleData,
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "", nil),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", ""),
exp: []interface{}{nil, pam.ErrNoModuleData},
},
},
},
"set-data-empty-to-value": {
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "", []string{"hello", "world"}),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", ""),
exp: []interface{}{[]string{"hello", "world"}, nil},
},
},
},
"set-data-to-value": {
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "some-error-data",
utils.SerializableError{Msg: "An error"}),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", "some-error-data"),
exp: []interface{}{utils.SerializableError{Msg: "An error"}, nil},
},
},
},
"set-data-to-value-replacing": {
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "some-data",
utils.SerializableError{Msg: "An error"}),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", "some-data"),
exp: []interface{}{utils.SerializableError{Msg: "An error"}, nil},
},
{
r: NewRequest("SetData", "some-data", "Hello"),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", "some-data"),
exp: []interface{}{"Hello", nil},
},
},
},
"set-data-to-value-unset": {
expectedError: pam.ErrNoModuleData,
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "some-data",
utils.SerializableError{Msg: "An error"}),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", "some-data"),
exp: []interface{}{utils.SerializableError{Msg: "An error"}, nil},
},
{
r: NewRequest("SetData", "some-data", nil),
exp: []interface{}{nil},
},
{
r: NewRequest("GetData", "some-data"),
exp: []interface{}{nil, pam.ErrNoModuleData},
},
},
},
"start-conv-no-conv-set": {
expectedError: pam.ErrConv,
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.TextInfo,
"hello PAM!",
}),
exp: []interface{}{nil, pam.ErrConv},
},
{
r: NewRequest("StartStringConv", pam.TextInfo, "hello PAM!"),
exp: []interface{}{nil, pam.ErrConv},
},
},
},
"start-conv-prompt-text-info": {
credentials: utils.Credentials{
ExpectedMessage: "hello PAM!",
ExpectedStyle: pam.TextInfo,
TextInfo: "nice to see you, Go!",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.TextInfo,
"hello PAM!",
}),
exp: []interface{}{SerializableStringConvResponse{
pam.TextInfo,
"nice to see you, Go!",
}, nil},
},
{
r: NewRequest("StartStringConv", pam.TextInfo, "hello PAM!"),
exp: []interface{}{SerializableStringConvResponse{
pam.TextInfo,
"nice to see you, Go!",
}, nil},
},
{
r: NewRequest("StartStringConvf", pam.TextInfo, "hello %s!", "PAM"),
exp: []interface{}{SerializableStringConvResponse{
pam.TextInfo,
"nice to see you, Go!",
}, nil},
},
},
},
"start-conv-prompt-error-msg": {
credentials: utils.Credentials{
ExpectedMessage: "This is wrong, PAM!",
ExpectedStyle: pam.ErrorMsg,
ErrorMsg: "ops, sorry...",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.ErrorMsg,
"This is wrong, PAM!",
}),
exp: []interface{}{SerializableStringConvResponse{
pam.ErrorMsg,
"ops, sorry...",
}, nil},
},
{
r: NewRequest("StartStringConv", pam.ErrorMsg,
"This is wrong, PAM!",
),
exp: []interface{}{SerializableStringConvResponse{
pam.ErrorMsg,
"ops, sorry...",
}, nil},
},
{
r: NewRequest("StartStringConvf", pam.ErrorMsg,
"This is wrong, %s!", "PAM",
),
exp: []interface{}{SerializableStringConvResponse{
pam.ErrorMsg,
"ops, sorry...",
}, nil},
},
},
},
"start-conv-prompt-echo-on": {
credentials: utils.Credentials{
ExpectedMessage: "Give me your non-private infos",
ExpectedStyle: pam.PromptEchoOn,
EchoOn: "here's my public data",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.PromptEchoOn,
"Give me your non-private infos",
}),
exp: []interface{}{SerializableStringConvResponse{
pam.PromptEchoOn,
"here's my public data",
}, nil},
},
{
r: NewRequest("StartStringConv", pam.PromptEchoOn,
"Give me your non-private infos",
),
exp: []interface{}{SerializableStringConvResponse{
pam.PromptEchoOn,
"here's my public data",
}, nil},
},
},
},
"start-conv-prompt-echo-off": {
credentials: utils.Credentials{
ExpectedMessage: "Give me your super-secret data",
ExpectedStyle: pam.PromptEchoOff,
EchoOff: "here's my private token",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.PromptEchoOff,
"Give me your super-secret data",
}),
exp: []interface{}{SerializableStringConvResponse{
pam.PromptEchoOff,
"here's my private token",
}, nil},
},
{
r: NewRequest("StartStringConv", pam.PromptEchoOff,
"Give me your super-secret data",
),
exp: []interface{}{SerializableStringConvResponse{
pam.PromptEchoOff,
"here's my private token",
}, nil},
},
},
},
"start-conv-text-info-handle-failure-message-mismatch": {
expectedError: pam.ErrConv,
credentials: utils.Credentials{
ExpectedMessage: "This is an info message",
ExpectedStyle: pam.TextInfo,
TextInfo: "And this is what is returned",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.TextInfo,
"This should have been an info message, but is not",
}),
exp: []interface{}{nil, pam.ErrConv},
},
{
r: NewRequest("StartStringConv", pam.TextInfo,
"This should have been an info message, but is not",
),
exp: []interface{}{nil, pam.ErrConv},
},
},
},
"start-conv-text-info-handle-failure-style-mismatch": {
expectedError: pam.ErrConv,
credentials: utils.Credentials{
ExpectedMessage: "This is an info message",
ExpectedStyle: pam.PromptEchoOff,
TextInfo: "And this is what is returned",
},
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.TextInfo,
"This is an info message",
}),
exp: []interface{}{nil, pam.ErrConv},
},
{
r: NewRequest("StartStringConv", pam.TextInfo,
"This is an info message",
),
exp: []interface{}{nil, pam.ErrConv},
},
},
},
"start-conv-binary": {
credentials: utils.NewBinaryTransactionWithData([]byte(
"\x00This is a binary data request\xC5\x00\xffYes it is!"),
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffYes it is!")),
}),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
{
r: NewRequest("StartBinaryConv",
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffYes it is!"))),
exp: []interface{}{SerializableBinaryConvResponse{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}, nil},
},
},
},
"start-conv-binary-handle-failure-passed-data-mismatch": {
expectedError: pam.ErrConv,
credentials: utils.NewBinaryTransactionWithData([]byte(
"\x00This is a binary data request\xC5\x00\xffYes it is!"),
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
(&Request{"Not the expected binary data", nil}).toTransactionData(t),
}),
exp: []interface{}{nil, pam.ErrConv},
},
{
r: NewRequest("StartBinaryConv",
(&Request{"Not the expected binary data", nil}).toTransactionData(t)),
exp: []interface{}{nil, pam.ErrConv},
},
},
},
"start-conv-binary-handle-failure-returned-data-mismatch": {
expectedError: pam.ErrConv,
credentials: utils.NewBinaryTransactionWithRandomData(100,
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99}),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
(&Request{"Wrong binary data", nil}).toTransactionData(t),
}),
exp: []interface{}{nil, pam.ErrConv},
},
{
r: NewRequest("StartBinaryConv",
(&Request{"Wrong binary data", nil}).toTransactionData(t)),
exp: []interface{}{nil, pam.ErrConv},
},
},
},
"start-conv-binary-in-nil": {
credentials: utils.NewBinaryTransactionWithData(nil,
(&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t)),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{}),
exp: []interface{}{SerializableBinaryConvResponse{
(&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t),
}, nil},
},
{
r: NewRequest("StartBinaryConv", nil),
exp: []interface{}{SerializableBinaryConvResponse{
(&Request{"Binary data", []interface{}{true, 123, 0.5, "yay!"}}).toBytes(t),
}, nil},
},
},
},
"start-conv-binary-out-nil": {
credentials: utils.NewBinaryTransactionWithData([]byte(
"\x00This is a binary data request\xC5\x00\xffGimme nil!"), nil),
checkedRequests: []checkedRequest{
{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffGimme nil!")),
}),
exp: []interface{}{SerializableBinaryConvResponse{}, nil},
},
{
r: NewRequest("StartBinaryConv",
utils.TestBinaryDataEncoder(
[]byte("\x00This is a binary data request\xC5\x00\xffGimme nil!"))),
exp: []interface{}{SerializableBinaryConvResponse{}, nil},
},
},
},
}
for name, tc := range tests {
tc := tc
name := name
t.Run(name, func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(ts.WorkDir(), name+".socket")
ts.CreateService(name, []utils.ServiceLine{
{Action: utils.Auth, Control: utils.Requisite, Module: modulePath,
Args: []string{socketPath}},
})
switch tc.credentials.(type) {
case pam.BinaryConversationHandler:
if !pam.CheckPamHasBinaryProtocol() {
t.Skip("Binary protocol is not supported")
}
case pam.BinaryPointerConversationHandler:
if !pam.CheckPamHasBinaryProtocol() {
t.Skip("Binary protocol is not supported")
}
}
tx, err := pam.StartConfDir(name, tc.user, tc.credentials, ts.WorkDir())
if err != nil {
t.Fatalf("start #error: %v", err)
}
defer func() {
err := tx.End()
if err != nil {
t.Fatalf("end #error: %v", err)
}
}()
listener := NewListener(socketPath)
if err := listener.StartListening(); err != nil {
t.Fatalf("listening #error: %v", err)
}
listenerHandler := func() error {
res, err := listener.WaitForData()
if err != nil {
return err
}
if res == nil || res.Action != "hello" {
return errors.New("missing hello packet")
}
req := NewRequest("GetItem", pam.Service)
if err := req.checkRemote(listener,
[]interface{}{strings.ToLower(name), nil}); err != nil {
return err
}
testState := testState{}
if tc.setup != nil {
if err := tc.setup(tx, listener, testState); err != nil {
return err
}
}
for i, req := range tc.checkedRequests {
if req.compareWithTestState {
expectedResults, _ := testState["expectedResults"].([][]interface{})
if err := req.r.checkRemote(listener, expectedResults[i]); err != nil {
return err
}
} else if err := req.checkRemote(listener); err != nil {
return err
}
}
if tc.finish != nil {
if err := tc.finish(tx, listener, testState); err != nil {
return err
}
}
if err := listener.SendRequest(&Request{Action: "bye"}); err != nil {
return err
}
return nil
}
serverError := make(chan error)
go func() {
serverError <- listenerHandler()
}()
authResult := make(chan error)
go func() {
authResult <- tx.Authenticate(pam.Silent)
}()
if err = <-serverError; err != nil {
t.Fatalf("communication #error: %v", err)
}
err = <-authResult
if !errors.Is(err, tc.expectedError) {
t.Fatalf("authenticate #unexpected: %#v vs %#v",
err, tc.expectedError)
}
})
}
t.Cleanup(func() {
// Ensure GC will happen, so that transaction's pam_end will be called
runtime.GC()
time.Sleep(5 * time.Millisecond)
})
}
func Test_Moduler_IntegrationTesterModule_handleRequest(t *testing.T) {
t.Parallel()
module := integrationTesterModule{}
mt := pam.NewModuleTransactionInvoker(nil)
tests := []struct {
checkedRequest
name string
parallel bool
}{
{
name: "putEnv",
checkedRequest: checkedRequest{
r: NewRequest("PutEnv", "FOO_ENV=Bar"),
exp: []interface{}{pam.ErrAbort},
},
},
{
parallel: true,
name: "get-item-Service",
checkedRequest: checkedRequest{
r: NewRequest("GetItem", pam.Service),
exp: []interface{}{"", pam.ErrSystem},
},
},
{
parallel: true,
name: "set-item-Service",
checkedRequest: checkedRequest{
r: NewRequest("SetItem", pam.Service, "foo"),
exp: []interface{}{pam.ErrSystem},
},
},
}
for _, cr := range tests {
cr := cr
t.Run(cr.name, func(t *testing.T) {
if cr.parallel {
t.Parallel()
}
authRequest := authRequest{mt, nil}
res, err := module.handleRequest(&authRequest, &cr.r)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if res.Action != "return" {
t.Fatalf("unexpected result action %v", res.Action)
}
if err := cr.check(res); err != nil {
t.Fatalf("unexpected result %v", err)
}
})
}
t.Run("missing-method", func(t *testing.T) {
t.Parallel()
req := NewRequest("Hopefully a missing method")
res, err := module.handleRequest(&authRequest{mt, nil}, &req)
if err == nil {
t.Fatalf("error was expected, got %v", res)
}
if res != nil {
t.Fatalf("unexpected result %v", res)
}
})
t.Run("wrong-signature", func(t *testing.T) {
t.Parallel()
req := NewRequest("GetItem", "this", "and", 3, "of that")
res, err := module.handleRequest(&authRequest{mt, nil}, &req)
if err == nil {
t.Fatalf("error was expected, got %v", res)
}
if res != nil {
t.Fatalf("unexpected result %v", res)
}
})
}
func Test_Moduler_IntegrationTesterModule_Authenticate(t *testing.T) {
t.Parallel()
ts := utils.NewTestSetup(t, utils.WithWorkDir())
module := integrationTesterModule{}
tests := map[string]struct {
expectedError error
credentials pam.ConversationHandler
checkedRequests []checkedRequest
}{
"success": {
expectedError: nil,
},
"get-item-Service": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{
{
r: NewRequest("GetItem", pam.Service),
exp: []interface{}{"", pam.ErrSystem},
},
},
},
"get-item-User": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{
{
r: NewRequest("GetItem", pam.User),
exp: []interface{}{"", pam.ErrSystem},
},
},
},
"putEnv": {
expectedError: pam.ErrAbort,
checkedRequests: []checkedRequest{
{
r: NewRequest("PutEnv", "FooBar=Baz"),
exp: []interface{}{pam.ErrAbort},
},
},
},
"SetData-nil": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "some-data", nil),
exp: []interface{}{pam.ErrSystem},
},
},
},
"SetData": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{
{
r: NewRequest("SetData", "some-data", true),
exp: []interface{}{pam.ErrSystem},
},
},
},
"StartConv": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{{
r: NewRequest("StartConv", SerializableStringConvRequest{
pam.TextInfo,
"hello PAM!",
}),
exp: []interface{}{nil, pam.ErrSystem},
}},
},
"StartStringConv": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{{
r: NewRequest("StartStringConv", pam.TextInfo, "hello PAM!"),
exp: []interface{}{nil, pam.ErrSystem},
}},
},
"StartConv-Binary": {
expectedError: pam.ErrSystem,
checkedRequests: []checkedRequest{{
r: NewRequest("StartConv", SerializableBinaryConvRequest{
[]byte{0x01, 0x02, 0x03, 0x05, 0x00, 0x99},
}),
exp: []interface{}{nil, pam.ErrSystem},
}},
},
}
for name, tc := range tests {
tc := tc
name := name
t.Run(name, func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(ts.WorkDir(), name+".socket")
listener := NewListener(socketPath)
if err := listener.StartListening(); err != nil {
t.Fatalf("listening #error: %v", err)
}
listenerHandler := func() error {
res, err := listener.WaitForData()
if err != nil {
return err
}
if res == nil || res.Action != "hello" {
return errors.New("missing hello packet")
}
for _, req := range tc.checkedRequests {
if err := req.checkRemote(listener); err != nil {
return err
}
}
if err := listener.SendRequest(&Request{Action: "bye"}); err != nil {
return err
}
return nil
}
serverError := make(chan error)
go func() {
serverError <- listenerHandler()
}()
authResult := make(chan error)
go func() {
authResult <- module.Authenticate(
pam.NewModuleTransactionInvoker(nil),
pam.Silent, []string{socketPath})
}()
if err := <-serverError; err != nil {
t.Fatalf("communication #error: %v", err)
}
err := <-authResult
if !errors.Is(err, tc.expectedError) {
t.Fatalf("authenticate #unexpected: %#v vs %#v",
err, tc.expectedError)
}
})
}
}