Select Git revision
-
ale authored
The app is now self-hosted instead of relying on the static-content standalone server, so we can eventually add dynamic code for graph serving. The static content serving has improved, with more consistent cache header management, as well as the capability of serving pre-compressed content. Additional code to implement the generation of dependency (flow) graphs in dot format was added (not hooked to the HTTP server yet).
ale authoredThe app is now self-hosted instead of relying on the static-content standalone server, so we can eventually add dynamic code for graph serving. The static content serving has improved, with more consistent cache header management, as well as the capability of serving pre-compressed content. Additional code to implement the generation of dependency (flow) graphs in dot format was added (not hooked to the HTTP server yet).
actions_test.go 23.45 KiB
package accountserver
import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"testing"
ct "git.autistici.org/ai3/go-common/ldap/compositetypes"
"git.autistici.org/ai3/go-common/pwhash"
"git.autistici.org/id/auth"
sso "git.autistici.org/id/go-sso"
)
const testUser = "testuser@example.com"
type fakeBackend struct {
users map[string]*User
resources map[string]*Resource
passwords map[string]string
recoveryPasswords map[string]string
resourcePasswords map[string]string
appSpecificPasswords map[string][]*AppSpecificPasswordInfo
encryptionKeys map[string][]*ct.EncryptedKey
}
func (b *fakeBackend) NewTransaction() (TX, error) {
return b, nil
}
func (b *fakeBackend) Commit(_ context.Context) error {
return nil
}
func (b *fakeBackend) NextUID(_ context.Context) (int, error) {
return 42, nil
}
func (b *fakeBackend) CanAccessResource(_ context.Context, username string, rsrc *Resource) bool {
owner := strings.Split(string(rsrc.ID), "/")[0]
return owner == username
}
func (b *fakeBackend) GetUser(_ context.Context, username string) (*RawUser, error) {
u, ok := b.users[username]
if !ok {
return nil, errors.New("user not found in fake backend")
}
return &RawUser{
User: *u,
Password: b.passwords[username],
RecoveryPassword: b.recoveryPasswords[username],
Keys: b.encryptionKeys[username],
}, nil
}
func (b *fakeBackend) SearchUser(_ context.Context, pattern string, limit int) ([]string, error) {
var out []string
for username := range b.users {
if strings.HasPrefix(username, pattern) {
out = append(out, username)
}
}
return out, nil
}
func (b *fakeBackend) UpdateUser(_ context.Context, user *User) error {
b.users[user.Name] = user
return nil
}
func (b *fakeBackend) CreateUser(_ context.Context, user *User) (*User, error) {
for _, r := range user.Resources {
r.ID = makeResourceID(user.Name, r.Type, r.Name)
}
b.users[user.Name] = user
return user, nil
}
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
}
func (b *fakeBackend) FindResource(_ context.Context, req FindResourceRequest) (*RawResource, error) {
for id, r := range b.resources {
if r.Type == req.Type && r.Name == req.Name {
owner := strings.Split(id, "/")[0]
return &RawResource{Resource: *r, Owner: owner}, nil
}
}
return nil, nil
}
func (b *fakeBackend) SearchResource(_ context.Context, pattern string, limit int) ([]*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
}
func (b *fakeBackend) UpdateResource(_ context.Context, r *Resource) error {
b.resources[r.ID.String()] = r
return nil
}
func makeResourceID(owner, rtype, rname string) ResourceID {
return ResourceID(fmt.Sprintf("%s/%s/%s", owner, rtype, rname))
}
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
}
func (b *fakeBackend) SetUserPassword(_ context.Context, user *User, password string) error {
b.passwords[user.Name] = password
return nil
}
func (b *fakeBackend) SetAccountRecoveryHint(_ context.Context, user *User, hint, response string) error {
b.users[user.Name].AccountRecoveryHint = hint
b.recoveryPasswords[user.Name] = response
return nil
}
func (b *fakeBackend) DeleteAccountRecoveryHint(_ context.Context, user *User) error {
b.users[user.Name].AccountRecoveryHint = ""
delete(b.recoveryPasswords, user.Name)
return nil
}
func (b *fakeBackend) SetResourcePassword(_ context.Context, r *Resource, password string) error {
b.resourcePasswords[r.ID.String()] = password
return nil
}
func (b *fakeBackend) GetUserEncryptionKeys(_ context.Context, user *User) ([]*ct.EncryptedKey, error) {
return b.encryptionKeys[user.Name], nil
}
func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys []*ct.EncryptedKey) error {
b.encryptionKeys[user.Name] = keys
b.users[user.Name].HasEncryptionKeys = true
return nil
}
func (b *fakeBackend) SetUserEncryptionPublicKey(_ context.Context, user *User, pub []byte) error {
return nil
}
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
}
func (b *fakeBackend) SetUserTOTPSecret(_ context.Context, user *User, secret string) error {
return nil
}
func (b *fakeBackend) DeleteUserTOTPSecret(_ context.Context, user *User) error {
return nil
}
func (b *fakeBackend) HasAnyResource(_ context.Context, rsrcs []FindResourceRequest) (bool, error) {
for _, fr := range rsrcs {
for _, r := range b.resources {
if r.Type == fr.Type && r.Name == fr.Name {
return true, nil
}
}
}
return false, nil
}
// Make the fakeBackend match the auth.Client interface.
func (b *fakeBackend) Authenticate(_ context.Context, req *auth.Request) (*auth.Response, error) {
var pw string
var ok bool
switch req.Service {
case defaultUserAuthService:
pw, ok = b.passwords[req.Username]
case defaultAccountRecoveryAuthService:
pw, ok = b.recoveryPasswords[req.Username]
default:
return nil, errors.New("unknown service")
}
if !ok {
return nil, errors.New("no such user")
}
if !pwhash.ComparePassword(pw, string(req.Password)) {
return &auth.Response{Status: auth.StatusError}, nil
}
return &auth.Response{Status: auth.StatusOK}, nil
}
const testAdminGroupName = "admins"
// Fake SSO validator: the sso ticket username is just the ticket
// itself. The only invalid value is the empty string.
type fakeValidator struct {
adminUser string
}
func (v *fakeValidator) Validate(tkt, nonce, service string, _ []string) (*sso.Ticket, error) {
if tkt == "" {
return nil, errors.New("empty sso ticket")
}
var groups []string
if tkt == v.adminUser {
groups = []string{testAdminGroupName}
}
return &sso.Ticket{
User: tkt,
Service: service,
Domain: "test",
Groups: groups,
}, nil
}
func (b *fakeBackend) addUser(user *User, pw, rpw string) {
b.users[user.Name] = user
//b.resources[user.Name] = make(map[string]*Resource)
b.passwords[user.Name] = pwhash.Encrypt(pw)
if rpw != "" {
b.recoveryPasswords[user.Name] = pwhash.Encrypt(rpw)
}
for _, r := range user.Resources {
b.resources[r.ID.String()] = r
}
}
func createFakeBackend() *fakeBackend {
fb := &fakeBackend{
users: make(map[string]*User),
resources: make(map[string]*Resource),
passwords: make(map[string]string),
recoveryPasswords: make(map[string]string),
resourcePasswords: make(map[string]string),
appSpecificPasswords: make(map[string][]*AppSpecificPasswordInfo),
encryptionKeys: make(map[string][]*ct.EncryptedKey),
}
fb.addUser(&User{
Name: testUser,
Status: UserStatusActive,
Shard: "host1",
UID: 4242,
Resources: []*Resource{
{
ID: makeResourceID(testUser, ResourceTypeEmail, testUser),
Name: testUser,
Type: ResourceTypeEmail,
Status: ResourceStatusActive,
Shard: "host1",
OriginalShard: "host1",
Email: &Email{
Maildir: "example.com/testuser",
},
},
{
ID: makeResourceID(testUser, ResourceTypeDAV, "dav1"),
Name: "dav1",
Type: ResourceTypeDAV,
Status: ResourceStatusActive,
DAV: &WebDAV{
UID: 4242,
Homedir: "/home/dav1",
},
},
},
}, "password", "recoverypassword")
return fb
}
func testConfig() *Config {
var c Config
c.Validation.ForbiddenUsernames = []string{"root"}
c.Validation.AvailableDomains = map[string][]string{
ResourceTypeEmail: []string{"example.com"},
ResourceTypeMailingList: []string{"example.com"},
}
c.SSO.Domain = "mydomain"
c.SSO.Service = "service/"
c.SSO.AdminGroup = testAdminGroupName
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
return &c
}
func testService(admin string) *AccountService {
be := createFakeBackend()
svc, _ := newAccountServiceInternal(be, testConfig(), &fakeValidator{admin}, be)
return svc
}
func getUser(t testing.TB, svc *AccountService, username string) *User {
req := &GetUserRequest{
UserRequestBase: UserRequestBase{
RequestBase: RequestBase{
SSO: testUser,
},
Username: testUser,
},
IncludeInactive: true,
}
resp, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal(err)
}
return resp.(*User)
}
func TestService_GetUser(t *testing.T) {
svc := testService("")
user := getUser(t, svc, testUser)
if user.Name != testUser {
t.Fatalf("bad response: %+v", user)
}
}
func TestService_GetUser_ResourceGroups(t *testing.T) {
fb := createFakeBackend()
svc, _ := newAccountServiceInternal(fb, testConfig(), &fakeValidator{}, nil)
fb.addUser(&User{
Name: "testuser2",
Status: UserStatusActive,
Resources: []*Resource{
{
ID: makeResourceID("testuser2", ResourceTypeDAV, "dav1"),
Type: ResourceTypeDAV,
Name: "dav1",
DAV: &WebDAV{
Homedir: "/home/users/investici.org/dav1",
},
},
{
ID: makeResourceID("testuser2", ResourceTypeDAV, "dav1-domain2"),
Type: ResourceTypeDAV,
Name: "dav1-domain2",
DAV: &WebDAV{
Homedir: "/home/users/investici.org/dav1/html-domain2.com/subdir",
},
},
{
ID: makeResourceID("testuser2", ResourceTypeDomain, "domain1.com"),
Type: ResourceTypeDomain,
Name: "domain1.com",
Website: &Website{
DocumentRoot: "/home/users/investici.org/dav1/html-domain1.com",
},
},
{
ID: makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
Type: ResourceTypeDomain,
Name: "domain2.com",
Website: &Website{
DocumentRoot: "/home/users/investici.org/dav1/html-domain2.com",
},
},
{
ID: makeResourceID("testuser2", ResourceTypeDatabase, "db2"),
ParentID: makeResourceID("testuser2", ResourceTypeDomain, "domain2.com"),
Type: ResourceTypeDatabase,
Name: "db2",
Database: &Database{},
},
},
}, "", "")
req := &GetUserRequest{
UserRequestBase: UserRequestBase{
RequestBase: RequestBase{
SSO: "testuser2",
},
Username: "testuser2",
},
}
resp, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal(err)
}
user := resp.(*User)
var grouped []*Resource
for _, r := range user.Resources {
switch r.Type {
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)
}
}
}
func TestService_Auth(t *testing.T) {
svc := testService("adminuser")
for _, td := range []struct {
sso string
expectedOk bool
}{
{testUser, true},
{"otheruser", false},
{"adminuser", true},
} {
req := &GetUserRequest{
UserRequestBase: UserRequestBase{
RequestBase: RequestBase{
SSO: td.sso,
},
Username: testUser,
},
}
_, err := svc.Handle(context.TODO(), req)
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()
svc, _ := newAccountServiceInternal(fb, testConfig(), &fakeValidator{}, fb)
testdata := []struct {
password string
newPassword string
expectedOk bool
}{
// First, fail cur_password authentication.
{"BADPASS", "new_password", false},
// 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},
}
for _, td := range testdata {
req := &ChangeUserPasswordRequest{
PrivilegedRequestBase: PrivilegedRequestBase{
UserRequestBase: UserRequestBase{
RequestBase: RequestBase{
SSO: testUser,
},
Username: testUser,
},
CurPassword: td.password,
},
Password: td.newPassword,
}
_, err := svc.Handle(context.TODO(), req)
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)
}
}
if _, ok := fb.passwords[testUser]; !ok {
t.Error("password was not set on the backend")
}
// if len(fb.encryptionKeys[testUser]) != 1 {
// t.Errorf("no encryption keys were set")
// }
}
// Lower level test that basically corresponds to the same operations
// as TestService_ChangePassword above, but exercises the
// initializeUserEncryptionKeys / updateUserEncryptionKeys code path
// directly.
// func TestService_EncryptionKeys(t *testing.T) {
// fb := createFakeBackend()
// svc, _ := newAccountServiceWithSSO(fb, testConfig(), &fakeValidator{})
// tx, _ := fb.NewTransaction()
// ctx := context.Background()
// user, _ := getUserOrDie(ctx, tx, testUser)
// // 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)
// }
// if n := len(fb.encryptionKeys[testUser]); n != 1 {
// 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)
// }
// }
// Try adding aliases to the email resource.
func TestService_AddEmailAlias(t *testing.T) {
svc := testService("")
// Find the resource ID.
user := getUser(t, svc, testUser)
emailID := user.GetSingleResourceByType(ResourceTypeEmail).ID
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{
RequestBase: RequestBase{
SSO: testUser,
},
ResourceID: emailID,
},
Addr: td.addr,
}
_, err := svc.Handle(context.TODO(), req)
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)
}
}
}
func TestService_CreateResource(t *testing.T) {
svc := testService("admin")
req := &CreateResourcesRequest{
AdminRequestBase: AdminRequestBase{
RequestBase: RequestBase{
SSO: "admin",
},
},
Username: testUser,
Resources: []*Resource{
&Resource{
Type: ResourceTypeDAV,
Name: "dav2",
Status: ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
DAV: &WebDAV{
Homedir: "/home/dav2",
},
},
},
}
// The request should succeed the first time around.
obj, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal("CreateResources", err)
}
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!")
}
// The object already exists, so the same request should fail now.
_, err = svc.Handle(context.TODO(), req)
if err == nil {
t.Fatal("creating a duplicate resource did not fail")
}
}
func TestService_CreateResource_List(t *testing.T) {
svc := testService("admin")
// A list is an example of a user-less (global) resource.
req := &CreateResourcesRequest{
AdminRequestBase: AdminRequestBase{
RequestBase: RequestBase{
SSO: "admin",
},
},
Username: testUser,
Resources: []*Resource{
&Resource{
Type: ResourceTypeMailingList,
Name: "list@example.com",
Status: ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
List: &MailingList{
Admins: []string{testUser},
},
},
},
}
// The request should succeed.
_, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal("CreateResources", err)
}
}
func TestService_UpdateResource(t *testing.T) {
svc := testService("admin")
// Find the resource, modify a copy of it.
user := getUser(t, svc, testUser)
email := user.GetSingleResourceByType(ResourceTypeEmail).Copy()
email.Email.QuotaLimit = 9000
req := &AdminUpdateResourceRequest{
AdminResourceRequestBase: AdminResourceRequestBase{
ResourceRequestBase: ResourceRequestBase{
RequestBase: RequestBase{
SSO: "admin",
},
ResourceID: email.ID,
},
},
Resource: email,
}
_, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal("UpdateResource", err)
}
}
func TestService_UpdateResource_FailsValidation(t *testing.T) {
svc := testService("admin")
// Find the resource, modify a copy of it, but set a parameter
// that will fail validation (i.e. a bad shard).
user := getUser(t, svc, testUser)
email := user.GetSingleResourceByType(ResourceTypeEmail).Copy()
email.Shard = "invalid-shard"
req := &AdminUpdateResourceRequest{
AdminResourceRequestBase: AdminResourceRequestBase{
ResourceRequestBase: ResourceRequestBase{
RequestBase: RequestBase{
SSO: "admin",
},
ResourceID: email.ID,
},
},
Resource: email,
}
_, err := svc.Handle(context.TODO(), req)
if err == nil {
t.Fatal("UpdateResource() accepted an invalid 'shard' attribute")
}
}
func TestService_CreateUser(t *testing.T) {
svc := testService("admin")
//emailResourceID := NewResourceID(ResourceTypeEmail, "testuser2@example.com", "testuser2@example.com")
req := &CreateUserRequest{
AdminRequestBase: AdminRequestBase{
RequestBase: RequestBase{
SSO: "admin",
},
},
User: &User{
Name: "testuser2@example.com",
Resources: []*Resource{
&Resource{
Type: ResourceTypeEmail,
Name: "testuser2@example.com",
//Status: ResourceStatusActive,
//Shard: "host2",
//OriginalShard: "host2",
Email: &Email{
Maildir: "example.com/testuser2",
},
},
},
},
}
// The request should succeed the first time around.
resp, err := svc.Handle(context.Background(), req)
if err != nil {
t.Fatal("CreateResources", err)
}
cresp := resp.(*CreateUserResponse)
if cresp.User.Name != "testuser2@example.com" {
t.Fatalf("unexpected user in response: got %s, expected testuser2", cresp.User.Name)
}
// Hit the database to verify that the object has been created.
tx, _ := svc.backend.NewTransaction()
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.
resource := user.GetSingleResourceByType(ResourceTypeEmail)
if resource == nil {
t.Fatalf("no email resource in user %+v", user)
}
if resource.Shard == "" {
t.Fatalf("resource shard is unset: %+v", resource)
}
}
func TestService_CreateUser_FailIfNotAdmin(t *testing.T) {
svc := testService("admin")
req := &CreateUserRequest{
AdminRequestBase: AdminRequestBase{
RequestBase: RequestBase{
SSO: testUser,
},
},
User: &User{
Name: "testuser2@example.com",
Resources: []*Resource{
&Resource{
Type: ResourceTypeEmail,
Name: "testuser2@example.com",
Status: ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Email: &Email{
Maildir: "example.com/testuser2",
},
},
},
},
}
// The request should succeed the first time around.
_, err := svc.Handle(context.TODO(), req)
if err == nil {
t.Fatal("unauthorized CreateResources did not fail")
}
}
func TestService_ResetResourcePassword(t *testing.T) {
svc := testService("")
user := getUser(t, svc, testUser)
id := user.GetSingleResourceByType(ResourceTypeDAV).ID
req := &ResetResourcePasswordRequest{
ResourceRequestBase: ResourceRequestBase{
RequestBase: RequestBase{
SSO: testUser,
},
ResourceID: id,
},
}
resp, err := svc.Handle(context.TODO(), req)
if err != nil {
t.Fatal("ResetResourcePassword", err)
}
rresp := resp.(*ResetResourcePasswordResponse)
if len(rresp.Password) < 10 {
t.Fatalf("short password: %q", rresp.Password)
}
storedPw, ok := svc.backend.(*fakeBackend).resourcePasswords[id.String()]
if !ok {
t.Fatal("resource password was not actually set on the backend")
}
if storedPw == rresp.Password {
t.Fatal("oops, it appears that the password was stored in cleartext on the backend")
}
}
func TestService_AccountRecovery(t *testing.T) {
svc := testService("")
// 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)
}
}
func TestService_DisableUser(t *testing.T) {
svc := testService("")
req := &DisableUserRequest{
UserRequestBase: UserRequestBase{
RequestBase: RequestBase{
SSO: testUser,
},
Username: testUser,
},
}
_, err := svc.Handle(context.Background(), req)
if err != nil {
t.Fatal("DisableUser", err)
}
// Verify that everything has been disabled.
user := getUser(t, svc, testUser)
if user.Status != UserStatusInactive {
t.Fatalf("user status is '%s', expected inactive", user.Status)
}
for _, rsrc := range user.Resources {
if rsrc.Status != ResourceStatusInactive {
t.Fatalf("resource %s status is '%s', expected inactive", rsrc.ID, rsrc.Status)
}
}
}