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

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

10
	"git.autistici.org/ai3/go-common/pwhash"
11 12 13
	sso "git.autistici.org/id/go-sso"
)

14 15
const testUser = "testuser@example.com"

16 17
type fakeBackend struct {
	users                map[string]*User
ale's avatar
ale committed
18
	resources            map[string]*Resource
19
	passwords            map[string]string
20
	recoveryPasswords    map[string]string
21 22 23 24
	appSpecificPasswords map[string][]*AppSpecificPasswordInfo
	encryptionKeys       map[string][]*UserEncryptionKey
}

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

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

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

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

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

ale's avatar
ale committed
55 56 57 58 59 60 61 62 63 64
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
}

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

ale's avatar
ale committed
70 71 72 73
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
74
	b.users[user.Name] = user
ale's avatar
ale committed
75
	return user, nil
ale's avatar
ale committed
76 77
}

ale's avatar
ale committed
78 79 80 81
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
82 83
}

84
func (b *fakeBackend) UpdateResource(_ context.Context, r *Resource) error {
ale's avatar
ale committed
85
	b.resources[r.ID.String()] = r
86 87 88
	return nil
}

ale's avatar
ale committed
89 90 91
func makeResourceID(owner, rtype, rname string) ResourceID {
	return ResourceID(fmt.Sprintf("%s/%s/%s", owner, rtype, rname))
}
ale's avatar
ale committed
92

ale's avatar
ale committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
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
114 115
}

116 117 118 119 120
func (b *fakeBackend) SetUserPassword(_ context.Context, user *User, password string) error {
	b.passwords[user.Name] = password
	return nil
}

121 122
func (b *fakeBackend) SetAccountRecoveryHint(_ context.Context, user *User, hint, response string) error {
	b.users[user.Name].AccountRecoveryHint = hint
123
	b.recoveryPasswords[user.Name] = response
ale's avatar
ale committed
124 125 126
	return nil
}

127 128
func (b *fakeBackend) DeleteAccountRecoveryHint(_ context.Context, user *User) error {
	b.users[user.Name].AccountRecoveryHint = ""
129 130
	delete(b.recoveryPasswords, user.Name)
	return nil
131 132
}

133
func (b *fakeBackend) SetResourcePassword(_ context.Context, r *Resource, password string) error {
134
	b.passwords[r.ID.String()] = password
135 136 137 138 139 140 141 142 143
	return nil
}

func (b *fakeBackend) GetUserEncryptionKeys(_ context.Context, user *User) ([]*UserEncryptionKey, error) {
	return b.encryptionKeys[user.Name], nil
}

func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys []*UserEncryptionKey) error {
	b.encryptionKeys[user.Name] = keys
144
	b.users[user.Name].HasEncryptionKeys = true
145 146 147
	return nil
}

148 149 150 151
func (b *fakeBackend) SetUserEncryptionPublicKey(_ context.Context, user *User, pub []byte) error {
	return nil
}

152 153 154 155 156 157 158 159 160
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
161 162 163 164 165 166 167 168
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
169
func (b *fakeBackend) HasAnyResource(_ context.Context, rsrcs []FindResourceRequest) (bool, error) {
170
	for _, fr := range rsrcs {
ale's avatar
ale committed
171 172 173
		for _, r := range b.resources {
			if r.Type == fr.Type && r.Name == fr.Name {
				return true, nil
174 175 176
			}
		}
	}
ale's avatar
ale committed
177 178 179
	return false, nil
}

180 181
const testAdminGroupName = "admins"

ale's avatar
ale committed
182 183
// Fake SSO validator: the sso ticket username is just the ticket
// itself. The only invalid value is the empty string.
184 185 186 187
type fakeValidator struct {
	adminUser string
}

188
func (v *fakeValidator) Validate(tkt, nonce, service string, _ []string) (*sso.Ticket, error) {
ale's avatar
ale committed
189 190 191
	if tkt == "" {
		return nil, errors.New("empty sso ticket")
	}
192 193 194 195 196 197 198 199 200 201 202 203
	var groups []string
	if tkt == v.adminUser {
		groups = []string{testAdminGroupName}
	}
	return &sso.Ticket{
		User:    tkt,
		Service: service,
		Domain:  "test",
		Groups:  groups,
	}, nil
}

204
func (b *fakeBackend) addUser(user *User, pw, rpw string) {
ale's avatar
ale committed
205
	b.users[user.Name] = user
ale's avatar
ale committed
206
	//b.resources[user.Name] = make(map[string]*Resource)
207 208 209 210
	b.passwords[user.Name] = pwhash.Encrypt(pw)
	if rpw != "" {
		b.recoveryPasswords[user.Name] = pwhash.Encrypt(rpw)
	}
ale's avatar
ale committed
211
	for _, r := range user.Resources {
ale's avatar
ale committed
212
		b.resources[r.ID.String()] = r
ale's avatar
ale committed
213 214 215
	}
}

216 217
func createFakeBackend() *fakeBackend {
	fb := &fakeBackend{
ale's avatar
ale committed
218 219
		users:                make(map[string]*User),
		resources:            make(map[string]*Resource),
220
		passwords:            make(map[string]string),
221
		recoveryPasswords:    make(map[string]string),
222 223
		appSpecificPasswords: make(map[string][]*AppSpecificPasswordInfo),
		encryptionKeys:       make(map[string][]*UserEncryptionKey),
224
	}
ale's avatar
ale committed
225
	fb.addUser(&User{
226
		Name:   testUser,
ale's avatar
ale committed
227 228 229
		Status: UserStatusActive,
		Shard:  "1",
		UID:    4242,
ale's avatar
ale committed
230 231
		Resources: []*Resource{
			{
232 233
				ID:     makeResourceID(testUser, ResourceTypeEmail, testUser),
				Name:   testUser,
ale's avatar
ale committed
234
				Type:   ResourceTypeEmail,
ale's avatar
ale committed
235
				Status: ResourceStatusActive,
ale's avatar
ale committed
236
				Shard:  "1",
ale's avatar
ale committed
237 238 239 240 241
				Email: &Email{
					Maildir: "example.com/testuser",
				},
			},
			{
242
				ID:     makeResourceID(testUser, ResourceTypeDAV, "dav1"),
ale's avatar
ale committed
243
				Name:   "dav1",
ale's avatar
ale committed
244
				Type:   ResourceTypeDAV,
ale's avatar
ale committed
245 246
				Status: ResourceStatusActive,
				DAV: &WebDAV{
247
					UID:     4242,
ale's avatar
ale committed
248 249
					Homedir: "/home/dav1",
				},
ale's avatar
ale committed
250 251
			},
		},
252
	}, "password", "recoverypassword")
253 254 255
	return fb
}

ale's avatar
ale committed
256 257
func testConfig() *Config {
	var c Config
258 259
	c.Validation.ForbiddenUsernames = []string{"root"}
	c.Validation.AvailableDomains = map[string][]string{
ale's avatar
ale committed
260 261
		ResourceTypeEmail:       []string{"example.com"},
		ResourceTypeMailingList: []string{"example.com"},
ale's avatar
ale committed
262
	}
ale's avatar
ale committed
263 264 265
	c.SSO.Domain = "mydomain"
	c.SSO.Service = "service/"
	c.SSO.AdminGroup = testAdminGroupName
ale's avatar
ale committed
266 267 268 269 270 271 272 273
	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
274 275 276
	return &c
}

277
func testService(admin string) *AccountService {
ale's avatar
ale committed
278
	be := createFakeBackend()
279
	svc, _ := newAccountServiceWithSSO(be, testConfig(), &fakeValidator{admin})
280
	return svc
ale's avatar
ale committed
281 282
}

ale's avatar
ale committed
283
func getUser(t testing.TB, svc *AccountService, username string) *User {
284
	req := &GetUserRequest{
285 286
		UserRequestBase: UserRequestBase{
			RequestBase: RequestBase{
287
				SSO: testUser,
288
			},
289
			Username: testUser,
290 291
		},
	}
292
	resp, err := svc.Handle(context.TODO(), req)
293 294 295
	if err != nil {
		t.Fatal(err)
	}
ale's avatar
ale committed
296 297 298 299 300 301
	return resp.(*User)
}

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

302 303
	user := getUser(t, svc, testUser)
	if user.Name != testUser {
ale's avatar
ale committed
304
		t.Fatalf("bad response: %+v", user)
305 306 307
	}
}

308 309 310 311 312
func TestService_GetUser_ResourceGroups(t *testing.T) {
	fb := createFakeBackend()
	svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})

	fb.addUser(&User{
ale's avatar
ale committed
313 314
		Name:   "testuser2",
		Status: UserStatusActive,
315 316
		Resources: []*Resource{
			{
ale's avatar
ale committed
317 318
				ID:   makeResourceID("testuser2", ResourceTypeDAV, "dav1"),
				Type: ResourceTypeDAV,
319 320 321 322 323 324
				Name: "dav1",
				DAV: &WebDAV{
					Homedir: "/home/users/investici.org/dav1",
				},
			},
			{
ale's avatar
ale committed
325 326
				ID:   makeResourceID("testuser2", ResourceTypeDAV, "dav1-domain2"),
				Type: ResourceTypeDAV,
327 328 329 330 331 332
				Name: "dav1-domain2",
				DAV: &WebDAV{
					Homedir: "/home/users/investici.org/dav1/html-domain2.com/subdir",
				},
			},
			{
ale's avatar
ale committed
333 334
				ID:   makeResourceID("testuser2", ResourceTypeDomain, "domain1.com"),
				Type: ResourceTypeDomain,
335 336 337 338 339 340
				Name: "domain1.com",
				Website: &Website{
					DocumentRoot: "/home/users/investici.org/dav1/html-domain1.com",
				},
			},
			{
ale's avatar
ale committed
341 342
				ID:   makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
				Type: ResourceTypeDomain,
343 344 345 346 347 348
				Name: "domain2.com",
				Website: &Website{
					DocumentRoot: "/home/users/investici.org/dav1/html-domain2.com",
				},
			},
			{
ale's avatar
ale committed
349 350 351
				ID:       makeResourceID("testuser2", ResourceTypeDatabase, "db2"),
				ParentID: makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
				Type:     ResourceTypeDatabase,
352 353 354 355 356 357 358
				Name:     "db2",
				Database: &Database{},
			},
		},
	}, "", "")

	req := &GetUserRequest{
359 360 361 362
		UserRequestBase: UserRequestBase{
			RequestBase: RequestBase{
				SSO: "testuser2",
			},
363 364 365
			Username: "testuser2",
		},
	}
366
	resp, err := svc.Handle(context.TODO(), req)
367 368 369
	if err != nil {
		t.Fatal(err)
	}
370
	user := resp.(*User)
371 372 373

	var grouped []*Resource
	for _, r := range user.Resources {
ale's avatar
ale committed
374
		switch r.Type {
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
		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)
		}
	}

}

395
func TestService_Auth(t *testing.T) {
396
	svc := testService("adminuser")
397 398 399 400 401

	for _, td := range []struct {
		sso        string
		expectedOk bool
	}{
402
		{testUser, true},
403 404 405 406
		{"otheruser", false},
		{"adminuser", true},
	} {
		req := &GetUserRequest{
407 408 409 410
			UserRequestBase: UserRequestBase{
				RequestBase: RequestBase{
					SSO: td.sso,
				},
411
				Username: testUser,
412 413
			},
		}
414
		_, err := svc.Handle(context.TODO(), req)
415 416 417 418 419 420 421 422 423 424 425 426 427 428
		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()
429
	svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})
430

431 432 433 434 435
	testdata := []struct {
		password    string
		newPassword string
		expectedOk  bool
	}{
436 437 438
		// First, fail cur_password authentication.
		{"BADPASS", "new_password", false},

439 440 441 442 443 444
		// 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},
445
	}
446 447 448
	for _, td := range testdata {
		req := &ChangeUserPasswordRequest{
			PrivilegedRequestBase: PrivilegedRequestBase{
449 450
				UserRequestBase: UserRequestBase{
					RequestBase: RequestBase{
451
						SSO: testUser,
452
					},
453
					Username: testUser,
454 455 456 457 458
				},
				CurPassword: td.password,
			},
			Password: td.newPassword,
		}
459
		_, err := svc.Handle(context.TODO(), req)
460 461 462 463 464
		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)
		}
465 466
	}

467
	if _, ok := fb.passwords[testUser]; !ok {
ale's avatar
ale committed
468 469
		t.Error("password was not set on the backend")
	}
470
	// if len(fb.encryptionKeys[testUser]) != 1 {
471 472
	// 	t.Errorf("no encryption keys were set")
	// }
473
}
474 475 476 477 478

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

485
// 	user, _ := getUserOrDie(ctx, tx, testUser)
486 487 488 489 490 491 492 493 494

// 	// 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)
// 	}
495
// 	if n := len(fb.encryptionKeys[testUser]); n != 1 {
496 497 498 499 500 501 502 503 504 505 506
// 		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
507 508 509

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

ale's avatar
ale committed
512
	// Find the resource ID.
513
	user := getUser(t, svc, testUser)
ale's avatar
ale committed
514 515
	emailID := user.GetSingleResourceByType(ResourceTypeEmail).ID

ale's avatar
ale committed
516 517 518 519 520 521 522 523 524 525 526 527
	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{
528
				RequestBase: RequestBase{
529
					SSO: testUser,
530
				},
ale's avatar
ale committed
531
				ResourceID: emailID,
ale's avatar
ale committed
532 533 534
			},
			Addr: td.addr,
		}
535
		_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
536 537 538 539 540 541 542
		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)
		}
	}
}
543

ale's avatar
ale committed
544
func TestService_CreateResource(t *testing.T) {
545
	svc := testService("admin")
546

547
	req := &CreateResourcesRequest{
548 549 550
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
551 552
			},
		},
553
		Username: testUser,
554 555
		Resources: []*Resource{
			&Resource{
ale's avatar
ale committed
556
				Type:          ResourceTypeDAV,
ale's avatar
ale committed
557
				Name:          "dav2",
558 559 560
				Status:        ResourceStatusActive,
				Shard:         "host2",
				OriginalShard: "host2",
ale's avatar
ale committed
561 562
				DAV: &WebDAV{
					Homedir: "/home/dav2",
563 564 565
				},
			},
		},
566 567 568
	}

	// The request should succeed the first time around.
ale's avatar
ale committed
569
	obj, err := svc.Handle(context.TODO(), req)
570 571 572
	if err != nil {
		t.Fatal("CreateResources", err)
	}
ale's avatar
ale committed
573 574 575 576 577 578 579 580 581 582
	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!")
	}
583 584

	// The object already exists, so the same request should fail now.
585
	_, err = svc.Handle(context.TODO(), req)
586 587 588
	if err == nil {
		t.Fatal("creating a duplicate resource did not fail")
	}
589
}
ale's avatar
ale committed
590

ale's avatar
ale committed
591
func TestService_CreateResource_List(t *testing.T) {
592
	svc := testService("admin")
ale's avatar
ale committed
593 594 595

	// A list is an example of a user-less (global) resource.
	req := &CreateResourcesRequest{
596 597 598
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
599 600
			},
		},
601
		Username: testUser,
ale's avatar
ale committed
602 603
		Resources: []*Resource{
			&Resource{
ale's avatar
ale committed
604
				Type:          ResourceTypeMailingList,
ale's avatar
ale committed
605 606 607 608 609
				Name:          "list@example.com",
				Status:        ResourceStatusActive,
				Shard:         "host2",
				OriginalShard: "host2",
				List: &MailingList{
610
					Admins: []string{testUser},
ale's avatar
ale committed
611 612 613 614 615 616
				},
			},
		},
	}

	// The request should succeed.
617
	_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
618 619 620 621 622
	if err != nil {
		t.Fatal("CreateResources", err)
	}
}

ale's avatar
ale committed
623
func TestService_CreateUser(t *testing.T) {
624
	svc := testService("admin")
ale's avatar
ale committed
625

ale's avatar
ale committed
626
	//emailResourceID := NewResourceID(ResourceTypeEmail, "testuser2@example.com", "testuser2@example.com")
ale's avatar
ale committed
627
	req := &CreateUserRequest{
628 629 630 631 632
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
				SSO: "admin",
			},
		},
ale's avatar
ale committed
633 634 635 636
		User: &User{
			Name: "testuser2@example.com",
			Resources: []*Resource{
				&Resource{
ale's avatar
ale committed
637
					Type: ResourceTypeEmail,
ale's avatar
ale committed
638 639 640 641
					Name: "testuser2@example.com",
					//Status:        ResourceStatusActive,
					//Shard:         "host2",
					//OriginalShard: "host2",
ale's avatar
ale committed
642 643
					Email: &Email{
						Maildir: "example.com/testuser2",
ale's avatar
ale committed
644 645 646 647 648 649 650
					},
				},
			},
		},
	}

	// The request should succeed the first time around.
651
	resp, err := svc.Handle(context.Background(), req)
ale's avatar
ale committed
652 653 654
	if err != nil {
		t.Fatal("CreateResources", err)
	}
655 656 657
	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
658
	}
ale's avatar
ale committed
659

660 661
	// Hit the database to verify that the object has been created.
	tx, _ := svc.backend.NewTransaction()
ale's avatar
ale committed
662 663 664 665 666 667
	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
668
	resource := user.GetSingleResourceByType(ResourceTypeEmail)
ale's avatar
ale committed
669
	if resource == nil {
ale's avatar
ale committed
670
		t.Fatalf("no email resource in user %+v", user)
ale's avatar
ale committed
671 672 673 674
	}
	if resource.Shard == "" {
		t.Fatalf("resource shard is unset: %+v", resource)
	}
ale's avatar
ale committed
675
}
ale's avatar
ale committed
676 677

func TestService_CreateUser_FailIfNotAdmin(t *testing.T) {
678
	svc := testService("admin")
ale's avatar
ale committed
679 680

	req := &CreateUserRequest{
681 682
		AdminRequestBase: AdminRequestBase{
			RequestBase: RequestBase{
683
				SSO: testUser,
684 685
			},
		},
ale's avatar
ale committed
686 687 688 689
		User: &User{
			Name: "testuser2@example.com",
			Resources: []*Resource{
				&Resource{
ale's avatar
ale committed
690
					Type:          ResourceTypeEmail,
ale's avatar
ale committed
691 692 693 694 695 696 697 698 699 700 701 702 703
					Name:          "testuser2@example.com",
					Status:        ResourceStatusActive,
					Shard:         "host2",
					OriginalShard: "host2",
					Email: &Email{
						Maildir: "example.com/testuser2",
					},
				},
			},
		},
	}

	// The request should succeed the first time around.
704
	_, err := svc.Handle(context.TODO(), req)
ale's avatar
ale committed
705
	if err == nil {
706
		t.Fatal("unauthorized CreateResources did not fail")
ale's avatar
ale committed
707 708
	}
}
709 710

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

713
	user := getUser(t, svc, testUser)
ale's avatar
ale committed
714
	id := user.GetSingleResourceByType(ResourceTypeDAV).ID
715 716
	req := &ResetResourcePasswordRequest{
		ResourceRequestBase: ResourceRequestBase{
717
			RequestBase: RequestBase{
718
				SSO: testUser,
719
			},
720 721 722
			ResourceID: id,
		},
	}
723
	resp, err := svc.Handle(context.TODO(), req)
724 725 726
	if err != nil {
		t.Fatal("ResetResourcePassword", err)
	}
727 728 729
	rresp := resp.(*ResetResourcePasswordResponse)
	if len(rresp.Password) < 10 {
		t.Fatalf("short password: %q", rresp.Password)
730
	}
731
	storedPw, ok := svc.backend.(*fakeBackend).passwords[id.String()]
732 733 734
	if !ok {
		t.Fatal("resource password was not actually set on the backend")
	}
735
	if storedPw == rresp.Password {
736 737 738
		t.Fatal("oops, it appears that the password was stored in cleartext on the backend")
	}
}
739

740 741 742 743
// func TestService_Recovery(t *testing.T) {
// 	svc := testService("")

// 	// Bad recovery response.
744
// 	_, err := svc.RecoverPassword(context.Background(), tx, &AccountRecoveryRequest{
745
// 		Username:         testUser,
746 747 748 749 750 751 752 753
// 		RecoveryPassword: "BADPASS",
// 		Password:         "new_password",
// 	})
// 	if err == nil {
// 		t.Fatal("oops, recovered account with bad password")
// 	}

// 	// Successful account recovery.
754
// 	_, err = svc.RecoverPassword(context.Background(), tx, &AccountRecoveryRequest{
755
// 		Username:         testUser,
756 757 758 759 760 761 762
// 		RecoveryPassword: "recoverypassword",
// 		Password:         "new_password",
// 	})
// 	if err != nil {
// 		t.Fatalf("RecoverPassword failed: %v", err)
// 	}
// }