Commit ec82ed43 authored by ale's avatar ale

Rename PasswordRecovery to AccountRecovery

Referring to the account is clearer. Also add account recovery
integration tests, and a test fixture with encryption keys.
parent da2e38da
......@@ -4,6 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"git.autistici.org/ai3/go-common/pwhash"
......@@ -162,7 +163,7 @@ func (r *PrivilegedRequestBase) Authorize(rctx *RequestContext) error {
}
// TODO: call out to the auth-server
if !pwhash.ComparePassword(rctx.User.Password, r.CurPassword) {
return fmt.Errorf("bad password (%s vs %s)", rctx.User.Password, r.CurPassword)
return errors.New("bad password")
}
return nil
}
......
......@@ -118,7 +118,7 @@ func (r *CreateUserRequest) applyTemplate(rctx *RequestContext) error {
r.User.Has2FA = false
r.User.HasOTP = false
r.User.HasEncryptionKeys = true // set to true so that resetPassword will create keys.
r.User.PasswordRecoveryHint = ""
r.User.AccountRecoveryHint = ""
r.User.AppSpecificPasswords = nil
r.User.U2FRegistrations = nil
if r.User.Lang == "" {
......
......@@ -73,14 +73,14 @@ func (b *fakeBackend) SetUserPassword(_ context.Context, user *User, password st
return nil
}
func (b *fakeBackend) SetPasswordRecoveryHint(_ context.Context, user *User, hint, response string) error {
b.users[user.Name].PasswordRecoveryHint = hint
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) DeletePasswordRecoveryHint(_ context.Context, user *User) error {
b.users[user.Name].PasswordRecoveryHint = ""
func (b *fakeBackend) DeleteAccountRecoveryHint(_ context.Context, user *User) error {
b.users[user.Name].AccountRecoveryHint = ""
delete(b.recoveryPasswords, user.Name)
return nil
}
......@@ -667,7 +667,7 @@ func TestService_ResetResourcePassword(t *testing.T) {
// svc := testService("")
// // Bad recovery response.
// _, err := svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
// _, err := svc.RecoverPassword(context.Background(), tx, &AccountRecoveryRequest{
// Username: "testuser",
// RecoveryPassword: "BADPASS",
// Password: "new_password",
......@@ -677,7 +677,7 @@ func TestService_ResetResourcePassword(t *testing.T) {
// }
// // Successful account recovery.
// _, err = svc.RecoverPassword(context.Background(), tx, &PasswordRecoveryRequest{
// _, err = svc.RecoverPassword(context.Background(), tx, &AccountRecoveryRequest{
// Username: "testuser",
// RecoveryPassword: "recoverypassword",
// Password: "new_password",
......
......@@ -49,25 +49,25 @@ func (r *ChangeUserPasswordRequest) Serve(rctx *RequestContext) (interface{}, er
return nil, err
}
// PasswordRecoveryRequest lets users reset their password by providing
// AccountRecoveryRequest lets users reset their password by providing
// secondary credentials, which we authenticate ourselves. It is not
// authenticated with SSO.
//
// Two-factor authentication is disabled on successful recovery.
type PasswordRecoveryRequest struct {
type AccountRecoveryRequest struct {
Username string `json:"username"`
RecoveryPassword string `json:"recovery_password"`
Password string `json:"password"`
// TODO: add DeviceInfo
}
// PasswordRecoveryResponse is the response type for PasswordRecoveryRequest.
type PasswordRecoveryResponse struct {
// AccountRecoveryResponse is the response type for AccountRecoveryRequest.
type AccountRecoveryResponse struct {
Hint string `json:"hint,optional"`
}
// Sanitize the request.
func (r *PasswordRecoveryRequest) Sanitize() {
func (r *AccountRecoveryRequest) Sanitize() {
if r.RecoveryPassword != "" {
r.RecoveryPassword = sanitizedValue
}
......@@ -77,16 +77,19 @@ func (r *PasswordRecoveryRequest) Sanitize() {
}
// Validate the request.
func (r *PasswordRecoveryRequest) Validate(rctx *RequestContext) error {
if err := rctx.fieldValidators.password(rctx.Context, r.Password); err != nil {
return newValidationError(nil, "password", err.Error())
func (r *AccountRecoveryRequest) Validate(rctx *RequestContext) error {
// Only validate the password if attempting recovery.
if r.RecoveryPassword != "" {
if err := rctx.fieldValidators.password(rctx.Context, r.Password); err != nil {
return newValidationError(nil, "password", err.Error())
}
}
return nil
}
// PopulateContext extracts information from the request and stores it
// into the RequestContext.
func (r *PasswordRecoveryRequest) PopulateContext(rctx *RequestContext) error {
func (r *AccountRecoveryRequest) PopulateContext(rctx *RequestContext) error {
user, err := getUserOrDie(rctx.Context, rctx.TX, r.Username)
if err != nil {
return err
......@@ -96,7 +99,7 @@ func (r *PasswordRecoveryRequest) PopulateContext(rctx *RequestContext) error {
}
// Authorize the request.
func (r *PasswordRecoveryRequest) Authorize(rctx *RequestContext) error {
func (r *AccountRecoveryRequest) Authorize(rctx *RequestContext) error {
// Anyone can request the hint (rate-limit above this layer).
if r.RecoveryPassword == "" {
return nil
......@@ -112,9 +115,9 @@ func (r *PasswordRecoveryRequest) Authorize(rctx *RequestContext) error {
}
// Serve the request.
func (r *PasswordRecoveryRequest) Serve(rctx *RequestContext) (interface{}, error) {
resp := PasswordRecoveryResponse{
Hint: rctx.User.PasswordRecoveryHint,
func (r *AccountRecoveryRequest) Serve(rctx *RequestContext) (interface{}, error) {
resp := AccountRecoveryResponse{
Hint: rctx.User.AccountRecoveryHint,
}
// Only attempt to authenticate if the recovery password is
......@@ -172,16 +175,16 @@ func (r *ResetPasswordRequest) Serve(rctx *RequestContext) (interface{}, error)
return nil, nil
}
// SetPasswordRecoveryHintRequest lets users set the password recovery hint
// SetAccountRecoveryHintRequest lets users set the password recovery hint
// and expected response (secondary password).
type SetPasswordRecoveryHintRequest struct {
type SetAccountRecoveryHintRequest struct {
PrivilegedRequestBase
Hint string `json:"recovery_hint"`
Response string `json:"recovery_response"`
}
// Validate the request.
func (r *SetPasswordRecoveryHintRequest) Validate(rctx *RequestContext) error {
func (r *SetAccountRecoveryHintRequest) Validate(rctx *RequestContext) error {
var err *validationError
if r.Hint == "" {
err = newValidationError(err, "recovery_hint", "mandatory field")
......@@ -189,12 +192,12 @@ func (r *SetPasswordRecoveryHintRequest) Validate(rctx *RequestContext) error {
if verr := rctx.fieldValidators.password(rctx.Context, r.Response); verr != nil {
err = newValidationError(err, "recovery_response", verr.Error())
}
return err
return err.orNil()
}
// Serve the request.
func (r *SetPasswordRecoveryHintRequest) Serve(rctx *RequestContext) (interface{}, error) {
return nil, rctx.User.setPasswordRecoveryHint(rctx.Context, rctx.TX, r.CurPassword, r.Hint, r.Response)
func (r *SetAccountRecoveryHintRequest) Serve(rctx *RequestContext) (interface{}, error) {
return nil, rctx.User.setAccountRecoveryHint(rctx.Context, rctx.TX, r.CurPassword, r.Hint, r.Response)
}
// CreateApplicationSpecificPasswordRequest lets users create their own ASPs.
......
......@@ -120,16 +120,17 @@ func newUser(entry *ldap.Entry) (*as.RawUser, error) {
//
// The case of password recovery attributes is more complex:
// the current schema has those on email=, but we'd like to
// move them to uid=, so we currently have to support both.
// move them to uid=, so we currently have to support both
// (but the uid= one takes precedence).
uidNumber, _ := strconv.Atoi(entry.GetAttributeValue(uidNumberLDAPAttr)) // nolint
user := &as.RawUser{
User: as.User{
Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue(preferredLanguageLDAPAttr),
UID: uidNumber,
PasswordRecoveryHint: entry.GetAttributeValue(recoveryHintLDAPAttr),
U2FRegistrations: decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
HasOTP: entry.GetAttributeValue(totpSecretLDAPAttr) != "",
Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue(preferredLanguageLDAPAttr),
UID: uidNumber,
AccountRecoveryHint: entry.GetAttributeValue(recoveryHintLDAPAttr),
U2FRegistrations: decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
HasOTP: entry.GetAttributeValue(totpSecretLDAPAttr) != "",
},
// Remove the legacy LDAP {crypt} prefix on old passwords.
Password: strings.TrimPrefix(entry.GetAttributeValue(passwordLDAPAttr), "{crypt}"),
......@@ -261,13 +262,19 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.RawUser,
}
for _, entry := range result.Entries {
// Some user-level attributes are actually stored on the email
// object, a shortcoming of the legacy A/I database model. Set
// them on the main User object.
// Some user-level attributes are actually stored on
// the email object, which is desired in some cases,
// but in others is a shortcoming of the legacy A/I
// database model. Set them on the main User
// object. For the latter, attributes on the main User
// object take precedence.
if isObjectClass(entry, "virtualMailUser") {
if s := entry.GetAttributeValue(recoveryHintLDAPAttr); s != "" {
user.PasswordRecoveryHint = s
if user.AccountRecoveryHint == "" {
if s := entry.GetAttributeValue(recoveryHintLDAPAttr); s != "" {
user.AccountRecoveryHint = s
}
}
user.AppSpecificPasswords = getASPInfo(decodeAppSpecificPasswords(entry.GetAttributeValues(aspLDAPAttr)))
user.Keys = decodeUserEncryptionKeys(
entry.GetAttributeValues(storagePrivateKeyLDAPAttr))
......@@ -297,7 +304,7 @@ func (tx *backendTX) SetUserPassword(ctx context.Context, user *as.User, encrypt
return
}
func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *as.User, hint, response string) error {
func (tx *backendTX) SetAccountRecoveryHint(ctx context.Context, user *as.User, hint, response string) error {
// Write the password recovery attributes on the uid= object,
// as per the new schema.
dn := tx.getUserDN(user)
......@@ -306,7 +313,7 @@ func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *as.User,
return nil
}
func (tx *backendTX) DeletePasswordRecoveryHint(ctx context.Context, user *as.User) error {
func (tx *backendTX) DeleteAccountRecoveryHint(ctx context.Context, user *as.User) error {
// Write the password recovery attributes on the uid= object,
// as per the new schema.
dn := tx.getUserDN(user)
......
......@@ -85,8 +85,12 @@ func newValidationError(err *validationError, field, msg string) *validationErro
func (v *validationError) Error() string {
var p []string
for f, l := range v.fields {
p = append(p, fmt.Sprintf("%s: %s", f, strings.Join(l, ", ")))
if v.fields != nil {
for f, l := range v.fields {
p = append(p, fmt.Sprintf("%s: %s", f, strings.Join(l, ", ")))
}
} else {
p = append(p, "INVALID ERROR")
}
return fmt.Sprintf("request validation error: %s", strings.Join(p, "; "))
}
......@@ -95,3 +99,10 @@ func (v *validationError) JSON() []byte {
data, _ := json.Marshal(v.fields) // nolint
return data
}
func (v *validationError) orNil() error {
if v == nil {
return nil
}
return v
}
......@@ -2,6 +2,7 @@ package integrationtest
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
......@@ -17,6 +18,8 @@ import (
"git.autistici.org/ai3/accountserver/backend"
"git.autistici.org/ai3/accountserver/ldaptest"
"git.autistici.org/ai3/accountserver/server"
"git.autistici.org/ai3/go-common/pwhash"
"git.autistici.org/ai3/go-common/userenckey"
"git.autistici.org/id/go-sso"
"golang.org/x/crypto/ed25519"
)
......@@ -99,12 +102,16 @@ func (c *testClient) request(uri string, req, out interface{}) error {
return json.Unmarshal(data, out)
}
func startService(t testing.TB) (func(), *testClient) {
func startService(t testing.TB) (func(), as.Backend, *testClient) {
stop := ldaptest.StartServer(t, &ldaptest.Config{
Dir: "../ldaptest",
Port: testLDAPPort,
Base: "dc=example,dc=com",
LDIFs: []string{"testdata/base.ldif", "testdata/test1.ldif"},
Dir: "../ldaptest",
Port: testLDAPPort,
Base: "dc=example,dc=com",
LDIFs: []string{
"testdata/base.ldif",
"testdata/test1.ldif",
"testdata/test2.ldif",
},
})
be, err := backend.NewLDAPBackend(testLDAPAddr, "cn=manager,dc=example,dc=com", "password", "dc=example,dc=com")
......@@ -151,14 +158,14 @@ func startService(t testing.TB) (func(), *testClient) {
stop()
srv.Close()
ssoStop()
}, c
}, be, c
}
// 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)
stop, _, c := startService(t)
defer stop()
testdata := []struct {
......@@ -194,16 +201,16 @@ func TestIntegration_GetUser_Auth(t *testing.T) {
}
}
// Verify that a user can change their password.
func TestIntegration_ChangeUserPassword(t *testing.T) {
stop, c := startService(t)
// 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("uno@investici.org"),
SSO: c.ssoTicket("due@investici.org"),
},
Username: "uno@investici.org",
},
......@@ -212,14 +219,23 @@ func TestIntegration_ChangeUserPassword(t *testing.T) {
Password: "new_password",
}, nil)
if err != nil {
t.Fatal("ChangePassword", err)
if err == nil {
t.Fatal("ChangePassword for another user succeeded")
}
}
// Verify that changing the password sets user encryption keys.
func TestIntegration_ChangeUserPassword_SetsEncryptionKeys(t *testing.T) {
stop, c := startService(t)
// Verify various attempts at changing the password (user has no encryption keys).
func TestIntegration_ChangeUserPassword(t *testing.T) {
runChangeUserPasswordTest(t, "uno@investici.org")
}
// Verify various attempts at changing the password (user with encryption keys).
func TestIntegration_ChangeUserPassword_WithEncryptionKeys(t *testing.T) {
runChangeUserPasswordTest(t, "due@investici.org")
}
func runChangeUserPasswordTest(t *testing.T, username string) {
stop, _, c := startService(t)
defer stop()
testdata := []struct {
......@@ -239,9 +255,9 @@ func TestIntegration_ChangeUserPassword_SetsEncryptionKeys(t *testing.T) {
PrivilegedRequestBase: as.PrivilegedRequestBase{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
SSO: c.ssoTicket(username),
},
Username: "uno@investici.org",
Username: username,
},
CurPassword: td.password,
},
......@@ -256,7 +272,7 @@ func TestIntegration_ChangeUserPassword_SetsEncryptionKeys(t *testing.T) {
}
func TestIntegration_CreateResource(t *testing.T) {
stop, c := startService(t)
stop, _, c := startService(t)
defer stop()
testdata := []struct {
......@@ -376,7 +392,7 @@ func TestIntegration_CreateResource(t *testing.T) {
}
func TestIntegration_CreateMultipleResources_WithTemplate(t *testing.T) {
stop, c := startService(t)
stop, _, c := startService(t)
defer stop()
// The create request is very bare, most values will be filled
......@@ -407,3 +423,130 @@ func TestIntegration_CreateMultipleResources_WithTemplate(t *testing.T) {
t.Errorf("CreateResources failed: %v", err)
}
}
func TestIntegration_CreateUser(t *testing.T) {
stop, be, c := startService(t)
defer stop()
// The create request is very bare, most values will be filled
// in by the server using resource templates.
var resp as.CreateUserResponse
err := c.request("/api/user/create", &as.CreateUserRequest{
AdminRequestBase: as.AdminRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket(testAdminUser),
},
},
User: &as.User{
Name: "newuser@example.com",
Resources: []*as.Resource{
&as.Resource{
ID: as.NewResourceID(as.ResourceTypeEmail, "newuser@example.com", "newuser@example.com"),
Name: "newuser@example.com",
},
},
},
}, &resp)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if resp.Password == "" {
t.Fatalf("no password in response (%v)", resp)
}
// Verify that the new password works.
checkUserInvariants(t, be, "newuser@example.com", resp.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)
}
// Verify that some user authentication invariants are true. Returns
// the RawUser for further checks.
func checkUserInvariants(t *testing.T, be as.Backend, username, primaryPassword string) *as.RawUser {
tx, _ := be.NewTransaction()
user, err := tx.GetUser(context.Background(), username)
if err != nil {
t.Fatalf("GetUser(%s): %v", username, err)
}
// Verify that the password is correct.
if !pwhash.ComparePassword(user.Password, primaryPassword) {
t.Fatalf("password for user %s is not %s", username, primaryPassword)
}
// Verify that we can successfully encrypt keys.
if user.HasEncryptionKeys {
if _, err := userenckey.Decrypt(keysToBytes(user.Keys), []byte(primaryPassword)); err != nil {
t.Fatalf("password for user %s can't decrypt keys", username)
}
}
return user
}
func keysToBytes(keys []*as.UserEncryptionKey) [][]byte {
var rawKeys [][]byte
for _, k := range keys {
rawKeys = append(rawKeys, k.Key)
}
return rawKeys
}
dn: uid=due@investici.org,ou=People,dc=example,dc=com
cn: due@investici.org
gecos: due@investici.org
gidNumber: 2000
givenName: Private
homeDirectory: /var/empty
loginShell: /bin/false
objectClass: top
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
objectClass: organizationalPerson
objectClass: inetOrgPerson
shadowLastChange: 12345
shadowMax: 99999
shadowWarning: 7
sn: due@investici.org
uid: due@investici.org
uidNumber: 256799
userPassword:: JGEyJDQkMzI3NjgkMSQwZDgyMzU1YjQ0Mzg0M2NmZDY4MjU1MzE4ZTVjYTdiZSRmNTQ0ODkxOTFiNWZlYzk2MDRlNWQ2ODZjMDQxZjJkNTFmOTgxOGY4ZTFmM2E4MDYzY2U3ZTEwMTE3OTc2OGI0
dn: mail=due@investici.org,uid=due@investici.org,ou=People,dc=example,dc=com
creationDate: 01-08-2013
gidNumber: 2000
host: host2
mail: due@investici.org
mailMessageStore: investici.org/due/
objectClass: top
objectClass: virtualMailUser
originalHost: host2
status: active
uidNumber: 256799
userPassword:: JGEyJDQkMzI3NjgkMSQwZDgyMzU1YjQ0Mzg0M2NmZDY4MjU1MzE4ZTVjYTdiZSRmNTQ0ODkxOTFiNWZlYzk2MDRlNWQ2ODZjMDQxZjJkNTFmOTgxOGY4ZTFmM2E4MDYzY2U3ZTEwMTE3OTc2OGI0
storagePublicKey:: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUU0d0VBWUhLb1pJemowQ0FRWUZLNEVFQUNFRE9nQUVKaDFSdW1MTkt3M1dBTXNSMFFpMVRrc2pJSC9udCtwaApGRjZjdmhPZFN3anhsOVhFVVovVzlONWdDcUlqbGl0eFk2VHdSZWlzZThrPQotLS0tLUVORCBQVUJMSUMgS0VZLS0tLS0K
storageEncryptedSecretKey:: bWFpbjoBAQAAAAAQAAAEIKLsDnzqCxHAmVXiSi3WT77YqNDY5sW0les/rC0owl0MegC3frvzAGG7vr6PhYNozksn2Fw4tQbj7G52HeQr3V8R58J3F2CHLdiwLGDMKCNy1hjyCN6rXCp1OQqg4VWtEovumogA4FaJtZS74WnCP2YGcxJy/0Am3U2TlFmx0e0jzuCk9lZ8piX+YKR6c8Qh/bv5vjq2gZ9AO2nh5Q==
dn: ftpname=due,uid=due@investici.org,ou=People,dc=example,dc=com
status: active
givenName: Private
cn: due
objectClass: top
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: ftpAccount
loginShell: /bin/false
userPassword:: JGEyJDQkMzI3NjgkMSQwZDgyMzU1YjQ0Mzg0M2NmZDY4MjU1MzE4ZTVjYTdiZSRmNTQ0ODkxOTFiNWZlYzk2MDRlNWQ2ODZjMDQxZjJkNTFmOTgxOGY4ZTFmM2E4MDYzY2U3ZTEwMTE3OTc2OGI0
shadowWarning: 7
uidNumber: 256799
host: host2
shadowMax: 99999
ftpname: due
gidNumber: 33
gecos: FTP Account for due@investici.org
sn: Private
homeDirectory: /home/users/investici.org/due
uid: due
creationDate: 01-08-2013
shadowLastChange: 12345
originalHost: host2
dn: alias=due,uid=due@investici.org,ou=People,dc=example,dc=com
status: active
parentSite: autistici.org
objectClass: top
objectClass: subSite
alias: due
host: host2
documentRoot: /home/users/investici.org/due/html-due
creationDate: 01-08-2013
originalHost: host2
statsId: 2193
option: php
dn: dbname=due,alias=due,uid=due@investici.org,ou=People,dc=example,dc=com
clearPassword: tae1tei8eir7wae1OZaeXXX
dbname: due
dbuser: due
host: host2
originalHost: host2
mysqlPassword: *7DD66AA8CD1E7687B993732C3A87CFFA43B95E27
objectClass: top
objectClass: dbMysql
status: active
......@@ -34,15 +34,15 @@ func (r *actionRegistry) newRequest(path string) (as.Request, bool) {
return reflect.New(h).Interface().(as.Request), true
}
// AccountServer is the HTTP API interface to AccountService.
type AccountServer struct {
// APIServer is the HTTP API interface to AccountService.
type APIServer struct {
*actionRegistry
service *as.AccountService
}
// New creates a new AccountServer.
func New(service *as.AccountService, backend as.Backend) *AccountServer {
s := &AccountServer{
// New creates a new APIServer.
func New(service *as.AccountService, backend as.Backend) *APIServer {
s := &APIServer{
actionRegistry: newActionRegistry(),
service: service,
}
......@@ -51,7 +51,7 @@ func New(service *as.AccountService, backend as.Backend) *AccountServer {
s.Register("/api/user/create", &as.CreateUserRequest{})
s.Register("/api/user/update", &as.UpdateUserRequest{})
s.Register("/api/user/change_password", &as.ChangeUserPasswordRequest{})
s.Register("/api/user/set_password_recovery_hint", &as.SetPasswordRecoveryHintRequest{})
s.Register("/api/user/set_account_recovery_hint", &as.SetAccountRecoveryHintRequest{})
s.Register("/api/user/enable_otp", &as.EnableOTPRequest{})
s.Register("/api/user/disable_otp", &as.DisableOTPRequest{})
s.Register("/api/user/create_app_specific_password", &as.CreateApplicationSpecificPasswordRequest{})
......@@ -63,14 +63,14 @@ func New(service *as.AccountService, backend as.Backend) *AccountServer {
s.Register("/api/resource/reset_password", &as.ResetPasswordRequest{})
s.Register("/api/resource/email/add_alias", &as.AddEmailAliasRequest{})
s.Register("/api/resource/email/delete_alias", &as.DeleteEmailAliasRequest{})
s.Register("/api/recover_password", &as.PasswordRecoveryRequest{})
s.Register("/api/recover_account", &as.AccountRecoveryRequest{})
return s
}
var emptyResponse struct{}
func (s *AccountServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (s *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Create a new empty request object based on the request
// path, then decode the HTTP request JSON body onto it.
r, ok := s.newRequest(req.URL.Path)
......
......@@ -47,8 +47,8 @@ type TX interface {
UpdateUser(context.Context, *User) error
CreateUser(context.Context, *User) error
SetUserPassword(context.Context, *User, string) error
SetPasswordRecoveryHint(context.Context, *User, string, string) error
DeletePasswordRecoveryHint(context.Context, *User) error
SetAccountRecoveryHint(context.Context, *User, string, string) error
DeleteAccountRecoveryHint(context.Context, *User) error
SetUserEncryptionKeys(context.Context, *User, []*UserEncryptionKey) error
SetUserEncryptionPublicKey(context.Context, *User, []byte) error
SetApplicationSpecificPassword(context.Context, *User, *AppSpecificPasswordInfo, string) error
......
......@@ -39,7 +39,7 @@ type User struct {
// this user. TODO: consider disabling it.
HasEncryptionKeys bool `json:"has_encryption_keys"`
PasswordRecoveryHint string `json:"password_recovery_hint"`
AccountRecoveryHint string `json:"account_recovery_hint"`
AppSpecificPasswords []*AppSpecificPasswordInfo `json:"app_specific_passwords,omitempty"`
......@@ -203,7 +203,7 @@ func (u *RawUser) setPrimaryPassword(ctx context.Context, tx TX, unlockPassword,
// Set the password recovery hint for the user. When encryption keys are
// present, requires a valid unlockPassword.
func (u *RawUser) setPasswordRecoveryHint(ctx context.Context, tx TX, unlockPassword, hint, response string) error {
func (u *RawUser) setAccountRecoveryHint(ctx context.Context, tx TX, unlockPassword, hint, response string) error {
if u.HasEncryptionKeys {
l, err := u.Keys.add(UserEncryptionKeyRecoveryID, unlockPassword, response)
if err != nil {
......@@ -217,7 +217,7 @@ func (u *RawUser) setPasswordRecoveryHint(ctx context.Context, tx TX, unlockPass
enc := pwhash.Encrypt(response)
u.RecoveryPassword = enc
return tx.SetPasswordRecoveryHint(ctx, &u.User, hint, enc)
return tx.SetAccountRecoveryHint(ctx, &u.User, hint, enc)
}
// Initialize encryption keys for this user, given the primary authentication
......@@ -232,11 +232,11 @@ func (u *RawUser) initEncryption(ctx context.Context, tx TX, password string) er
return err
}
}