actions_test.go 20.3 KB
Newer Older
1 2 3 4
package accountserver

import (
	"context"
ale's avatar
ale committed
5
	"errors"
ale's avatar
ale committed
6
	"fmt"
7
	"path/filepath"
ale's avatar
ale committed
8
	"strings"
9 10
	"testing"

11
	ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
12
	"git.autistici.org/ai3/go-common/pwhash"
13 14 15
	sso "git.autistici.org/id/go-sso"
)

16 17
const testUser = "testuser@example.com"

18 19
type fakeBackend struct {
	users                map[string]*User
ale's avatar
ale committed
20
	resources            map[string]*Resource
21
	passwords            map[string]string
22
	recoveryPasswords    map[string]string
23
	appSpecificPasswords map[string][]*AppSpecificPasswordInfo
24
	encryptionKeys       map[string][]*ct.EncryptedKey
25 26
}

ale's avatar
ale committed
27 28 29 30 31 32 33 34
func (b *fakeBackend) NewTransaction() (TX, error) {
	return b, nil
}

func (b *fakeBackend) Commit(_ context.Context) error {
	return nil
}

ale's avatar
ale committed
35 36 37 38
func (b *fakeBackend) NextUID(_ context.Context) (int, error) {
	return 42, nil
}

ale's avatar
ale committed
39 40 41 42 43
func (b *fakeBackend) CanAccessResource(_ context.Context, username string, rsrc *Resource) bool {
	owner := strings.Split(string(rsrc.ID), "/")[0]
	return owner == username
}

44
func (b *fakeBackend) GetUser(_ context.Context, username string) (*RawUser, error) {
ale's avatar
ale committed
45 46 47 48
	u, ok := b.users[username]
	if !ok {
		return nil, errors.New("user not found in fake backend")
	}
49 50 51 52 53 54
	return &RawUser{
		User:             *u,
		Password:         b.passwords[username],
		RecoveryPassword: b.recoveryPasswords[username],
		Keys:             b.encryptionKeys[username],
	}, nil
55 56
}

ale's avatar
ale committed
57 58 59 60 61 62 63 64 65 66
func (b *fakeBackend) SearchUser(_ context.Context, pattern string) ([]string, error) {
	var out []string
	for username := range b.users {
		if strings.HasPrefix(username, pattern) {
			out = append(out, username)
		}
	}
	return out, nil
}

67 68 69 70 71
func (b *fakeBackend) UpdateUser(_ context.Context, user *User) error {
	b.users[user.Name] = user
	return nil
}

ale's avatar
ale committed
72 73 74 75
func (b *fakeBackend) CreateUser(_ context.Context, user *User) (*User, error) {
	for _, r := range user.Resources {
		r.ID = makeResourceID(user.Name, r.Type, r.Name)
	}
ale's avatar
ale committed
76
	b.users[user.Name] = user
ale's avatar
ale committed
77
	return user, nil
ale's avatar
ale committed
78 79
}

ale's avatar
ale committed
80 81 82 83
func (b *fakeBackend) GetResource(_ context.Context, resourceID ResourceID) (*RawResource, error) {
	owner := strings.Split(resourceID.String(), "/")[0]
	r := b.resources[resourceID.String()]
	return &RawResource{Resource: *r, Owner: owner}, nil
84 85
}

86 87 88 89 90 91 92 93 94 95 96 97
func (b *fakeBackend) SearchResource(_ context.Context, pattern string) ([]*RawResource, error) {
	var out []*RawResource
	for id, r := range b.resources {
		owner := strings.Split(id, "/")[0]
		// Emulate LDAP wildcard syntax.
		if ok, _ := filepath.Match(pattern, r.Name); ok {
			out = append(out, &RawResource{Resource: *r, Owner: owner})
		}
	}
	return out, nil
}

98
func (b *fakeBackend) UpdateResource(_ context.Context, r *Resource) error {
ale's avatar
ale committed
99
	b.resources[r.ID.String()] = r
100 101 102
	return nil
}

ale's avatar
ale committed
103 104 105
func makeResourceID(owner, rtype, rname string) ResourceID {
	return ResourceID(fmt.Sprintf("%s/%s/%s", owner, rtype, rname))
}
ale's avatar
ale committed
106

ale's avatar
ale committed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
func (b *fakeBackend) CreateResources(_ context.Context, u *User, rsrcs []*Resource) ([]*Resource, error) {
	var out []*Resource
	for _, r := range rsrcs {
		if !r.ID.Empty() {
			return nil, errors.New("resource ID not empty")
		}

		var username string
		if u != nil {
			username = u.Name
		}
		r.ID = makeResourceID(username, r.Type, r.Name)

		if _, ok := b.resources[r.ID.String()]; ok {
			return nil, errors.New("resource already exists")
		}

		b.resources[r.ID.String()] = r
		out = append(out, r)
	}
	return out, nil
ale's avatar
ale committed
128 129
}

130 131 132 133 134
func (b *fakeBackend) SetUserPassword(_ context.Context, user *User, password string) error {
	b.passwords[user.Name] = password
	return nil
}

135 136
func (b *fakeBackend) SetAccountRecoveryHint(_ context.Context, user *User, hint, response string) error {
	b.users[user.Name].AccountRecoveryHint = hint
137
	b.recoveryPasswords[user.Name] = response
ale's avatar
ale committed
138 139 140
	return nil
}

141 142
func (b *fakeBackend) DeleteAccountRecoveryHint(_ context.Context, user *User) error {
	b.users[user.Name].AccountRecoveryHint = ""
143 144
	delete(b.recoveryPasswords, user.Name)
	return nil
145 146
}

147
func (b *fakeBackend) SetResourcePassword(_ context.Context, r *Resource, password string) error {
148
	b.passwords[r.ID.String()] = password
149 150 151
	return nil
}

152
func (b *fakeBackend) GetUserEncryptionKeys(_ context.Context, user *User) ([]*ct.EncryptedKey, error) {
153 154 155
	return b.encryptionKeys[user.Name], nil
}

156
func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys []*ct.EncryptedKey) error {
157
	b.encryptionKeys[user.Name] = keys
158
	b.users[user.Name].HasEncryptionKeys = true
159 160 161
	return nil
}

162 163 164 165
func (b *fakeBackend) SetUserEncryptionPublicKey(_ context.Context, user *User, pub []byte) error {
	return nil
}

166 167 168 169 170 171 172 173 174
func (b *fakeBackend) SetApplicationSpecificPassword(_ context.Context, user *User, info *AppSpecificPasswordInfo, _ string) error {
	b.appSpecificPasswords[user.Name] = append(b.appSpecificPasswords[user.Name], info)
	return nil
}

func (b *fakeBackend) DeleteApplicationSpecificPassword(_ context.Context, user *User, id string) error {
	return nil
}

ale's avatar
ale committed
175 176 177 178 179 180 181 182
func (b *fakeBackend) SetUserTOTPSecret(_ context.Context, user *User, secret string) error {
	return nil
}

func (b *fakeBackend) DeleteUserTOTPSecret(_ context.Context, user *User) error {
	return nil
}

ale's avatar
ale committed
183
func (b *fakeBackend) HasAnyResource(_ context.Context, rsrcs []FindResourceRequest) (bool, error) {
184
	for _, fr := range rsrcs {
ale's avatar
ale committed
185 186 187
		for _, r := range b.resources {
			if r.Type == fr.Type && r.Name == fr.Name {
				return true, nil
188 189 190
			}
		}
	}
ale's avatar
ale committed
191 192 193
	return false, nil
}

194 195
const testAdminGroupName = "admins"

ale's avatar
ale committed
196 197
// Fake SSO validator: the sso ticket username is just the ticket
// itself. The only invalid value is the empty string.
198 199 200 201
type fakeValidator struct {
	adminUser string
}

202
func (v *fakeValidator) Validate(tkt, nonce, service string, _ []string) (*sso.Ticket, error) {
ale's avatar
ale committed
203 204 205
	if tkt == "" {
		return nil, errors.New("empty sso ticket")
	}
206 207 208 209 210 211 212 213 214 215 216 217
	var groups []string
	if tkt == v.adminUser {
		groups = []string{testAdminGroupName}
	}
	return &sso.Ticket{
		User:    tkt,
		Service: service,
		Domain:  "test",
		Groups:  groups,
	}, nil
}

218
func (b *fakeBackend) addUser(user *User, pw, rpw string) {
ale's avatar
ale committed
219
	b.users[user.Name] = user
ale's avatar
ale committed
220
	//b.resources[user.Name] = make(map[string]*Resource)
221 222 223 224
	b.passwords[user.Name] = pwhash.Encrypt(pw)
	if rpw != "" {
		b.recoveryPasswords[user.Name] = pwhash.Encrypt(rpw)
	}
ale's avatar
ale committed
225
	for _, r := range user.Resources {
ale's avatar
ale committed
226
		b.resources[r.ID.String()] = r
ale's avatar
ale committed
227 228 229
	}
}

230 231
func createFakeBackend() *fakeBackend {
	fb := &fakeBackend{
ale's avatar
ale committed
232 233
		users:                make(map[string]*User),
		resources:            make(map[string]*Resource),
234
		passwords:            make(map[string]string),
235
		recoveryPasswords:    make(map[string]string),
236
		appSpecificPasswords: make(map[string][]*AppSpecificPasswordInfo),
237
		encryptionKeys:       make(map[string][]*ct.EncryptedKey),
238
	}
ale's avatar
ale committed
239
	fb.addUser(&User{
240
		Name:   testUser,
ale's avatar
ale committed
241 242 243
		Status: UserStatusActive,
		Shard:  "1",
		UID:    4242,
ale's avatar
ale committed
244 245
		Resources: []*Resource{
			{
246 247
				ID:     makeResourceID(testUser, ResourceTypeEmail, testUser),
				Name:   testUser,
ale's avatar
ale committed
248
				Type:   ResourceTypeEmail,
ale's avatar
ale committed
249
				Status: ResourceStatusActive,
ale's avatar
ale committed
250
				Shard:  "1",
ale's avatar
ale committed
251 252 253 254 255
				Email: &Email{
					Maildir: "example.com/testuser",
				},
			},
			{
256
				ID:     makeResourceID(testUser, ResourceTypeDAV, "dav1"),
ale's avatar
ale committed
257
				Name:   "dav1",
ale's avatar
ale committed
258
				Type:   ResourceTypeDAV,
ale's avatar
ale committed
259 260
				Status: ResourceStatusActive,
				DAV: &WebDAV{
261
					UID:     4242,
ale's avatar
ale committed
262 263
					Homedir: "/home/dav1",
				},
ale's avatar
ale committed
264 265
			},
		},
266
	}, "password", "recoverypassword")
267 268 269
	return fb
}

ale's avatar
ale committed
270 271
func testConfig() *Config {
	var c Config
272 273
	c.Validation.ForbiddenUsernames = []string{"root"}
	c.Validation.AvailableDomains = map[string][]string{
ale's avatar
ale committed
274 275
		ResourceTypeEmail:       []string{"example.com"},
		ResourceTypeMailingList: []string{"example.com"},
ale's avatar
ale committed
276
	}
ale's avatar
ale committed
277 278 279
	c.SSO.Domain = "mydomain"
	c.SSO.Service = "service/"
	c.SSO.AdminGroup = testAdminGroupName
ale's avatar
ale committed
280 281 282 283 284 285 286 287
	c.Shards.Available = map[string][]string{
		ResourceTypeEmail:       []string{"host1", "host2", "host3"},
		ResourceTypeMailingList: []string{"host1", "host2", "host3"},
		ResourceTypeWebsite:     []string{"host1", "host2", "host3"},
		ResourceTypeDomain:      []string{"host1", "host2", "host3"},
		ResourceTypeDAV:         []string{"host1", "host2", "host3"},
	}
	c.Shards.Allowed = c.Shards.Available
ale's avatar
ale committed
288 289 290
	return &c
}

291
func testService(admin string) *AccountService {
ale's avatar
ale committed
292
	be := createFakeBackend()
293
	svc, _ := newAccountServiceWithSSO(be, testConfig(), &fakeValidator{admin})
294
	return svc
ale's avatar
ale committed
295 296
}

ale's avatar
ale committed
297
func getUser(t testing.TB, svc *AccountService, username string) *User {
298
	req := &GetUserRequest{
299 300
		UserRequestBase: UserRequestBase{
			RequestBase: RequestBase{
301
				SSO: testUser,
302
			},
303
			Username: testUser,
304 305
		},
	}
306
	resp, err := svc.Handle(context.TODO(), req)
307 308 309
	if err != nil {
		t.Fatal(err)
	}
ale's avatar
ale committed
310 311 312 313 314 315
	return resp.(*User)
}

func TestService_GetUser(t *testing.T) {
	svc := testService("")

316 317
	user := getUser(t, svc, testUser)
	if user.Name != testUser {
ale's avatar
ale committed
318
		t.Fatalf("bad response: %+v", user)
319 320 321
	}
}

322 323 324 325 326
func TestService_GetUser_ResourceGroups(t *testing.T) {
	fb := createFakeBackend()
	svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})

	fb.addUser(&User{
ale's avatar
ale committed
327 328
		Name:   "testuser2",
		Status: UserStatusActive,
329 330
		Resources: []*Resource{
			{
ale's avatar
ale committed
331 332
				ID:   makeResourceID("testuser2", ResourceTypeDAV, "dav1"),
				Type: ResourceTypeDAV,
333 334 335 336 337 338
				Name: "dav1",
				DAV: &WebDAV{
					Homedir: "/home/users/investici.org/dav1",
				},
			},
			{
ale's avatar
ale committed
339 340
				ID:   makeResourceID("testuser2", ResourceTypeDAV, "dav1-domain2"),
				Type: ResourceTypeDAV,
341 342 343 344 345 346
				Name: "dav1-domain2",
				DAV: &WebDAV{
					Homedir: "/home/users/investici.org/dav1/html-domain2.com/subdir",
				},
			},
			{
ale's avatar
ale committed
347 348
				ID:   makeResourceID("testuser2", ResourceTypeDomain, "domain1.com"),
				Type: ResourceTypeDomain,
349 350 351 352 353 354
				Name: "domain1.com",
				Website: &Website{
					DocumentRoot: "/home/users/investici.org/dav1/html-domain1.com",
				},
			},
			{
ale's avatar
ale committed
355 356
				ID:   makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
				Type: ResourceTypeDomain,
357 358 359 360 361 362
				Name: "domain2.com",
				Website: &Website{
					DocumentRoot: "/home/users/investici.org/dav1/html-domain2.com",
				},
			},
			{
ale's avatar
ale committed
363 364 365
				ID:       makeResourceID("testuser2", ResourceTypeDatabase, "db2"),
				ParentID: makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
				Type:     ResourceTypeDatabase,
366 367 368 369 370 371 372
				Name:     "db2",
				Database: &Database{},
			},
		},
	}, "", "")

	req := &GetUserRequest{
373 374 375 376
		UserRequestBase: UserRequestBase{
			RequestBase: RequestBase{
				SSO: "testuser2",
			},
377 378 379
			Username: "testuser2",
		},
	}
380
	resp, err := svc.Handle(context.TODO(), req)
381 382 383
	if err != nil {
		t.Fatal(err)
	}
384
	user := resp.(*User)
385 386 387

	var grouped []*Resource
	for _, r := range user.Resources {
ale's avatar
ale committed
388
		switch r.Type {
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
		case ResourceTypeWebsite, ResourceTypeDomain, ResourceTypeDAV, ResourceTypeDatabase:
			grouped = append(grouped, r)
		}
	}

	var group string
	for _, r := range grouped {
		if r.Group == "" {
			t.Errorf("group not set on %s", r.ID)
			continue
		}
		if group == "" {
			group = r.Group
		} else if group != r.Group {
			t.Errorf("wrong group on %s (%s, expected %s)", r.ID, r.Group, group)
		}
	}

}

409
func TestService_Auth(t *testing.T) {
410
	svc := testService("adminuser")
411 412 413 414 415

	for _, td := range []struct {
		sso        string
		expectedOk bool
	}{
416
		{testUser, true},
417 418 419 420
		{"otheruser", false},
		{"adminuser", true},
	} {
		req := &GetUserRequest{
421 422 423 424
			UserRequestBase: UserRequestBase{
				RequestBase: RequestBase{
					SSO: td.sso,
				},
425
				Username: testUser,
426 427
			},
		}
428
		_, err := svc.Handle(context.TODO(), req)
429 430 431 432 433 434 435 436 437 438 439 440 441 442
		if err != nil {
			if !IsAuthError(err) {
				t.Errorf("error for sso_user=%s is not an auth error: %v", td.sso, err)
			} else if td.expectedOk {
				t.Errorf("error with sso_user=%s: %v", td.sso, err)
			}
		} else if !td.expectedOk {
			t.Errorf("no error with sso_user=%s", td.sso)
		}
	}
}

func TestService_ChangePassword(t *testing.T) {
	fb := createFakeBackend()
443
	svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})
444

445 446 447 448 449
	testdata := []struct {
		password    string
		newPassword string
		expectedOk  bool
	}{
450 451 452
		// First, fail cur_password authentication.
		{"BADPASS", "new_password", false},

453 454 455 456 457 458
		// Ordering is important as it is meant to emulate
		// setting the password, failing to reset it, then
		// succeeding.
		{"password", "new_password", true},
		{"BADPASS", "new_password_2", false},
		{"new_password", "new_password_2", true},
459
	}
460 461 462
	for _, td := range testdata {
		req := &ChangeUserPasswordRequest{
			PrivilegedRequestBase: PrivilegedRequestBase{
463 464
				UserRequestBase: UserRequestBase{
					RequestBase: RequestBase{
465
						SSO: testUser,
466
					},
467
					Username: testUser,
468 469 470 471 472
				},
				CurPassword: td.password,
			},
			Password: td.newPassword,
		}
473
		_, err := svc.Handle(context.TODO(), req)
474 475 476 477 478
		if err == nil && !td.expectedOk {
			t.Fatalf("ChangeUserPassword(old=%s new=%s) should have failed but didn't", td.password, td.newPassword)
		} else if err != nil && td.expectedOk {
			t.Fatalf("ChangeUserPassword(old=%s new=%s) failed: %v", td.password, td.newPassword, err)
		}
479 480
	}

481
	if _, ok := fb.passwords[testUser]; !ok {
ale's avatar
ale committed
482 483
		t.Error("password was not set on the backend")
	}
484
	// if len(fb.encryptionKeys[testUser]) != 1 {
485 486
	// 	t.Errorf("no encryption keys were set")
	// }
487
}
488 489 490 491 492

// Lower level test that basically corresponds to the same operations
// as TestService_ChangePassword above, but exercises the
// initializeUserEncryptionKeys / updateUserEncryptionKeys code path
// directly.
493 494 495 496 497 498
// func TestService_EncryptionKeys(t *testing.T) {
// 	fb := createFakeBackend()
// 	svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})
// 	tx, _ := fb.NewTransaction()
// 	ctx := context.Background()

499
// 	user, _ := getUserOrDie(ctx, tx, testUser)
500 501 502 503 504 505 506 507 508

// 	// Set the keys to something.
// 	keys, _, err := svc.initializeEncryptionKeys(ctx, tx, user, "password")
// 	if err != nil {
// 		t.Fatal("init", err)
// 	}
// 	if err := tx.SetUserEncryptionKeys(ctx, user, keys); err != nil {
// 		t.Fatal("SetUserEncryptionKeys", err)
// 	}
509
// 	if n := len(fb.encryptionKeys[testUser]); n != 1 {
510 511 512 513 514 515 516 517 518 519 520
// 		t.Fatalf("found %d encryption keys, expected 1", n)
// 	}

// 	// Try to read (decrypt) them again using bad / good passwords.
// 	if _, _, err := svc.readOrInitializeEncryptionKeys(ctx, tx, user, "BADPASS", "new_password"); err == nil {
// 		t.Fatal("read with bad password did not fail")
// 	}
// 	if _, _, err := svc.readOrInitializeEncryptionKeys(ctx, tx, user, "password", "new_password"); err != nil {
// 		t.Fatal("readOrInitialize", err)
// 	}
// }
ale's avatar
ale committed
521 522 523

// Try adding aliases to the email resource.
func TestService_AddEmailAlias(t *testing.T) {
524
	svc := testService("")
ale's avatar
ale committed
525

ale's avatar
ale committed
526
	// Find the resource ID.
527
	user := getUser(t, svc, testUser)
ale's avatar
ale committed
528 529
	emailID := user.GetSingleResourceByType(ResourceTypeEmail).ID

ale's avatar
ale committed
530 531 532 533 534 535 536 537 538 539 540 541
	testdata := []struct {
		addr       string
		expectedOk bool
	}{
		{"alias@example.com", true},
		{"another-example-address@example.com", true},
		{"root@example.com", false},
		{"alias@other-domain.com", false},
	}
	for _, td := range testdata {
		req := &AddEmailAliasRequest{
			ResourceRequestBase: ResourceRequestBase{
542
				RequestBase: RequestBase{
543
					SSO: testUser,
544
				},
ale's avatar
ale committed
545
				ResourceID: emailID,
ale's avatar
ale committed
546 547 548
			},
			Addr: td.addr,
		}
549
		_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
550 551 552 553 554 555 556
		if err != nil && td.expectedOk {
			t.Errorf("AddEmailAlias(%s) failed: %v", td.addr, err)
		} else if err == nil && !td.expectedOk {
			t.Errorf("AddEmailAlias(%s) did not fail but should have", td.addr)
		}
	}
}
557

ale's avatar
ale committed
558
func TestService_CreateResource(t *testing.T) {
559
	svc := testService("admin")
560

561
	req := &CreateResourcesRequest{
562 563 564
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
565 566
			},
		},
567
		Username: testUser,
568 569
		Resources: []*Resource{
			&Resource{
ale's avatar
ale committed
570
				Type:          ResourceTypeDAV,
ale's avatar
ale committed
571
				Name:          "dav2",
572 573 574
				Status:        ResourceStatusActive,
				Shard:         "host2",
				OriginalShard: "host2",
ale's avatar
ale committed
575 576
				DAV: &WebDAV{
					Homedir: "/home/dav2",
577 578 579
				},
			},
		},
580 581 582
	}

	// The request should succeed the first time around.
ale's avatar
ale committed
583
	obj, err := svc.Handle(context.TODO(), req)
584 585 586
	if err != nil {
		t.Fatal("CreateResources", err)
	}
ale's avatar
ale committed
587 588 589 590 591 592 593 594 595 596
	resp := obj.(*CreateResourcesResponse)

	// Check that a ResourceID has been set.
	if len(resp.Resources) != 1 {
		t.Fatalf("bad response, expected 1 resource, got %+v", resp)
	}
	rsrc := resp.Resources[0]
	if rsrc.ID.Empty() {
		t.Fatal("Resource ID is empty!")
	}
597 598

	// The object already exists, so the same request should fail now.
599
	_, err = svc.Handle(context.TODO(), req)
600 601 602
	if err == nil {
		t.Fatal("creating a duplicate resource did not fail")
	}
603
}
ale's avatar
ale committed
604

ale's avatar
ale committed
605
func TestService_CreateResource_List(t *testing.T) {
606
	svc := testService("admin")
ale's avatar
ale committed
607 608 609

	// A list is an example of a user-less (global) resource.
	req := &CreateResourcesRequest{
610 611 612
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
613 614
			},
		},
615
		Username: testUser,
ale's avatar
ale committed
616 617
		Resources: []*Resource{
			&Resource{
ale's avatar
ale committed
618
				Type:          ResourceTypeMailingList,
ale's avatar
ale committed
619 620 621 622 623
				Name:          "list@example.com",
				Status:        ResourceStatusActive,
				Shard:         "host2",
				OriginalShard: "host2",
				List: &MailingList{
624
					Admins: []string{testUser},
ale's avatar
ale committed
625 626 627 628 629 630
				},
			},
		},
	}

	// The request should succeed.
631
	_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
632 633 634 635 636
	if err != nil {
		t.Fatal("CreateResources", err)
	}
}

ale's avatar
ale committed
637
func TestService_CreateUser(t *testing.T) {
638
	svc := testService("admin")
ale's avatar
ale committed
639

ale's avatar
ale committed
640
	//emailResourceID := NewResourceID(ResourceTypeEmail, "testuser2@example.com", "testuser2@example.com")
ale's avatar
ale committed
641
	req := &CreateUserRequest{
642 643 644 645 646
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
			},
		},
ale's avatar
ale committed
647 648 649 650
		User: &User{
			Name: "testuser2@example.com",
			Resources: []*Resource{
				&Resource{
ale's avatar
ale committed
651
					Type: ResourceTypeEmail,
ale's avatar
ale committed
652 653 654 655
					Name: "testuser2@example.com",
					//Status:        ResourceStatusActive,
					//Shard:         "host2",
					//OriginalShard: "host2",
ale's avatar
ale committed
656 657
					Email: &Email{
						Maildir: "example.com/testuser2",
ale's avatar
ale committed
658 659 660 661 662 663 664
					},
				},
			},
		},
	}

	// The request should succeed the first time around.
665
	resp, err := svc.Handle(context.Background(), req)
ale's avatar
ale committed
666 667 668
	if err != nil {
		t.Fatal("CreateResources", err)
	}
669 670 671
	cresp := resp.(*CreateUserResponse)
	if cresp.User.Name != "testuser2@example.com" {
		t.Fatalf("unexpected user in response: got %s, expected testuser2", cresp.User.Name)
ale's avatar
ale committed
672
	}
ale's avatar
ale committed
673

674 675
	// Hit the database to verify that the object has been created.
	tx, _ := svc.backend.NewTransaction()
ale's avatar
ale committed
676 677 678 679 680 681
	user, _ := tx.GetUser(context.Background(), "testuser2@example.com")
	if user == nil {
		t.Fatal("GetUser returned nil")
	}

	// Verify that the new resource has default fields set.
ale's avatar
ale committed
682
	resource := user.GetSingleResourceByType(ResourceTypeEmail)
ale's avatar
ale committed
683
	if resource == nil {
ale's avatar
ale committed
684
		t.Fatalf("no email resource in user %+v", user)
ale's avatar
ale committed
685 686 687 688
	}
	if resource.Shard == "" {
		t.Fatalf("resource shard is unset: %+v", resource)
	}
ale's avatar
ale committed
689
}
ale's avatar
ale committed
690 691

func TestService_CreateUser_FailIfNotAdmin(t *testing.T) {
692
	svc := testService("admin")
ale's avatar
ale committed
693 694

	req := &CreateUserRequest{
695 696
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
697
				SSO: testUser,
698 699
			},
		},
ale's avatar
ale committed
700 701 702 703
		User: &User{
			Name: "testuser2@example.com",
			Resources: []*Resource{
				&Resource{
ale's avatar
ale committed
704
					Type:          ResourceTypeEmail,
ale's avatar
ale committed
705 706 707 708 709 710 711 712 713 714 715 716 717
					Name:          "testuser2@example.com",
					Status:        ResourceStatusActive,
					Shard:         "host2",
					OriginalShard: "host2",
					Email: &Email{
						Maildir: "example.com/testuser2",
					},
				},
			},
		},
	}

	// The request should succeed the first time around.
718
	_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
719
	if err == nil {
720
		t.Fatal("unauthorized CreateResources did not fail")
ale's avatar
ale committed
721 722
	}
}
723 724

func TestService_ResetResourcePassword(t *testing.T) {
725
	svc := testService("")
ale's avatar
ale committed
726

727
	user := getUser(t, svc, testUser)
ale's avatar
ale committed
728
	id := user.GetSingleResourceByType(ResourceTypeDAV).ID
729 730
	req := &ResetResourcePasswordRequest{
		ResourceRequestBase: ResourceRequestBase{
731
			RequestBase: RequestBase{
732
				SSO: testUser,
733
			},
734 735 736
			ResourceID: id,
		},
	}
737
	resp, err := svc.Handle(context.TODO(), req)
738 739 740
	if err != nil {
		t.Fatal("ResetResourcePassword", err)
	}
741 742 743
	rresp := resp.(*ResetResourcePasswordResponse)
	if len(rresp.Password) < 10 {
		t.Fatalf("short password: %q", rresp.Password)
744
	}
745
	storedPw, ok := svc.backend.(*fakeBackend).passwords[id.String()]
746 747 748
	if !ok {
		t.Fatal("resource password was not actually set on the backend")
	}
749
	if storedPw == rresp.Password {
750 751 752
		t.Fatal("oops, it appears that the password was stored in cleartext on the backend")
	}
}
753

ale's avatar
ale committed
754 755
func TestService_AccountRecovery(t *testing.T) {
	svc := testService("")
756

ale's avatar
ale committed
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
	// Bad recovery response.
	req := &AccountRecoveryRequest{
		Username:         testUser,
		RecoveryPassword: "BADPASS",
		Password:         "new_password",
	}
	_, err := svc.Handle(context.TODO(), req)
	if err == nil {
		t.Fatal("oops, recovered account with bad password")
	}

	// Successful account recovery.
	req = &AccountRecoveryRequest{
		Username:         testUser,
		RecoveryPassword: "recoverypassword",
		Password:         "new_password",
	}
	_, err = svc.Handle(context.TODO(), req)
	if err != nil {
		t.Fatalf("account recovery failed: %v", err)
	}
}