1281 lines
34 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|