Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ai3
accountserver
Commits
fa4ce752
Commit
fa4ce752
authored
Nov 17, 2018
by
ale
Browse files
Increase integration test coverage, split it into multiple files
parent
78d08eef
Changes
6
Hide whitespace changes
Inline
Side-by-side
integrationtest/account_mgmt_test.go
0 → 100644
View file @
fa4ce752
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
)
}
integrationtest/create_resources_test.go
0 → 100644
View file @
fa4ce752
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
)
}
}
}
integrationtest/create_user_test.go
0 → 100644
View file @
fa4ce752
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
)
}
}
}
integrationtest/email_alias_test.go
0 → 100644
View file @
fa4ce752
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
)
}
}
}
integrationtest/integration_test.go
View file @
fa4ce752
...
...
@@ -94,8 +94,6 @@ func (c *testClient) request(uri string, req, out interface{}) error {
return
fmt
.
Errorf
(
"unexpected content-type %s"
,
resp
.
Header
.
Get
(
"Content-Type"
))
}
//log.Printf("response:\n%s\n", string(data))
if
out
==
nil
{
return
nil
}
...
...
@@ -111,6 +109,7 @@ func startServiceWithConfig(t testing.TB, svcConfig as.Config) (func(), as.Backe
"testdata/base.ldif"
,
"testdata/test1.ldif"
,
"testdata/test2.ldif"
,
"testdata/test3.ldif"
,
},
})
...
...
@@ -165,441 +164,6 @@ func startService(t testing.TB) (func(), as.Backend, *testClient) {
return
startServiceWithConfig
(
t
,
as
.
Config
{})
}
// 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
},