integration_test.go 5.79 KB
Newer Older
ale's avatar
ale committed
1 2 3 4
package integrationtest

import (
	"bytes"
5
	"context"
ale's avatar
ale committed
6
	"encoding/json"
7
	"errors"
ale's avatar
ale committed
8 9 10 11 12 13 14 15 16
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

17
	as "git.autistici.org/ai3/accountserver"
ale's avatar
ale committed
18 19
	cachebackend "git.autistici.org/ai3/accountserver/backend/cache"
	ldapbackend "git.autistici.org/ai3/accountserver/backend/ldap"
ale's avatar
ale committed
20 21
	"git.autistici.org/ai3/accountserver/ldaptest"
	"git.autistici.org/ai3/accountserver/server"
22
	ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
23 24
	"git.autistici.org/ai3/go-common/pwhash"
	"git.autistici.org/ai3/go-common/userenckey"
25
	"git.autistici.org/id/go-sso"
ale's avatar
ale committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
	"golang.org/x/crypto/ed25519"
)

const (
	testLDAPPort = 42872
	testLDAPAddr = "ldap://127.0.0.1:42872"

	testSSODomain  = "domain"
	testSSOService = "accountserver.domain/"
	testAdminUser  = "admin"
	testAdminGroup = "admins"
)

func withSSO(t testing.TB) (func(), sso.Signer, string) {
	tmpf, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}

	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		t.Fatal(err)
	}
ale's avatar
ale committed
49
	tmpf.Write(pub) // nolint
ale's avatar
ale committed
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
	tmpf.Close()

	signer, err := sso.NewSigner(priv)
	if err != nil {
		t.Fatal(err)
	}

	return func() {
		os.Remove(tmpf.Name())
	}, signer, tmpf.Name()
}

type testClient struct {
	srvURL string
	signer sso.Signer
}

67 68
func (c *testClient) ssoTicket(username string, groups ...string) string {
	if len(groups) == 0 && username == testAdminUser {
ale's avatar
ale committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
		groups = append(groups, testAdminGroup)
	}
	signed, err := c.signer.Sign(sso.NewTicket(username, testSSOService, testSSODomain, "", groups, 1*time.Hour))
	if err != nil {
		panic(err)
	}
	return signed
}

func (c *testClient) request(uri string, req, out interface{}) error {
	data, _ := json.Marshal(req)
	resp, err := http.Post(c.srvURL+uri, "application/json", bytes.NewReader(data))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
85 86 87 88 89 90
	data, _ = ioutil.ReadAll(resp.Body)

	if resp.StatusCode >= 400 && resp.StatusCode < 500 {
		log.Printf("request error: %s", string(data))
		return errors.New(string(data))
	}
ale's avatar
ale committed
91
	if resp.StatusCode != 200 {
92
		log.Printf("remote error: %s", string(data))
ale's avatar
ale committed
93 94 95 96 97 98 99 100 101 102 103 104
		return fmt.Errorf("http status code %d", resp.StatusCode)
	}
	if resp.Header.Get("Content-Type") != "application/json" {
		return fmt.Errorf("unexpected content-type %s", resp.Header.Get("Content-Type"))
	}

	if out == nil {
		return nil
	}
	return json.Unmarshal(data, out)
}

ale's avatar
ale committed
105
func startServiceWithConfigAndCache(t testing.TB, svcConfig as.Config, enableCache bool) (func(), as.Backend, *testClient) {
ale's avatar
ale committed
106
	stop := ldaptest.StartServer(t, &ldaptest.Config{
107 108 109 110 111 112 113
		Dir:  "../ldaptest",
		Port: testLDAPPort,
		Base: "dc=example,dc=com",
		LDIFs: []string{
			"testdata/base.ldif",
			"testdata/test1.ldif",
			"testdata/test2.ldif",
114
			"testdata/test3.ldif",
115
		},
ale's avatar
ale committed
116 117
	})

ale's avatar
ale committed
118
	be, err := ldapbackend.NewLDAPBackend(testLDAPAddr, "cn=manager,dc=example,dc=com", "password", "dc=example,dc=com")
ale's avatar
ale committed
119 120 121
	if err != nil {
		t.Fatal("NewLDAPBackend", err)
	}
ale's avatar
ale committed
122
	if enableCache {
123 124 125 126
		be, err = cachebackend.Wrap(be, nil, nil)
		if err != nil {
			t.Fatal("cachebackend.Wrap", err)
		}
ale's avatar
ale committed
127
	}
ale's avatar
ale committed
128 129 130 131 132 133 134

	ssoStop, signer, ssoPubKeyFile := withSSO(t)

	svcConfig.SSO.PublicKeyFile = ssoPubKeyFile
	svcConfig.SSO.Domain = testSSODomain
	svcConfig.SSO.Service = testSSOService
	svcConfig.SSO.AdminGroup = testAdminGroup
135 136
	svcConfig.Validation.ForbiddenUsernames = []string{"forbidden"}
	svcConfig.Validation.AvailableDomains = map[string][]string{
137 138
		as.ResourceTypeEmail:       []string{"example.com"},
		as.ResourceTypeMailingList: []string{"example.com"},
ale's avatar
ale committed
139
	}
140
	svcConfig.Validation.WebsiteRootDir = "/home/users/investici.org"
ale's avatar
ale committed
141
	shards := []string{"host1", "host2", "host3"}
142
	svcConfig.Shards.Available = map[string][]string{
143 144 145 146 147 148
		as.ResourceTypeEmail:       shards,
		as.ResourceTypeMailingList: shards,
		as.ResourceTypeWebsite:     shards,
		as.ResourceTypeDomain:      shards,
		as.ResourceTypeDAV:         shards,
		as.ResourceTypeDatabase:    shards,
149 150
	}
	svcConfig.Shards.Allowed = svcConfig.Shards.Available
ale's avatar
ale committed
151

152
	service, err := as.NewAccountService(be, &svcConfig)
ale's avatar
ale committed
153 154 155 156 157
	if err != nil {
		stop()
		t.Fatal("NewAccountService", err)
	}

158 159 160 161
	as, err := server.New(service, be, "", nil)
	if err != nil {
		t.Fatal("server.New", err)
	}
162
	srv := httptest.NewServer(as)
ale's avatar
ale committed
163 164 165 166 167 168 169 170 171 172

	c := &testClient{
		srvURL: srv.URL,
		signer: signer,
	}

	return func() {
		stop()
		srv.Close()
		ssoStop()
173
	}, be, c
ale's avatar
ale committed
174 175
}

ale's avatar
ale committed
176 177 178 179
func startServiceWithConfig(t testing.TB, svcConfig as.Config) (func(), as.Backend, *testClient) {
	return startServiceWithConfigAndCache(t, svcConfig, false)
}

180 181 182 183
func startService(t testing.TB) (func(), as.Backend, *testClient) {
	return startServiceWithConfig(t, as.Config{})
}

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
// Verify that some user authentication invariants are true. Returns
// the RawUser for further checks.
func checkUserInvariants(t *testing.T, be as.Backend, username, primaryPassword string) *as.RawUser {
	tx, _ := be.NewTransaction()
	user, err := tx.GetUser(context.Background(), username)
	if err != nil {
		t.Fatalf("GetUser(%s): %v", username, err)
	}

	// Verify that the password is correct.
	if !pwhash.ComparePassword(user.Password, primaryPassword) {
		t.Fatalf("password for user %s is not %s", username, primaryPassword)
	}

	// Verify that we can successfully encrypt keys.
	if user.HasEncryptionKeys {
		if _, err := userenckey.Decrypt(keysToBytes(user.Keys), []byte(primaryPassword)); err != nil {
			t.Fatalf("password for user %s can't decrypt keys", username)
		}
	}

ale's avatar
ale committed
205 206 207 208 209 210 211 212 213
	// Verify that the user shard matches the email resource shard.
	email := user.GetSingleResourceByType(as.ResourceTypeEmail)
	if email == nil {
		t.Fatalf("no email resources for user %s", username)
	}
	if user.Shard != email.Shard {
		t.Fatalf("user and email shards differ ('%s' vs '%s')", user.Shard, email.Shard)
	}

214 215 216
	return user
}

217
func keysToBytes(keys []*ct.EncryptedKey) [][]byte {
218 219
	var rawKeys [][]byte
	for _, k := range keys {
220
		rawKeys = append(rawKeys, k.EncryptedKey)
221 222 223
	}
	return rawKeys
}