Modules have the ability to start PAM conversations, so while the transaction code can handle them we did not have a way to init them. Yet. So add some APIs allowing this, making it easier from the go side to handle the conversations. In this commit we only support text-based conversations, but code is designed with the idea of supporting binary cases too. Added the integration tests using the module that is now able to both start conversation and handle them using Go only.
1149 lines
30 KiB
Go
1149 lines
30 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/msteinert/pam/v2"
|
|
"github.com/msteinert/pam/v2/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 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},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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}},
|
|
})
|
|
|
|
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},
|
|
}},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|