Commit 522d2760 authored by ale's avatar ale

Merge branch 'simpler-interfaces' into 'master'

Simpler interfaces

See merge request !2
parents 6f16cef4 ec82ed43
Pipeline #1515 passed with stages
in 1 minute and 53 seconds
This diff is collapsed.
package accountserver
import (
"context"
"errors"
"log"
"git.autistici.org/ai3/go-common/pwhash"
)
// CreateResourcesRequest lets administrators create one or more resources.
type CreateResourcesRequest struct {
AdminRequestBase
// Resources to create. All must either be global resources
// (no user ownership), or belong to the same user.
Resources []*Resource `json:"resources"`
}
// CreateResourcesResponse is the response type for CreateResourcesRequest.
type CreateResourcesResponse struct {
// Resources that were created.
Resources []*Resource `json:"resources"`
}
func (r *CreateResourcesRequest) getOwner(rctx *RequestContext) (*RawUser, error) {
// Fetch the user associated with the first resource (if
// any). Since resource validation might reference other
// resources, we need to provide it with a view of what the
// future resources will be. So we merge the resources from
// the database with those from the request, using a local
// copy of the User object.
if len(r.Resources) > 0 {
if owner := r.Resources[0].ID.User(); owner != "" {
u, err := getUserOrDie(rctx.Context, rctx.TX, owner)
if err != nil {
return nil, err
}
user := *u
user.Resources = mergeResources(u.Resources, r.Resources)
return &user, nil
}
}
return nil, nil
}
// Validate the request.
func (r *CreateResourcesRequest) Validate(rctx *RequestContext) error {
var owner string
user, err := r.getOwner(rctx)
if err != nil {
return err
}
var tplUser *User
if user != nil {
owner = user.Name
tplUser = &user.User
}
for _, rsrc := range r.Resources {
rctx.resourceTemplates.applyTemplate(rctx.Context, rsrc, tplUser)
// Check same-user ownership.
if rsrc.ID.User() != owner {
return errors.New("resources are owned by different users")
}
// Validate the resource.
if err := rctx.resourceValidator.validateResource(rctx.Context, rsrc, tplUser); err != nil {
log.Printf("validation error while creating resource %s: %v", rsrc.ID, err)
return err
}
}
return nil
}
// Serve the request.
func (r *CreateResourcesRequest) Serve(rctx *RequestContext) (interface{}, error) {
var resp CreateResourcesResponse
for _, rsrc := range r.Resources {
if err := rctx.TX.CreateResource(rctx.Context, rsrc); err != nil {
return nil, err
}
rctx.audit.Log(rctx, rsrc.ID, "resource created")
resp.Resources = append(resp.Resources, rsrc)
}
return &resp, nil
}
// Merge two resource lists by ID (the second one wins), return a new list.
func mergeResources(a, b []*Resource) []*Resource {
tmp := make(map[string]*Resource)
for _, l := range [][]*Resource{a, b} {
for _, r := range l {
tmp[r.ID.String()] = r
}
}
out := make([]*Resource, 0, len(tmp))
for _, r := range tmp {
out = append(out, r)
}
return out
}
// CreateUserRequest lets administrators create a new user along with the
// associated resources.
type CreateUserRequest struct {
AdminRequestBase
// User to create, along with associated resources.
User *User `json:"user"`
}
// applyTemplate fills in default values for the resources in the request.
func (r *CreateUserRequest) applyTemplate(rctx *RequestContext) error {
// Some fields should be always unset because there are
// specific methods to modify them.
r.User.Has2FA = false
r.User.HasOTP = false
r.User.HasEncryptionKeys = true // set to true so that resetPassword will create keys.
r.User.AccountRecoveryHint = ""
r.User.AppSpecificPasswords = nil
r.User.U2FRegistrations = nil
if r.User.Lang == "" {
r.User.Lang = "en"
}
// Allocate a new user ID.
uid, err := rctx.TX.NextUID(rctx.Context)
if err != nil {
return err
}
r.User.UID = uid
// Apply templates to all resources in the request.
for _, rsrc := range r.User.Resources {
rctx.resourceTemplates.applyTemplate(rctx.Context, rsrc, r.User)
}
return nil
}
// Validate the request.
func (r *CreateUserRequest) Validate(rctx *RequestContext) error {
if err := r.applyTemplate(rctx); err != nil {
return err
}
// Validate the user *and* all resources.
if err := rctx.userValidator(rctx.Context, r.User); err != nil {
log.Printf("validation error while creating user %+v: %v", r.User, err)
return err
}
for _, rsrc := range r.User.Resources {
if err := rctx.resourceValidator.validateResource(rctx.Context, rsrc, r.User); err != nil {
log.Printf("validation error while creating resource %+v: %v", rsrc, err)
return err
}
}
return nil
}
// CreateUserResponse is the response type for CreateUserRequest.
type CreateUserResponse struct {
User *User `json:"user"`
Password string `json:"password"`
}
// Serve the request
func (r *CreateUserRequest) Serve(rctx *RequestContext) (interface{}, error) {
var resp CreateUserResponse
// Create the user first, along with all the resources.
if err := rctx.TX.CreateUser(rctx.Context, r.User); err != nil {
return nil, err
}
resp.User = r.User
// Now set a password for the user and return it, and
// set random passwords for all the resources
// (currently, we don't care about those, the user
// will reset them later). However, we could return
// them in the response as well, if necessary.
u := &RawUser{User: *r.User}
newPassword := randomPassword()
if err := u.resetPassword(rctx.Context, rctx.TX, newPassword); err != nil {
return nil, err
}
resp.Password = newPassword
//s.audit.Log(ctx, ResourceID{}, "user created")
for _, rsrc := range r.User.Resources {
//rctx.audit.Log(ctx, r.ID, "resource created")
if resourceHasPassword(rsrc) {
if _, err := doResetResourcePassword(rctx.Context, rctx.TX, rsrc); err != nil {
// Just log, don't fail.
log.Printf("can't set random password for resource %s: %v", rsrc.ID, err)
}
}
}
return &resp, nil
}
func doResetResourcePassword(ctx context.Context, tx TX, rsrc *Resource) (string, error) {
newPassword := randomPassword()
encPassword := pwhash.Encrypt(newPassword)
// TODO: this needs a resource type-switch.
if err := tx.SetResourcePassword(ctx, rsrc, encPassword); err != nil {
return "", err
}
return newPassword, nil
}
package accountserver
import (
"errors"
"fmt"
)
// setResourceStatus sets the status of a single resource (shared
// logic between enable / disable resource methods).
func setResourceStatus(rctx *RequestContext, status string) error {
rsrc := rctx.Resource
rsrc.Status = status
if err := rctx.TX.UpdateResource(rctx.Context, rsrc); err != nil {
return err
}
rctx.audit.Log(rctx, rsrc.ID, fmt.Sprintf("status set to %s", status))
return nil
}
// DisableResourceRequest disables a resource belonging to the user.
type DisableResourceRequest struct {
ResourceRequestBase
}
// Serve the request.
func (r *DisableResourceRequest) Serve(rctx *RequestContext) (interface{}, error) {
return nil, setResourceStatus(rctx, ResourceStatusInactive)
}
// EnableResourceRequest enables a resource belonging to the user (admin-only).
type EnableResourceRequest struct {
AdminResourceRequestBase
}
// Serve the request.
func (r *EnableResourceRequest) Serve(rctx *RequestContext) (interface{}, error) {
return nil, setResourceStatus(rctx, ResourceStatusActive)
}
// ResetResourcePasswordRequest will reset the password associated
// with a resource (if the resource type supports it). It will
// generate a random password and return it to the caller.
type ResetResourcePasswordRequest struct {
ResourceRequestBase
}
// ResetResourcePasswordResponse is the response type for
// ResetResourcePasswordRequest.
type ResetResourcePasswordResponse struct {
Password string `json:"password"`
}
func resourceHasPassword(r *Resource) bool {
switch r.ID.Type() {
case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList:
return true
default:
return false
}
}
// Validate the request.
func (r *ResetResourcePasswordRequest) Validate(_ *RequestContext) error {
switch r.ResourceID.Type() {
case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList:
case ResourceTypeEmail:
return errors.New("can't reset email passwords with this API")
default:
return errors.New("can't reset password on this resource type")
}
return nil
}
// Serve the request.
func (r *ResetResourcePasswordRequest) Serve(rctx *RequestContext) (interface{}, error) {
// TODO: this needs a resource-type switch, because in some
// cases we may want to call out to other backends in order to
// reset credentials for certain resources that have their own
// secondary authentication databases (lists, mysql).
password, err := doResetResourcePassword(rctx.Context, rctx.TX, rctx.Resource)
if err != nil {
return nil, err
}
return &ResetResourcePasswordResponse{
Password: password,
}, nil
}
// MoveResourceRequest is an administrative operation to move resources
// between shards. Resources that are part of a group are moved all at
// once regardless of which individual ResourceID is provided as long
// as it belongs to the group.
type MoveResourceRequest struct {
AdminResourceRequestBase
Shard string `json:"shard"`
}
// Validate the request.
func (r *MoveResourceRequest) Validate(rctx *RequestContext) error {
// TODO: check shard
return nil
}
// MoveResourceResponse is the response type for MoveResourceRequest.
type MoveResourceResponse struct {
MovedIDs []string `json:"moved_ids"`
}
// Serve the request.
func (r *MoveResourceRequest) Serve(rctx *RequestContext) (interface{}, error) {
resources := []*Resource{rctx.Resource}
// If we have an associated user, collect all related
// resources, as they should all be moved at once.
if rctx.User != nil && rctx.Resource.Group != "" {
resources = append(resources, rctx.User.GetResourcesByGroup(rctx.Resource.Group)...)
}
var resp MoveResourceResponse
for _, rsrc := range resources {
rsrc.Shard = r.Shard
if err := rctx.TX.UpdateResource(rctx.Context, rsrc); err != nil {
return nil, err
}
resp.MovedIDs = append(resp.MovedIDs, rsrc.ID.String())
}
return &resp, nil
}
// AddEmailAliasRequest adds an alias (additional address) to an email resource.
type AddEmailAliasRequest struct {
ResourceRequestBase
Addr string `json:"addr"`
}
// Validate the request.
func (r *AddEmailAliasRequest) Validate(rctx *RequestContext) error {
if err := rctx.fieldValidators.email(rctx.Context, r.Addr); err != nil {
return newValidationError(nil, "addr", err.Error())
}
return nil
}
const maxEmailAliases = 5
// Serve the request.
func (r *AddEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error) {
if rctx.Resource.ID.Type() != ResourceTypeEmail {
return nil, errors.New("this operation only works on email resources")
}
// Allow at most 5 aliases.
if len(rctx.Resource.Email.Aliases) >= maxEmailAliases {
return nil, errors.New("too many aliases")
}
rctx.Resource.Email.Aliases = append(rctx.Resource.Email.Aliases, r.Addr)
if err := rctx.TX.UpdateResource(rctx.Context, rctx.Resource); err != nil {
return nil, err
}
rctx.audit.Log(rctx, r.ResourceID, fmt.Sprintf("added alias %s", r.Addr))
return nil, nil
}
// DeleteEmailAliasRequest removes an alias from an email resource.
type DeleteEmailAliasRequest struct {
ResourceRequestBase
Addr string `json:"addr"`
}
// Serve the request.
func (r *DeleteEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error) {
if rctx.Resource.ID.Type() != ResourceTypeEmail {
return nil, errors.New("this operation only works on email resources")
}
var aliases []string
for _, a := range rctx.Resource.Email.Aliases {
if a != r.Addr {
aliases = append(aliases, a)
}
}
rctx.Resource.Email.Aliases = aliases
if err := rctx.TX.UpdateResource(rctx.Context, rctx.Resource); err != nil {
return nil, err
}
rctx.audit.Log(rctx, r.ResourceID, fmt.Sprintf("removed alias %s", r.Addr))
return nil, nil
}
This diff is collapsed.
This diff is collapsed.
package accountserver
import (
"context"
"encoding/json"
"log"
)
type auditLogger interface {
Log(context.Context, ResourceID, string)
Log(*RequestContext, ResourceID, string)
}
type auditLogEntry struct {
......@@ -21,18 +20,27 @@ type auditLogEntry struct {
type syslogAuditLogger struct{}
func (l *syslogAuditLogger) Log(ctx context.Context, resourceID ResourceID, what string) {
func (l *syslogAuditLogger) Log(rctx *RequestContext, rid ResourceID, what string) {
e := auditLogEntry{
User: userFromContext(ctx),
By: authUserFromContext(ctx),
Message: what,
Comment: commentFromContext(ctx),
Comment: rctx.Comment,
}
if rctx.SSO != nil {
e.By = rctx.SSO.User
}
if rctx.User != nil {
e.User = rctx.User.Name
}
if !resourceID.Empty() {
e.ResourceName = resourceID.Name()
e.ResourceType = resourceID.Type()
if u := resourceID.User(); u != "" {
// Fall back to resource from context if unspecified.
if rid.Empty() && rctx.Resource != nil {
rid = rctx.Resource.ID
}
if !rid.Empty() {
e.ResourceName = rid.Name()
e.ResourceType = rid.Type()
// TODO: redundant?
if u := rid.User(); u != "" {
e.User = u
}
}
......
......@@ -112,7 +112,7 @@ func newLDAPBackendWithConn(conn ldapConn, base string) (*backend, error) {
}, nil
}
func newUser(entry *ldap.Entry) (*as.User, error) {
func newUser(entry *ldap.Entry) (*as.RawUser, error) {
// Note that some user-level attributes related to
// authentication are stored on the uid= object, while others
// are on the email= object. We set the latter in the GetUser
......@@ -120,15 +120,21 @@ func newUser(entry *ldap.Entry) (*as.User, 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.User{
Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue(preferredLanguageLDAPAttr),
UID: uidNumber,
PasswordRecoveryHint: entry.GetAttributeValue(recoveryHintLDAPAttr),
U2FRegistrations: decodeU2FRegistrations(entry.GetAttributeValues(u2fRegistrationsLDAPAttr)),
HasOTP: entry.GetAttributeValue(totpSecretLDAPAttr) != "",
user := &as.RawUser{
User: as.User{
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}"),
RecoveryPassword: strings.TrimPrefix(entry.GetAttributeValue(recoveryResponseLDAPAttr), "{crypt}"),
}
// The user has 2FA enabled if it has a TOTP secret or U2F keys.
......@@ -226,7 +232,7 @@ func (tx *backendTX) UpdateUser(ctx context.Context, user *as.User) error {
}
// GetUser returns a user.
func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.User, error) {
func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.RawUser, error) {
// First of all, find the main user object, and just that one.
vars := map[string]string{"user": username}
result, err := tx.search(ctx, tx.backend.userQuery.query(vars))
......@@ -256,14 +262,22 @@ func (tx *backendTX) GetUser(ctx context.Context, username string) (*as.User, er
}
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))
user.HasEncryptionKeys = (entry.GetAttributeValue(storagePublicKeyLDAPAttr) != "")
}
......@@ -290,25 +304,7 @@ func (tx *backendTX) SetUserPassword(ctx context.Context, user *as.User, encrypt
return
}
func (tx *backendTX) GetUserEncryptedPassword(ctx context.Context, user *as.User) string {
values := tx.readAttributeValues(ctx, tx.getUserDN(user), passwordLDAPAttr)
if len(values) > 0 {
// Remove legacy LDAP encryption prefix.
return strings.TrimPrefix(values[0], "{crypt}")
}
return ""
}
func (tx *backendTX) GetUserRecoveryEncryptedPassword(ctx context.Context, user *as.User) string {
values := tx.readAttributeValues(ctx, tx.getUserDN(user), recoveryResponseLDAPAttr)
if len(values) > 0 {
// Remove legacy LDAP encryption prefix.
return strings.TrimPrefix(values[0], "{crypt}")
}
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)
......@@ -317,14 +313,13 @@ func (tx *backendTX) SetPasswordRecoveryHint(ctx context.Context, user *as.User,
return nil
}
func (tx *backendTX) GetUserEncryptionKeys(ctx context.Context, user *as.User) ([]*as.UserEncryptionKey, error) {
r := user.GetSingleResourceByType(as.ResourceTypeEmail)
dn, err := tx.backend.resources.GetDN(r.ID)
if err != nil {
return nil, err
}
rawKeys := tx.readAttributeValues(ctx, dn, storagePrivateKeyLDAPAttr)
return decodeUserEncryptionKeys(rawKeys), nil
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)
tx.setAttr(dn, recoveryHintLDAPAttr)
tx.setAttr(dn, recoveryResponseLDAPAttr)
return nil
}
func (tx *backendTX) SetUserEncryptionKeys(ctx context.Context, user *as.User, keys []*as.UserEncryptionKey) error {
......
......@@ -17,11 +17,11 @@ const (
testUser2 = "due@investici.org"
)
func startServerAndGetUser(t testing.TB) (func(), as.Backend, *as.User) {
func startServerAndGetUser(t testing.TB) (func(), as.Backend, *as.RawUser) {
return startServerAndGetUserWithName(t, testUser1)
}
func startServerAndGetUser2(t testing.TB) (func(), as.Backend, *as.User) {
func startServerAndGetUser2(t testing.TB) (func(), as.Backend, *as.RawUser) {
return startServerAndGetUserWithName(t, testUser2)
}
......@@ -45,7 +45,7 @@ func startServer(t testing.TB) (func(), as.Backend) {
return stop, b
}
func startServerAndGetUserWithName(t testing.TB, username string) (func(), as.Backend, *as.User) {
func startServerAndGetUserWithName(t testing.TB, username string) (func(), as.Backend, *as.RawUser) {
stop, b := startServer(t)
tx, _ := b.NewTransaction()
......@@ -233,7 +233,7 @@ func TestModel_SetUserPassword(t *testing.T) {
encPass := "encrypted password"
tx, _ := b.NewTransaction()
if err := tx.SetUserPassword(context.Background(), user, encPass); err != nil {
if err := tx.SetUserPassword(context.Background(), &user.User, encPass); err != nil {
t.Fatal("SetUserPassword", err)
}
if err := tx.Commit(context.Background()); err != nil {
......@@ -268,7 +268,7 @@ func TestModel_SetUserEncryptionKeys_Add(t *testing.T) {
Key: []byte("very secret key"),
},
}
if err := tx.SetUserEncryptionKeys(context.Background(), user, keys); err != nil {
if err := tx.SetUserEncryptionKeys(context.Background(), &user.User, keys); err != nil {
t.Fatal("SetUserEncryptionKeys", err)
}
if err := tx.Commit(context.Background()); err != nil {
......@@ -287,7 +287,7 @@ func TestModel_SetUserEncryptionKeys_Replace(t *testing.T) {
Key: []byte("very secret key"),
},
}
if err := tx.SetUserEncryptionKeys(context.Background(), user, keys); err != nil {
if err := tx.SetUserEncryptionKeys(context.Background(), &user.User, keys); err != nil {
t.Fatal("SetUserEncryptionKeys", err)
}
if err := tx.Commit(context.Background()); err != nil {
......
......@@ -18,9 +18,8 @@ givenName: Private
shadowLastChange: 12345
shadowWarning: 7
preferredLanguage: it
userPassword:: e2NyeXB0fSQ2JG1wVzdTZHZROG52OFJabE8kcFNqbm1hLi9CZDNIWU1hL29sMGZ
FRmZrS3pWRjAxUkkzMnpISEoxNnc2V2xaajFuSFVzMmd4ZXZIVDdoemdELnJ5Lk1BZWM3REptZVZ5
WEtkeDV0QTE=
userPassword:: JDYkbXBXN1NkdlE4bnY4UlpsTyRJNGZCV2RVSkV5VWxvR2l1WmdibzI1OVVUWkkyL3JWTlA4N1lT
TjNUTS53YXkyZHZSd1g2YTQ0dVVXZ2tYL1pzbkc4YXdHRFhYVGYwNU1VeE1saWdIMA==
dn: mail=uno@investici.org,uid=uno@investici.org,ou=People,dc=example,dc=com
status: active
......@@ -28,9 +27,8 @@ recoverQuestion:: dGkgc2VpIG1haSDDuMOgdHRvIG1hbGUgY2FkZW5kbyBkYSB1biBwYWxhenpv
IGRpIG90dG8gcGlhbmk/
objectClass: top
objectClass: virtualMailUser
userPassword:: e2NyeXB0fSQ2JG1wVzdTZHZROG52OFJabE8kcFNqbm1hLi9CZDNIWU1hL29sMGZ
FRmZrS3pWRjAxUkkzMnpISEoxNnc2V2xaajFuSFVzMmd4ZXZIVDdoemdELnJ5Lk1BZWM3REptZVZ5
WEtkeDV0QTE=
userPassword:: JDYkbXBXN1NkdlE4bnY4UlpsTyRJNGZCV2RVSkV5VWxvR2l1WmdibzI1OVVUWkkyL3JWTlA4N1lT
TjNUTS53YXkyZHZSd1g2YTQ0dVVXZ2tYL1pzbkc4YXdHRFhYVGYwNU1VeE1saWdIMA==
uidNumber: 19475
host: host2
mailAlternateAddress: uno@anche.no
......@@ -66,9 +64,8 @@ uid: uno
creationDate: 01-08-2013
shadowLastChange: 12345
originalHost: host2
userPassword:: e2NyeXB0fSQ2JElDYkx1WTI3QWl6bC5FeEgkUDhOZHJ3VEtxZ2UwQUp3QW9oNE1
EYlUxU3EySGtuRkF1cEx2RUI0U28waEw5NWtpZ3dIeXQuQnYxS0J5SFM2MXd6RnZuLnJsMEN4eFpx
RVgzUnVxbDE=
userPassword:: JDYkbXBXN1NkdlE4bnY4UlpsTyRJNGZCV2RVSkV5VWxvR2l1WmdibzI1OVVUWkkyL3JWTlA4N1lT
TjNUTS53YXkyZHZSd1g2YTQ0dVVXZ2tYL1pzbkc4YXdHRFhYVGYwNU1VeE1saWdIMA==
dn: alias=uno,uid=uno@investici.org,ou=People,dc=example,dc=com
status: active
......
......@@ -42,7 +42,7 @@ type ldapTX struct {
conn ldapConn
cache map[string][]string
newDNs map[string]struct{}
newDNs map[string]struct{} // nolint (it's plural DN, not DNS)
changes []ldapAttr
}
......
......@@ -115,7 +115,7 @@ func main() {
as := server.New(service, be)
if err := serverutil.Serve(as.Handler(), config.ServerConfig, *addr); err != nil {
if err := serverutil.Serve(as, config.ServerConfig, *addr); err != nil {
log.Fatal(err)
}
}
package accountserver
import (
"context"
"git.autistici.org/ai3/go-common/userenckey"
)
func keysToBytes(keys []*UserEncryptionKey) [][]byte {
var rawKeys [][]byte
for _, k := range keys {
rawKeys = append(rawKeys, k.Key)
}
return rawKeys
}
func (s *AccountService) initializeEncryptionKeys(ctx context.Context, tx TX, user *User, password string) (keys []*UserEncryptionKey, decrypted *userenckey.Key, err error) {
// Create new keys
pub, priv, err := userenckey.GenerateKey()
if err != nil {
return nil, nil, err
}
decrypted = priv
enc, err := userenckey.Encrypt(priv, []byte(password))
if err != nil {
return nil, nil, err
}
keys = append(keys, &UserEncryptionKey{
ID: UserEncryptionKeyMainID,
Key: enc,
})
// Save the new public key.
if err = tx.SetUserEncryptionPublicKey(ctx, user, pub); err != nil {
err = newBackendError(err)
}
return
}
func (s *AccountService) readOrInitializeEncryptionKeys(ctx context.Context, tx TX, user *User, oldPassword, newPassword string) (keys []*UserEncryptionKey, decrypted *userenckey.Key, err error) {
if user.HasEncryptionKeys {