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
907f30f6
Commit
907f30f6
authored
Nov 14, 2018
by
ale
Browse files
Add last password change timestamp to API
Mapped to the shadowLastChange attribute in LDAP.
parent
8facee8c
Changes
4
Show whitespace changes
Inline
Side-by-side
backend/model.go
View file @
907f30f6
...
...
@@ -6,6 +6,7 @@ import (
"math/rand"
"strconv"
"strings"
"time"
ldaputil
"git.autistici.org/ai3/go-common/ldap"
"github.com/tstranex/u2f"
...
...
@@ -24,6 +25,7 @@ const (
storagePublicKeyLDAPAttr
=
"storagePublicKey"
storagePrivateKeyLDAPAttr
=
"storageEncryptedSecretKey"
passwordLDAPAttr
=
"userPassword"
passwordLastChangeLDAPAttr
=
"shadowLastChange"
u2fRegistrationsLDAPAttr
=
"u2fRegistration"
uidNumberLDAPAttr
=
"uidNumber"
)
...
...
@@ -128,6 +130,7 @@ func newUser(entry *ldap.Entry) (*as.RawUser, error) {
Name
:
entry
.
GetAttributeValue
(
"uid"
),
Lang
:
entry
.
GetAttributeValue
(
preferredLanguageLDAPAttr
),
UID
:
uidNumber
,
LastPasswordChangeStamp
:
decodeShadowTimestamp
(
entry
.
GetAttributeValue
(
passwordLastChangeLDAPAttr
)),
AccountRecoveryHint
:
entry
.
GetAttributeValue
(
recoveryHintLDAPAttr
),
U2FRegistrations
:
decodeU2FRegistrations
(
entry
.
GetAttributeValues
(
u2fRegistrationsLDAPAttr
)),
HasOTP
:
entry
.
GetAttributeValue
(
totpSecretLDAPAttr
)
!=
""
,
...
...
@@ -158,7 +161,7 @@ func userToLDAP(user *as.User) (attrs []ldap.PartialAttribute) {
{
Type
:
"gecos"
,
Vals
:
s2l
(
user
.
Name
)},
{
Type
:
"loginShell"
,
Vals
:
[]
string
{
"/bin/false"
}},
{
Type
:
"homeDirectory"
,
Vals
:
[]
string
{
"/var/empty"
}},
{
Type
:
"shadow
LastChange
"
,
Vals
:
[]
string
{
"12345"
}},
{
Type
:
password
LastChange
LDAPAttr
,
Vals
:
[]
string
{
"12345"
}},
{
Type
:
"shadowWarning"
,
Vals
:
[]
string
{
"7"
}},
{
Type
:
"shadowMax"
,
Vals
:
[]
string
{
"99999"
}},
{
Type
:
preferredLanguageLDAPAttr
,
Vals
:
s2l
(
user
.
Lang
)},
...
...
@@ -294,6 +297,7 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.RawUser,
func
(
tx
*
backendTX
)
SetUserPassword
(
ctx
context
.
Context
,
user
*
as
.
User
,
encryptedPassword
string
)
(
err
error
)
{
dn
:=
tx
.
getUserDN
(
user
)
tx
.
setAttr
(
dn
,
passwordLDAPAttr
,
encryptedPassword
)
tx
.
setAttr
(
dn
,
passwordLastChangeLDAPAttr
,
encodeShadowTimestamp
(
time
.
Now
()))
for
_
,
r
:=
range
user
.
GetResourcesByType
(
as
.
ResourceTypeEmail
)
{
dn
,
err
=
tx
.
backend
.
resources
.
GetDN
(
r
.
ID
)
if
err
!=
nil
{
...
...
@@ -552,3 +556,17 @@ func (tx *backendTX) isUIDAvailable(ctx context.Context, uid int) (bool, error)
}
return
true
,
nil
}
const
oneDay
=
86400
func
encodeShadowTimestamp
(
t
time
.
Time
)
string
{
d
:=
t
.
UTC
()
.
Unix
()
/
oneDay
return
strconv
.
FormatInt
(
d
,
10
)
}
func
decodeShadowTimestamp
(
s
string
)
(
t
time
.
Time
)
{
if
i
,
err
:=
strconv
.
ParseInt
(
s
,
10
,
64
);
err
==
nil
{
t
=
time
.
Unix
(
i
*
oneDay
,
0
)
.
UTC
()
}
return
}
backend/model_test.go
View file @
907f30f6
...
...
@@ -3,6 +3,7 @@ package backend
import
(
"context"
"testing"
"time"
"github.com/go-test/deep"
...
...
@@ -79,10 +80,14 @@ func TestModel_GetUser(t *testing.T) {
defer
stop
()
if
user
.
Name
!=
testUser1
{
t
.
Fatal
f
(
"bad username: expected %s, got %s"
,
testUser1
,
user
.
Name
)
t
.
Error
f
(
"bad username: expected %s, got %s"
,
testUser1
,
user
.
Name
)
}
if
len
(
user
.
Resources
)
!=
5
{
t
.
Fatalf
(
"expected 5 resources, got %d"
,
len
(
user
.
Resources
))
t
.
Errorf
(
"expected 5 resources, got %d"
,
len
(
user
.
Resources
))
}
expectedPwChangeStamp
:=
time
.
Date
(
2018
,
11
,
14
,
0
,
0
,
0
,
0
,
time
.
UTC
)
if
user
.
LastPasswordChangeStamp
!=
expectedPwChangeStamp
{
t
.
Errorf
(
"bad last password change timestamp: expected %s, got %s"
,
expectedPwChangeStamp
,
user
.
LastPasswordChangeStamp
)
}
// Test a specific resource (the database).
...
...
backend/testdata/test1.ldif
View file @
907f30f6
...
...
@@ -16,7 +16,7 @@ sn: Private
homeDirectory: /var/empty
uid: uno@investici.org
givenName: Private
shadowLastChange: 1
2345
shadowLastChange: 1
7849
shadowWarning: 7
preferredLanguage: it
userPassword:: JDYkbXBXN1NkdlE4bnY4UlpsTyRJNGZCV2RVSkV5VWxvR2l1WmdibzI1OVVUWkkyL3JWTlA4N1lT
...
...
@@ -63,7 +63,7 @@ sn: Private
homeDirectory: /home/users/investici.org/uno
uid: uno
creationDate: 01-08-2013
shadowLastChange: 1
2345
shadowLastChange: 1
7849
originalHost: host2
userPassword:: JDYkbXBXN1NkdlE4bnY4UlpsTyRJNGZCV2RVSkV5VWxvR2l1WmdibzI1OVVUWkkyL3JWTlA4N1lT
TjNUTS53YXkyZHZSd1g2YTQ0dVVXZ2tYL1pzbkc4YXdHRFhYVGYwNU1VeE1saWdIMA==
...
...
types.go
View file @
907f30f6
...
...
@@ -27,6 +27,10 @@ type User struct {
// UNIX user id.
UID
int
`json:"uid"`
// Timestamp of last password change. This is serialized as a
// RFC3339 string in JSON.
LastPasswordChangeStamp
time
.
Time
`json:"last_password_change_stamp"`
// Has2FA is true if the user has a second-factor authentication
// mechanism properly set up. In practice, this is the case if either
// HasOTP is true, or len(U2FRegistrations) > 0.
...
...
@@ -36,15 +40,19 @@ type User struct {
HasOTP
bool
`json:"has_otp"`
// HasEncryptionKeys is true if encryption keys are properly set up for
// this user.
TODO: consider disabling it.
// this user.
HasEncryptionKeys
bool
`json:"has_encryption_keys"`
// The recovery hint for this account (empty if unset).
AccountRecoveryHint
string
`json:"account_recovery_hint"`
// List of application-specific passwords (metadata only).
AppSpecificPasswords
[]
*
AppSpecificPasswordInfo
`json:"app_specific_passwords,omitempty"`
// List of U2F registrations.
U2FRegistrations
[]
*
U2FRegistration
`json:"u2f_registrations,omitempty"`
// All the resources owned by this user.
Resources
[]
*
Resource
`json:"resources,omitempty"`
}
...
...
ale
@ale
mentioned in issue
#4 (closed)
·
Nov 14, 2018
mentioned in issue
#4 (closed)
mentioned in issue #4
Toggle commit list
Write
Preview
Supports
Markdown
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