Commit fa4ce752 authored by ale's avatar ale

Increase integration test coverage, split it into multiple files

parent 78d08eef
package integrationtest
import (
"testing"
as "git.autistici.org/ai3/accountserver"
)
// Verify that authentication on GetUser works as expected:
// - users can fetch their own data but not other users'
// - admins can read everything.
func TestIntegration_GetUser_Auth(t *testing.T) {
stop, _, c := startService(t)
defer stop()
testdata := []struct {
authUser string
authGroup string
expectedOk bool
}{
{"uno@investici.org", "", true},
{"uno@investici.org", "users", true},
{"due@investici.org", "users", false},
{testAdminUser, testAdminGroup, true},
}
for _, td := range testdata {
var user as.User
var groups []string
if td.authGroup != "" {
groups = append(groups, td.authGroup)
}
err := c.request("/api/user/get", &as.GetUserRequest{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(td.authUser, groups...),
},
Username: "uno@investici.org",
},
}, &user)
if td.expectedOk && err != nil {
t.Errorf("access error for user %s: expected ok, got error: %v", td.authUser, err)
} else if !td.expectedOk && err == nil {
t.Errorf("access error for user %s: expected error, got ok", td.authUser)
}
}
}
// Verify that a user can't change someone else's password.
func TestIntegration_ChangeUserPassword_AuthFail(t *testing.T) {
stop, _, c := startService(t)
defer stop()
err := c.request("/api/user/change_password", &as.ChangeUserPasswordRequest{
PrivilegedRequestBase: as.PrivilegedRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("due@investici.org"),
},
Username: "uno@investici.org",
},
CurPassword: "password",
},
Password: "new_password",
}, nil)
if err == nil {
t.Fatal("ChangePassword for another user succeeded")
}
}
// Verify various attempts at changing the password (user has no encryption keys).
func TestIntegration_ChangeUserPassword(t *testing.T) {
runChangeUserPasswordTest(t, "uno@investici.org", as.Config{})
}
// Verify various attempts at changing the password (user has no encryption keys).
func TestIntegration_ChangeUserPassword_WithOpportunisticEncryption(t *testing.T) {
user := runChangeUserPasswordTest(t, "uno@investici.org", as.Config{
EnableOpportunisticEncryption: true,
})
if !user.HasEncryptionKeys {
t.Fatal("encryption keys were not created on password change")
}
}
// Verify various attempts at changing the password (user with encryption keys).
func TestIntegration_ChangeUserPassword_WithEncryptionKeys(t *testing.T) {
runChangeUserPasswordTest(t, "due@investici.org", as.Config{})
}
func runChangeUserPasswordTest(t *testing.T, username string, cfg as.Config) *as.RawUser {
stop, be, c := startServiceWithConfig(t, cfg)
defer stop()
testdata := []struct {
password string
newPassword string
expectedOk bool
}{
// 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", "password", true},
}
for _, td := range testdata {
err := c.request("/api/user/change_password", &as.ChangeUserPasswordRequest{
PrivilegedRequestBase: as.PrivilegedRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(username),
},
Username: username,
},
CurPassword: td.password,
},
Password: td.newPassword,
}, nil)
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)
}
}
// The password that should work at the end of the above
// series of checks is still "password".
return checkUserInvariants(t, be, username, "password")
}
func TestIntegration_AccountRecovery(t *testing.T) {
runAccountRecoveryTest(t, "uno@investici.org")
}
func TestIntegration_AccountRecovery_WithEncryptionKeys(t *testing.T) {
user := runAccountRecoveryTest(t, "due@investici.org")
if !user.HasEncryptionKeys {
t.Fatalf("encryption keys not enabled after account recovery")
}
}
func runAccountRecoveryTest(t *testing.T, username string) *as.RawUser {
stop, be, c := startService(t)
defer stop()
hint := "secret code?"
secondaryPw := "open sesame!"
err := c.request("/api/user/set_account_recovery_hint", &as.SetAccountRecoveryHintRequest{
PrivilegedRequestBase: as.PrivilegedRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(username),
},
Username: username,
},
CurPassword: "password",
},
Hint: hint,
Response: secondaryPw,
}, nil)
if err != nil {
t.Fatalf("SetAccountRecoveryHint failed: %v", err)
}
// The first request just fetches the recovery hint.
var resp as.AccountRecoveryResponse
err = c.request("/api/recover_account", &as.AccountRecoveryRequest{
Username: username,
}, &resp)
if err != nil {
t.Fatalf("AccountRecovery (hint only) failed: %v", err)
}
if resp.Hint != hint {
t.Fatalf("bad AccountRecovery hint, got '%s' expected '%s'", resp.Hint, hint)
}
// Now recover the account and set a new password.
newPw := "new password"
err = c.request("/api/recover_account", &as.AccountRecoveryRequest{
Username: username,
RecoveryPassword: secondaryPw,
Password: newPw,
}, &resp)
if err != nil {
t.Fatalf("AccountRecovery failed: %v", err)
}
return checkUserInvariants(t, be, username, newPw)
}
package integrationtest
import (
"testing"
as "git.autistici.org/ai3/accountserver"
)
func TestIntegration_CreateResources(t *testing.T) {
stop, _, c := startService(t)
defer stop()
testdata := []struct {
resource *as.Resource
expectedOk bool
}{
// Create a domain resource.
{
&as.Resource{
Name: "example2.com",
Type: as.ResourceTypeDomain,
Status: as.ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Website: &as.Website{
URL: "https://example2.com",
DocumentRoot: "/home/users/investici.org/uno/html-example2.com",
AcceptMail: true,
},
},
true,
},
// Duplicate of the above request, should fail due to conflict.
{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example2.com",
Status: as.ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Website: &as.Website{
URL: "https://example2.com",
DocumentRoot: "/home/users/investici.org/uno/html-example2.com",
},
},
false,
},
// Empty document root will be fixed by templating.
{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example3.com",
Status: as.ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Website: &as.Website{},
},
true,
},
// Malformed resource metadata (name fails validation).
{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example$.com",
Status: as.ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Website: &as.Website{
URL: "https://example$.com",
DocumentRoot: "/home/users/investici.org/uno/html-example3.com",
},
},
false,
},
// Bad shard.
{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example4.com",
Status: as.ResourceStatusActive,
Shard: "zebra",
OriginalShard: "zebra",
Website: &as.Website{
URL: "https://example4.com",
DocumentRoot: "/home/users/investici.org/uno/html-example4.com",
},
},
false,
},
// The document root has no associated DAV account.
{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example5.com",
Status: as.ResourceStatusActive,
Shard: "host2",
OriginalShard: "host2",
Website: &as.Website{
URL: "https://example5.com",
DocumentRoot: "/home/users/investici.org/nonexisting",
},
},
false,
},
}
for _, td := range testdata {
err := c.request("/api/resource/create", &as.CreateResourcesRequest{
AdminUserRequestBase: as.AdminUserRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(testAdminUser),
},
Username: "uno@investici.org",
},
},
Resources: []*as.Resource{td.resource},
}, nil)
if err == nil && !td.expectedOk {
t.Errorf("CreateResource(%s) should have failed but didn't", td.resource.ID)
} else if err != nil && td.expectedOk {
t.Errorf("CreateResource(%s) failed: %v", td.resource.ID, err)
}
}
}
func TestIntegration_CreateMultipleResources(t *testing.T) {
stop, _, c := startService(t)
defer stop()
testdata := []struct {
name string
username string
resources []*as.Resource
expectedOk bool
}{
{
// The create request is very bare, most values will be
// filled in by the server using resource
// templates. Note that we specify client-side IDs and
// use them in ParentIDs to specify nesting.
"site_with_db",
"uno@investici.org",
[]*as.Resource{
&as.Resource{
ID: as.ResourceID("example3_site"),
Type: as.ResourceTypeDomain,
Name: "example3.com",
},
&as.Resource{
Type: as.ResourceTypeDAV,
Name: "example3dav",
},
&as.Resource{
Type: as.ResourceTypeDatabase,
ParentID: as.ResourceID("example3_site"),
Name: "example3",
},
},
true,
},
{
// An attempt to create resources without client-side
// IDs. It should fail the database validation.
"site_with_db_no_nesting",
"uno@investici.org",
[]*as.Resource{
&as.Resource{
Type: as.ResourceTypeDomain,
Name: "example4.com",
},
&as.Resource{
Type: as.ResourceTypeDAV,
Name: "example4dav",
},
&as.Resource{
Type: as.ResourceTypeDatabase,
Name: "example4",
},
},
false,
},
{
// An attempt to create a website without an associated
// DAV account, it should pick up the DAV account that
// already exists for user #1.
"site_with_db_no_dav_picks_up_existing_account",
"uno@investici.org",
[]*as.Resource{
&as.Resource{
ID: as.ResourceID("example5_site"),
Type: as.ResourceTypeDomain,
Name: "example5.com",
},
&as.Resource{
ParentID: as.ResourceID("example5_site"),
Type: as.ResourceTypeDatabase,
Name: "example5",
},
},
true,
},
{
// An attempt to create a website without an associated
// DAV account, for a user without an existing DAV
// account. Should fail.
"site_with_db_no_dav_and_no_existing_account",
"tre@investici.org",
[]*as.Resource{
&as.Resource{
ID: as.ResourceID("example6_site"),
Type: as.ResourceTypeDomain,
Name: "example6.com",
},
&as.Resource{
ParentID: as.ResourceID("example6_site"),
Type: as.ResourceTypeDatabase,
Name: "example6",
},
},
false,
},
}
for _, td := range testdata {
req := &as.CreateResourcesRequest{
AdminUserRequestBase: as.AdminUserRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(testAdminUser),
},
Username: td.username,
},
},
Resources: td.resources,
}
err := c.request("/api/resource/create", req, nil)
if err == nil && !td.expectedOk {
t.Errorf("'%s' should have failed but didn't", td.name)
} else if err != nil && td.expectedOk {
t.Errorf("'%s' failed: %v", td.name, err)
}
}
}
package integrationtest
import (
"testing"
as "git.autistici.org/ai3/accountserver"
)
func TestIntegration_CreateUser(t *testing.T) {
stop, be, c := startService(t)
defer stop()
testdata := []struct {
name string
user *as.User
expectedOk bool
}{
{
// Bare user creation request, most values will be filled
// in by the server using resource templates.
"email_only",
&as.User{
Name: "newuser1@example.com",
Resources: []*as.Resource{
&as.Resource{
Type: as.ResourceTypeEmail,
Name: "newuser1@example.com",
},
},
},
true,
},
{
// User creation request without any resources at all.
"no_resources",
&as.User{
Name: "newuser2@example.com",
},
false,
},
{
// User creation request without an email account.
"no_email",
&as.User{
Name: "newuser3@example.com",
Resources: []*as.Resource{
&as.Resource{
Type: as.ResourceTypeDAV,
Name: "davuser3",
},
},
},
false,
},
}
for _, td := range testdata {
var resp as.CreateUserResponse
err := c.request("/api/user/create", &as.CreateUserRequest{
AdminRequestBase: as.AdminRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(testAdminUser),
},
},
User: td.user,
}, &resp)
if err == nil && !td.expectedOk {
t.Errorf("'%s' should have failed but didn't", td.name)
} else if err != nil && td.expectedOk {
t.Errorf("'%s' failed: %v", td.name, err)
} else if td.expectedOk {
if resp.Password == "" {
t.Fatalf("no password in response (%v)", resp)
}
// Verify that the new password works.
checkUserInvariants(t, be, td.user.Name, resp.Password)
}
}
}
package integrationtest
import (
"testing"
as "git.autistici.org/ai3/accountserver"
)
func TestIntegration_AddEmailAlias(t *testing.T) {
stop, _, c := startService(t)
defer stop()
// The following are basically checks for email validation.
testdata := []struct {
addr string
expectedOk bool
}{
{"alias@example.com", false}, // already taken
{"alias@otherdomain.com", false}, // bad domain
{"x@example.com", false}, // too short
{"........@example.com", false}, // malformed
{"due@investici.org", false}, // already taken
{"forbidden@example.com", false}, // reserved
{"alias1@example.com", true},
{"alias2@example.com", true},
{"alias3@example.com", true},
{"alias4@example.com", true},
{"alias5@example.com", false}, // limit of 5 aliases reached
}
// Cheat a bit, we know the DN.
rsrcID := as.ResourceID("mail=uno@investici.org,uid=uno@investici.org,ou=People,dc=example,dc=com")
for _, td := range testdata {
err := c.request("/api/resource/email/add_alias", &as.AddEmailAliasRequest{
ResourceRequestBase: as.ResourceRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
},
ResourceID: rsrcID,
},
Addr: td.addr,
}, nil)
if err == nil && !td.expectedOk {
t.Errorf("AddEmailAlias(%s) should have failed but didn't", td.addr)
} else if err != nil && td.expectedOk {
t.Errorf("AddEmailAlias(%s) failed: %v", td.addr, err)
}
}
}
This diff is collapsed.
package integrationtest
import (
"testing"
as "git.autistici.org/ai3/accountserver"
)
func TestIntegration_MoveResource(t *testing.T) {
stop, be, c := startService(t)
defer stop()
var resp as.MoveResourceResponse
err := c.request("/api/resource/move", &as.MoveResourceRequest{
AdminResourceRequestBase: as.AdminResourceRequestBase{
ResourceRequestBase: as.ResourceRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(testAdminUser),
},
ResourceID: as.ResourceID("mail=uno@investici.org,uid=uno@investici.org,ou=People,dc=example,dc=com"),
},
},
Shard: "host1",
}, &resp)
if err != nil {
t.Fatalf("MoveResource: %v", err)
}
checkUserInvariants(t, be, "uno@investici.org", "password")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment