Commit 28476a76 authored by ale's avatar ale

Move code around to improve readability

parent ab294a25
......@@ -14,15 +14,9 @@ import (
"github.com/tstranex/u2f"
)
// Some considerations about the model design:
//
// * Every user has a unique name (email)
//
// * Every resource has a unique ID, of the form <type>:<id>, so that
// resource IDs are unique to their separate namespaces
// User information, public: includes data *about* credentials, but
// not the credentials themselves.
// not the credentials themselves. Every user has a unique
// identifier, which may be an email address.
type User struct {
// Name of the user. Also its email.
Name string `json:"name"`
......@@ -54,6 +48,50 @@ type User struct {
Resources []*Resource `json:"resources,omitempty"`
}
// GetResourceByID returns the resource with the specified ID, or nil
// if not found.
func (u *User) GetResourceByID(id ResourceID) *Resource {
for _, r := range u.Resources {
if r.ID.Equal(id) {
return r
}
}
return nil
}
// GetResourcesByType returns all resources with the specified type.
func (u *User) GetResourcesByType(resourceType string) []*Resource {
var out []*Resource
for _, r := range u.Resources {
if r.ID.Type() == resourceType {
out = append(out, r)
}
}
return out
}
// GetSingleResourceByType returns a single resource of the specified
// type. If there are none, returns nil.
func (u *User) GetSingleResourceByType(resourceType string) *Resource {
for _, r := range u.Resources {
if r.ID.Type() == resourceType {
return r
}
}
return nil
}
// GetResourcesByGroup returns all resources belonging to the specified group.
func (u *User) GetResourcesByGroup(group string) []*Resource {
var out []*Resource
for _, r := range u.Resources {
if r.Group == group {
out = append(out, r)
}
}
return out
}
// RawUser extends User with private information (as stored in the
// database) that we have a direct use for.
//
......@@ -63,6 +101,8 @@ type User struct {
// mechanisms. In any case both the database and the underlying User
// object are kept in sync.
//
// The separation between User and RawUser makes it easier to prevent
// private data from being served over the API.
type RawUser struct {
User
......@@ -95,10 +135,10 @@ func (u *RawUser) disable2FA(ctx context.Context, tx TX) error {
}
u.Has2FA = false
return u.deleteAllApplicationSpecificPasswords(ctx, tx)
}
// Disable OTP.
func (u *RawUser) disableOTP(ctx context.Context, tx TX) error {
if u.HasOTP {
if err := tx.DeleteUserTOTPSecret(ctx, &u.User); err != nil {
......@@ -109,6 +149,7 @@ func (u *RawUser) disableOTP(ctx context.Context, tx TX) error {
return u.check2FAState(ctx, tx)
}
// Enable OTP with the specified secret. Overwrites the current one, if set.
func (u *RawUser) setTOTPSecret(ctx context.Context, tx TX, totpSecret string) error {
if err := tx.SetUserTOTPSecret(ctx, &u.User, totpSecret); err != nil {
return err
......@@ -118,6 +159,7 @@ func (u *RawUser) setTOTPSecret(ctx context.Context, tx TX, totpSecret string) e
return nil
}
// Update the list of U2F registrations for the user. The list may be empty.
func (u *RawUser) setU2FRegistrations(ctx context.Context, tx TX, regs []*U2FRegistration) error {
u.U2FRegistrations = regs
if err := tx.UpdateUser(ctx, &u.User); err != nil {
......@@ -126,6 +168,9 @@ func (u *RawUser) setU2FRegistrations(ctx context.Context, tx TX, regs []*U2FReg
return u.check2FAState(ctx, tx)
}
// Whenever one of OTP or U2F is modified, we'd like to check if it was the
// last 2FA method available: in that case, 2FA has been disabled and we also
// want to clear all application-specific passwords.
func (u *RawUser) check2FAState(ctx context.Context, tx TX) error {
if u.HasOTP || len(u.U2FRegistrations) > 0 {
u.Has2FA = true
......@@ -137,6 +182,8 @@ func (u *RawUser) check2FAState(ctx context.Context, tx TX) error {
return u.deleteAllApplicationSpecificPasswords(ctx, tx)
}
// Set the primary password for the user. When encryption keys are present,
// requires a valid unlockPassword.
func (u *RawUser) setPrimaryPassword(ctx context.Context, tx TX, unlockPassword, password string) error {
if u.HasEncryptionKeys {
l, err := u.Keys.add(UserEncryptionKeyMainID, unlockPassword, password)
......@@ -154,6 +201,8 @@ func (u *RawUser) setPrimaryPassword(ctx context.Context, tx TX, unlockPassword,
return tx.SetUserPassword(ctx, &u.User, enc)
}
// 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 {
if u.HasEncryptionKeys {
l, err := u.Keys.add(UserEncryptionKeyRecoveryID, unlockPassword, response)
......@@ -171,6 +220,56 @@ func (u *RawUser) setPasswordRecoveryHint(ctx context.Context, tx TX, unlockPass
return tx.SetPasswordRecoveryHint(ctx, &u.User, hint, enc)
}
// Initialize encryption keys for this user, given the primary authentication
// password. If encryption keys are already present, they are discarded. All
// secondary authentication tokens are cleared.
func (u *RawUser) initEncryption(ctx context.Context, tx TX, password string) error {
// Disable all secondary credentials, as we only have the
// primary password to initialize encryption so all other
// credentials would not be able to unlock the keys.
for _, asp := range u.AppSpecificPasswords {
if err := tx.DeleteApplicationSpecificPassword(ctx, &u.User, asp.ID); err != nil {
return err
}
}
if u.PasswordRecoveryHint != "" {
if err := tx.DeletePasswordRecoveryHint(ctx, &u.User); err != nil {
return err
}
u.PasswordRecoveryHint = ""
}
// Initialize a new key storage with the given primary password.
pub, keys, err := newEncryptionKeys(password)
if err != nil {
return err
}
if err := tx.SetUserEncryptionPublicKey(ctx, &u.User, pub); err != nil {
return err
}
u.Keys = keys
u.HasEncryptionKeys = true
return tx.SetUserEncryptionKeys(ctx, &u.User, keys)
}
// Reset the primary password for the user. When encryption keys are present,
// this will disable all other secondary authentication mechanisms (including
// recovery), as keys would be unreadable otherwise.
func (u *RawUser) resetPassword(ctx context.Context, tx TX, password string) error {
// If a user has associated encryption keys, we need to
// disable all secondary authentication credentials as we are
// going to wipe the existing keys clean.
if u.HasEncryptionKeys {
if err := u.initEncryption(ctx, tx, password); err != nil {
return err
}
}
enc := pwhash.Encrypt(password)
return tx.SetUserPassword(ctx, &u.User, enc)
}
// Add a new application-specific password.
func (u *RawUser) addApplicationSpecificPassword(ctx context.Context, tx TX, unlockPassword, password string, asp *AppSpecificPasswordInfo) error {
if u.HasEncryptionKeys {
l, err := u.Keys.add(aspKeyID(asp.ID), unlockPassword, password)
......@@ -187,6 +286,7 @@ func (u *RawUser) addApplicationSpecificPassword(ctx context.Context, tx TX, unl
return tx.SetApplicationSpecificPassword(ctx, &u.User, asp, enc)
}
// Delete an existing application-specific password.
func (u *RawUser) deleteApplicationSpecificPassword(ctx context.Context, tx TX, aspID string) error {
if u.HasEncryptionKeys {
u.Keys = u.Keys.deleteByID(aspKeyID(aspID))
......@@ -217,49 +317,8 @@ func (u *RawUser) deleteAllApplicationSpecificPasswords(ctx context.Context, tx
return nil
}
func (u *RawUser) initEncryption(ctx context.Context, tx TX, password string) error {
// Disable all secondary credentials, as we only have the
// primary password to initialize encryption so all other
// credentials would not be able to unlock the keys.
for _, asp := range u.AppSpecificPasswords {
if err := tx.DeleteApplicationSpecificPassword(ctx, &u.User, asp.ID); err != nil {
return err
}
}
if u.PasswordRecoveryHint != "" {
if err := tx.DeletePasswordRecoveryHint(ctx, &u.User); err != nil {
return err
}
u.PasswordRecoveryHint = ""
}
// Initialize a new key storage with the given primary password.
pub, keys, err := newEncryptionKeys(password)
if err != nil {
return err
}
if err := tx.SetUserEncryptionPublicKey(ctx, &u.User, pub); err != nil {
return err
}
u.Keys = keys
u.HasEncryptionKeys = true
return tx.SetUserEncryptionKeys(ctx, &u.User, keys)
}
func (u *RawUser) resetPassword(ctx context.Context, tx TX, password string) error {
// If a user has associated encryption keys, we need to
// disable all secondary authentication credentials as we are
// going to wipe the existing keys clean.
if u.HasEncryptionKeys {
if err := u.initEncryption(ctx, tx, password); err != nil {
return err
}
}
enc := pwhash.Encrypt(password)
return tx.SetUserPassword(ctx, &u.User, enc)
}
// A list of encrypted keys, all copies of the same key but encrypted with
// different passwords.
type encryptedKeyList []*UserEncryptionKey
func newEncryptionKeys(encryptionPassword string) ([]byte, encryptedKeyList, error) {
......@@ -280,6 +339,14 @@ func newEncryptionKeys(encryptionPassword string) ([]byte, encryptedKeyList, err
return pub, l, nil
}
func keysToBytes(keys []*UserEncryptionKey) [][]byte {
var rawKeys [][]byte
for _, k := range keys {
rawKeys = append(rawKeys, k.Key)
}
return rawKeys
}
func (l encryptedKeyList) add(keyID, unlockPassword, encryptionPassword string) (encryptedKeyList, error) {
decrypted, err := userenckey.Decrypt(keysToBytes(l), []byte(unlockPassword))
if err != nil {
......@@ -307,50 +374,6 @@ func (l encryptedKeyList) deleteByID(keyID string) encryptedKeyList {
return out
}
// GetResourceByID returns the resource with the specified ID, or nil
// if not found.
func (u *User) GetResourceByID(id ResourceID) *Resource {
for _, r := range u.Resources {
if r.ID.Equal(id) {
return r
}
}
return nil
}
// GetResourcesByType returns all resources with the specified type.
func (u *User) GetResourcesByType(resourceType string) []*Resource {
var out []*Resource
for _, r := range u.Resources {
if r.ID.Type() == resourceType {
out = append(out, r)
}
}
return out
}
// GetSingleResourceByType returns a single resource of the specified
// type. If there are none, returns nil.
func (u *User) GetSingleResourceByType(resourceType string) *Resource {
for _, r := range u.Resources {
if r.ID.Type() == resourceType {
return r
}
}
return nil
}
// GetResourcesByGroup returns all resources belonging to the specified group.
func (u *User) GetResourcesByGroup(group string) []*Resource {
var out []*Resource
for _, r := range u.Resources {
if r.Group == group {
out = append(out, r)
}
}
return out
}
// AppSpecificPasswordInfo stores public information about an
// app-specific password.
type AppSpecificPasswordInfo struct {
......@@ -697,11 +720,3 @@ func (r *U2FRegistration) UnmarshalJSON(data []byte) error {
r.Registration = new(u2f.Registration)
return r.Registration.UnmarshalBinary(b)
}
func keysToBytes(keys []*UserEncryptionKey) [][]byte {
var rawKeys [][]byte
for _, k := range keys {
rawKeys = append(rawKeys, k.Key)
}
return rawKeys
}
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