Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
ai3
accountserver
Commits
28476a76
Commit
28476a76
authored
Nov 09, 2018
by
ale
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move code around to improve readability
parent
ab294a25
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
119 additions
and
104 deletions
+119
-104
types.go
types.go
+119
-104
No files found.
types.go
View file @
28476a76
...
...
@@ -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
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment