actions.go 30.5 KB
Newer Older
ale's avatar
ale committed
1
2
3
4
package accountserver

import (
	"context"
5
6
	"crypto/rand"
	"encoding/base64"
ale's avatar
ale committed
7
	"errors"
8
9
	"fmt"
	"log"
ale's avatar
ale committed
10
11

	"git.autistici.org/ai3/go-common/pwhash"
ale's avatar
ale committed
12
	"github.com/pquerna/otp/totp"
13
	"github.com/sethvargo/go-password/password"
14
	"github.com/tstranex/u2f"
ale's avatar
ale committed
15
16
)

17
18
19
20
// RequestBase contains parameters shared by all request types.
type RequestBase struct {
	Username string `json:"username"`
	SSO      string `json:"sso"`
ale's avatar
ale committed
21

22
23
	// Optional comment, will end up in audit logs.
	Comment string `json:"comment,omitempty"`
ale's avatar
ale committed
24
25
}

26
type userCtxKeyType int
ale's avatar
ale committed
27

ale's avatar
ale committed
28
var userCtxKey userCtxKeyType
ale's avatar
ale committed
29

30
31
32
33
func userFromContext(ctx context.Context) string {
	s, ok := ctx.Value(userCtxKey).(string)
	if ok {
		return s
ale's avatar
ale committed
34
	}
35
	return ""
ale's avatar
ale committed
36
37
}

38
type commentCtxKeyType int
ale's avatar
ale committed
39

ale's avatar
ale committed
40
var commentCtxKey commentCtxKeyType
ale's avatar
ale committed
41

42
43
44
45
func commentFromContext(ctx context.Context) string {
	s, ok := ctx.Value(commentCtxKey).(string)
	if ok {
		return s
ale's avatar
ale committed
46
	}
47
	return ""
ale's avatar
ale committed
48
49
}

ale's avatar
ale committed
50
// NewContext returns a new Context with some request-related values set.
51
52
53
54
func (r RequestBase) NewContext(ctx context.Context) context.Context {
	ctx = context.WithValue(ctx, userCtxKey, r.Username)
	if r.Comment != "" {
		ctx = context.WithValue(ctx, commentCtxKey, r.Comment)
ale's avatar
ale committed
55
	}
56
	return ctx
ale's avatar
ale committed
57
58
}

ale's avatar
ale committed
59
60
61
62
63
64
65
// PrivilegedRequestBase extends RequestBase with the user password,
// for privileged endpoints.
type PrivilegedRequestBase struct {
	RequestBase
	CurPassword string `json:"cur_password"`
}

66
67
68
69
70
71
72
73
74
// ResourceRequestBase is the base type for resource-level requests.
type ResourceRequestBase struct {
	ResourceID ResourceID `json:"resource_id"`
	SSO        string     `json:"sso"`

	// Optional comment, will end up in audit logs.
	Comment string `json:"comment,omitempty"`
}

ale's avatar
ale committed
75
// NewContext returns a new Context with some request-related values set.
76
77
78
79
80
81
82
83
84
85
func (r ResourceRequestBase) NewContext(ctx context.Context) context.Context {
	if u := r.ResourceID.User(); u != "" {
		ctx = context.WithValue(ctx, userCtxKey, u)
	}
	if r.Comment != "" {
		ctx = context.WithValue(ctx, commentCtxKey, r.Comment)
	}
	return ctx
}

ale's avatar
ale committed
86
// GetUserRequest is the request type for AccountService.GetUser().
ale's avatar
ale committed
87
88
89
90
type GetUserRequest struct {
	RequestBase
}

ale's avatar
ale committed
91
// GetUser returns public information about a user.
92
93
94
95
96
97
func (s *AccountService) GetUser(ctx context.Context, tx TX, req *GetUserRequest) (resp *User, err error) {
	err = s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
		resp = user
		return nil
	})
	return
ale's avatar
ale committed
98
99
}

ale's avatar
ale committed
100
101
// setResourceStatus sets the status of a single resource (shared
// logic between enable / disable resource methods).
102
func (s *AccountService) setResourceStatus(ctx context.Context, tx TX, r *Resource, status string) error {
ale's avatar
ale committed
103
	r.Status = status
104
	if err := tx.UpdateResource(ctx, r); err != nil {
105
106
		return newBackendError(err)
	}
107
	s.audit.Log(ctx, r.ID, fmt.Sprintf("status set to %s", status))
108
	return nil
ale's avatar
ale committed
109
110
}

ale's avatar
ale committed
111
// DisableResourceRequest is the request type for AccountService.DisableResource().
ale's avatar
ale committed
112
type DisableResourceRequest struct {
113
	ResourceRequestBase
ale's avatar
ale committed
114
115
}

ale's avatar
ale committed
116
// DisableResource disables a resource belonging to the user.
117
func (s *AccountService) DisableResource(ctx context.Context, tx TX, req *DisableResourceRequest) error {
118
	return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
119
120
		return s.setResourceStatus(ctx, tx, r, ResourceStatusInactive)
	})
ale's avatar
ale committed
121
122
}

ale's avatar
ale committed
123
// EnableResourceRequest is the request type for AccountService.EnableResource().
ale's avatar
ale committed
124
type EnableResourceRequest struct {
125
	ResourceRequestBase
ale's avatar
ale committed
126
127
}

ale's avatar
ale committed
128
// EnableResource enables a resource belonging to the user.
129
func (s *AccountService) EnableResource(ctx context.Context, tx TX, req *EnableResourceRequest) error {
130
	return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
131
132
		return s.setResourceStatus(ctx, tx, r, ResourceStatusActive)
	})
ale's avatar
ale committed
133
134
}

ale's avatar
ale committed
135
// ChangeUserPasswordRequest is the request type for AccountService.ChangeUserPassword().
ale's avatar
ale committed
136
type ChangeUserPasswordRequest struct {
ale's avatar
ale committed
137
138
	PrivilegedRequestBase
	Password string `json:"password"`
ale's avatar
ale committed
139
140
}

141
// Validate the request.
142
func (r *ChangeUserPasswordRequest) Validate(ctx context.Context, s *AccountService) error {
143
	return s.fieldValidators.password(ctx, r.Password)
144
145
}

ale's avatar
ale committed
146
147
// ChangeUserPassword updates a user's password. It will also take
// care of re-encrypting the user encryption key, if present.
148
func (s *AccountService) ChangeUserPassword(ctx context.Context, tx TX, req *ChangeUserPasswordRequest) error {
149
	return s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
ale's avatar
ale committed
150
151
152
153
154
155
156
157
158
159
160
161
		return s.changeUserPasswordAndUpdateEncryptionKeys(ctx, tx, user, req.CurPassword, req.Password)
	})
}

// PasswordRecoveryRequest is the request type for AccountService.RecoverPassword().
// It is not authenticated with SSO.
type PasswordRecoveryRequest struct {
	Username         string `json:"username"`
	RecoveryPassword string `json:"recovery_password"`
	Password         string `json:"password"`
}

162
163
164
165
166
// NewContext adds logging data to the current request context.
func (r *PasswordRecoveryRequest) NewContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, userCtxKey, r.Username)
}

ale's avatar
ale committed
167
168
// Validate the request.
func (r *PasswordRecoveryRequest) Validate(ctx context.Context, s *AccountService) error {
169
	return s.fieldValidators.password(ctx, r.Password)
ale's avatar
ale committed
170
171
172
173
174
175
176
}

// RecoverPassword lets users reset their password by providing
// secondary credentials, which we authenticate ourselves.
//
// TODO: call out to auth-server for secondary authentication?
func (s *AccountService) RecoverPassword(ctx context.Context, tx TX, req *PasswordRecoveryRequest) error {
177
	user, err := getUserOrDie(ctx, tx, req.Username)
ale's avatar
ale committed
178
179
180
	if err != nil {
		return err
	}
181
182
183
184
185
186

	// Authenticate the secret recovery password.
	if !pwhash.ComparePassword(tx.GetUserRecoveryEncryptedPassword(ctx, user), req.RecoveryPassword) {
		return ErrUnauthorized
	}

ale's avatar
ale committed
187
188
	ctx = context.WithValue(ctx, authUserCtxKey, req.Username)

189
	return s.withRequest(ctx, tx, req, user, func(ctx context.Context) error {
190
191
		s.audit.Log(ctx, ResourceID{}, "password reset via account recovery")

ale's avatar
ale committed
192
		// Change the user password (the recovery password does not change).
193
194
195
196
197
		if err := s.changeUserPasswordAndUpdateEncryptionKeys(ctx, tx, user, req.RecoveryPassword, req.Password); err != nil {
			return err
		}
		// Disable 2FA.
		return s.disable2FA(ctx, tx, user)
ale's avatar
ale committed
198
199
200
201
202
203
204
205
206
207
208
	})
}

// ResetPasswordRequest is the request type for AccountService.ResetPassword().
type ResetPasswordRequest struct {
	RequestBase
	Password string `json:"password"`
}

// Validate the request.
func (r *ResetPasswordRequest) Validate(ctx context.Context, s *AccountService) error {
209
	return s.fieldValidators.password(ctx, r.Password)
ale's avatar
ale committed
210
211
212
213
214
215
}

// ResetPassword is an admin operation to forcefully reset the
// password for an account. The user will lose access to all stored
// email (because the encryption keys will be reset) and to 2FA.
func (s *AccountService) ResetPassword(ctx context.Context, tx TX, req *ResetPasswordRequest) error {
216
	return s.handleUserRequest(ctx, tx, req, s.authAdmin(req.RequestBase), func(ctx context.Context, user *User) error {
ale's avatar
ale committed
217
		// Disable 2FA.
218
		if err := s.disable2FA(ctx, tx, user); err != nil {
ale's avatar
ale committed
219
			return err
220
		}
221

ale's avatar
ale committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
		// Reset encryption keys and set the new password.
		return s.changeUserPasswordAndResetEncryptionKeys(ctx, tx, user, req.Password)
	})
}

// SetPasswordRecoveryHintRequest is the request type for
// AccountService.SetPasswordRecoveryHint().
type SetPasswordRecoveryHintRequest struct {
	PrivilegedRequestBase
	Hint     string `json:"recovery_hint"`
	Response string `json:"recovery_response"`
}

// Validate the request.
func (r *SetPasswordRecoveryHintRequest) Validate(ctx context.Context, s *AccountService) error {
237
	return s.fieldValidators.password(ctx, r.Response)
ale's avatar
ale committed
238
239
240
241
242
}

// SetPasswordRecoveryHint lets users set the password recovery hint
// and expected response (secondary password).
func (s *AccountService) SetPasswordRecoveryHint(ctx context.Context, tx TX, req *SetPasswordRecoveryHintRequest) error {
243
	return s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
ale's avatar
ale committed
244
245
246
247
248
249
250
251
		// If the encryption keys are not set up yet, use the
		// CurPassword to initialize them.
		keys, decrypted, err := s.readOrInitializeEncryptionKeys(ctx, tx, user, req.CurPassword, req.CurPassword)
		if err != nil {
			return err
		}

		keys, err = updateEncryptionKey(keys, decrypted, UserEncryptionKeyRecoveryID, req.Response)
252
253
254
		if err != nil {
			return err
		}
ale's avatar
ale committed
255

ale's avatar
ale committed
256
		if err := tx.SetUserEncryptionKeys(ctx, user, keys); err != nil {
257
258
			return newBackendError(err)
		}
ale's avatar
ale committed
259
260
261

		encResponse := pwhash.Encrypt(req.Response)
		return tx.SetPasswordRecoveryHint(ctx, user, req.Hint, encResponse)
262
	})
ale's avatar
ale committed
263
264
}

ale's avatar
ale committed
265
266
267
268
269
270
271
// Change the user password and update encryption keys, provided we
// have a password that we can use to decrypt them first.
func (s *AccountService) changeUserPasswordAndUpdateEncryptionKeys(ctx context.Context, tx TX, user *User, oldPassword, newPassword string) error {
	// If the user does not yet have an encryption key, generate one now.
	var err error

	keys, decrypted, err := s.readOrInitializeEncryptionKeys(ctx, tx, user, oldPassword, newPassword)
272
273
274
275
	if err != nil {
		return err
	}

ale's avatar
ale committed
276
	keys, err = updateEncryptionKey(keys, decrypted, UserEncryptionKeyMainID, newPassword)
277
278
279
280
	if err != nil {
		return err
	}

281
	if err := tx.SetUserEncryptionKeys(ctx, user, keys); err != nil {
282
283
		return newBackendError(err)
	}
ale's avatar
ale committed
284
285
286
287

	// Set the encrypted password attribute on the user (will set it on emails too).
	encPass := pwhash.Encrypt(newPassword)
	if err := tx.SetUserPassword(ctx, user, encPass); err != nil {
288
289
		return newBackendError(err)
	}
290
291
292

	s.audit.Log(ctx, ResourceID{}, "password changed")

293
294
295
	return nil
}

ale's avatar
ale committed
296
297
298
299
300
301
// Change the user password and reset all encryption keys. Existing email
// won't be readable anymore. Existing 2FA credentials will be deleted.
func (s *AccountService) changeUserPasswordAndResetEncryptionKeys(ctx context.Context, tx TX, user *User, newPassword string) error {
	// Calling initialize will wipe the current keys and replace
	// them with a new one.
	keys, _, err := s.initializeEncryptionKeys(ctx, tx, user, newPassword)
ale's avatar
ale committed
302
	if err != nil {
ale's avatar
ale committed
303
		return err
ale's avatar
ale committed
304
	}
ale's avatar
ale committed
305

306
	if err := tx.SetUserEncryptionKeys(ctx, user, keys); err != nil {
307
308
		return newBackendError(err)
	}
ale's avatar
ale committed
309

ale's avatar
ale committed
310
311
312
313
	// Set the encrypted password attribute on the user (will set it on emails too).
	encPass := pwhash.Encrypt(newPassword)
	if err := tx.SetUserPassword(ctx, user, encPass); err != nil {
		return newBackendError(err)
ale's avatar
ale committed
314
	}
315
316
317

	s.audit.Log(ctx, ResourceID{}, "password reset")

ale's avatar
ale committed
318
319
	return nil
}
ale's avatar
ale committed
320

321
322
323
324
325
326
327
// Disable 2FA for a user account.
func (s *AccountService) disable2FA(ctx context.Context, tx TX, user *User) error {
	// Disable OTP.
	if err := tx.DeleteUserTOTPSecret(ctx, user); err != nil {
		return newBackendError(err)
	}

ale's avatar
ale committed
328
329
330
331
332
333
334
335
	// Disable U2F.
	if len(user.U2FRegistrations) > 0 {
		user.U2FRegistrations = nil
		if err := tx.UpdateUser(ctx, user); err != nil {
			return newBackendError(err)
		}
	}

336
	// Wipe all app-specific passwords.
ale's avatar
ale committed
337
338
	for _, asp := range user.AppSpecificPasswords {
		if err := tx.DeleteApplicationSpecificPassword(ctx, user, asp.ID); err != nil {
339
			return newBackendError(err)
ale's avatar
ale committed
340
341
		}
	}
ale's avatar
ale committed
342
	return nil
ale's avatar
ale committed
343
344
}

ale's avatar
ale committed
345
346
// CreateApplicationSpecificPasswordRequest is the request type for
// AccountService.CreateApplicationSpecificPassword().
ale's avatar
ale committed
347
type CreateApplicationSpecificPasswordRequest struct {
ale's avatar
ale committed
348
349
	PrivilegedRequestBase
	Service string `json:"service"`
ale's avatar
ale committed
350
	Notes   string `json:"notes"`
351
352
}

ale's avatar
ale committed
353
// Validate the request.
354
func (r *CreateApplicationSpecificPasswordRequest) Validate(_ context.Context, _ *AccountService) error {
355
356
357
358
	if r.Service == "" {
		return errors.New("empty 'service' attribute")
	}
	return nil
ale's avatar
ale committed
359
360
}

ale's avatar
ale committed
361
362
// CreateApplicationSpecificPasswordResponse is the response type for
// AccountService.CreateApplicationSpecificPassword().
ale's avatar
ale committed
363
364
365
366
type CreateApplicationSpecificPasswordResponse struct {
	Password string `json:"password"`
}

ale's avatar
ale committed
367
368
// CreateApplicationSpecificPassword will generate a new
// application-specific password for the given service.
369
func (s *AccountService) CreateApplicationSpecificPassword(ctx context.Context, tx TX, req *CreateApplicationSpecificPasswordRequest) (*CreateApplicationSpecificPasswordResponse, error) {
370
	var resp CreateApplicationSpecificPasswordResponse
371
	err := s.handleUserRequest(ctx, tx, req, s.authUserWithPassword(req.PrivilegedRequestBase), func(ctx context.Context, user *User) error {
372
373
374
375
		// No application-specific passwords unless 2FA is enabled.
		if !user.Has2FA {
			return newRequestError(errors.New("2FA is not enabled for this user"))
		}
ale's avatar
ale committed
376

377
378
379
380
381
382
		// Create a new application-specific password and set it in
		// the database. We don't need to update the User object as
		// we're not reusing it.
		asp := &AppSpecificPasswordInfo{
			ID:      randomAppSpecificPasswordID(),
			Service: req.Service,
ale's avatar
ale committed
383
			Comment: req.Notes,
384
385
386
387
388
389
		}
		password := randomAppSpecificPassword()
		encPass := pwhash.Encrypt(password)
		if err := tx.SetApplicationSpecificPassword(ctx, user, asp, encPass); err != nil {
			return newBackendError(err)
		}
ale's avatar
ale committed
390

391
392
393
394
		// Create or update the user encryption key associated with
		// this password. The user encryption key IDs for ASPs all
		// have an 'asp_' prefix, followed by the ASP ID.
		if user.HasEncryptionKeys {
ale's avatar
ale committed
395
396
397
398
			keys, decrypted, err := s.readOrInitializeEncryptionKeys(ctx, tx, user, req.CurPassword, req.CurPassword)
			if err != nil {
				return err
			}
399
			keyID := "asp_" + asp.ID
ale's avatar
ale committed
400
401
			keys, err = updateEncryptionKey(keys, decrypted, keyID, password)
			if err != nil {
402
403
				return err
			}
ale's avatar
ale committed
404
405
406
			if err := tx.SetUserEncryptionKeys(ctx, user, keys); err != nil {
				return newBackendError(err)
			}
407
		}
ale's avatar
ale committed
408

409
410
411
412
		resp.Password = password
		return nil
	})
	return &resp, err
ale's avatar
ale committed
413
414
}

ale's avatar
ale committed
415
416
// DeleteApplicationSpecificPasswordRequest is the request type for
// AccountService.DeleteApplicationSpecificPassword().
ale's avatar
ale committed
417
418
419
420
421
type DeleteApplicationSpecificPasswordRequest struct {
	RequestBase
	AspID string `json:"asp_id"`
}

ale's avatar
ale committed
422
423
// DeleteApplicationSpecificPassword destroys an application-specific
// password, identified by its unique ID.
424
func (s *AccountService) DeleteApplicationSpecificPassword(ctx context.Context, tx TX, req *DeleteApplicationSpecificPasswordRequest) error {
425
426
	return s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
		if err := tx.DeleteApplicationSpecificPassword(ctx, user, req.AspID); err != nil {
427
			return err
ale's avatar
ale committed
428
		}
429

430
431
432
433
		// Delete the user encryption key associated with this
		// password (we're going to find it via its ID).
		keys, err := tx.GetUserEncryptionKeys(ctx, user)
		if err != nil {
ale's avatar
ale committed
434
			return newBackendError(err)
435
436
437
438
439
440
441
442
443
444
445
446
447
		}
		if len(keys) == 0 {
			return nil
		}
		aspKeyID := "asp_" + req.AspID
		var newKeys []*UserEncryptionKey
		for _, k := range keys {
			if k.ID != aspKeyID {
				newKeys = append(newKeys, k)
			}
		}
		return tx.SetUserEncryptionKeys(ctx, user, newKeys)
	})
ale's avatar
ale committed
448
449
}

450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
// ResetResourcePasswordRequest is the request type for AccountService.ResetResourcePassword().
type ResetResourcePasswordRequest struct {
	ResourceRequestBase
}

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(ctx context.Context, s *AccountService) error {
	switch r.ResourceID.Type() {
	case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList:
	case ResourceTypeEmail:
		return errors.New("can't reset email passwords independently")
	default:
		return errors.New("can't reset password on this resource type")
	}
	return nil
}

// ResetResourcePasswordResponse is the response type for AccountService.ResetResourcePassword().
type ResetResourcePasswordResponse struct {
	Password string `json:"password"`
}

// ResetResourcePassword can 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.
func (s *AccountService) ResetResourcePassword(ctx context.Context, tx TX, req *ResetResourcePasswordRequest) (*ResetResourcePasswordResponse, error) {
	var resp ResetResourcePasswordResponse
	err := s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
		newPassword, err := s.doResetResourcePassword(ctx, tx, r)
		if err != nil {
			return err
		}

		// Return the password to the caller, in cleartext.
		resp.Password = newPassword

		// TODO: This is where 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).
		return nil
	})
	return &resp, err
}

func (s *AccountService) doResetResourcePassword(ctx context.Context, tx TX, r *Resource) (string, error) {
	newPassword := randomPassword()
	encPassword := pwhash.Encrypt(newPassword)

	// TODO: this needs a resource type-switch.
	if err := tx.SetResourcePassword(ctx, r, encPassword); err != nil {
		return "", err
	}
	return newPassword, nil
}

ale's avatar
ale committed
515
// MoveResourceRequest is the request type for AccountService.MoveResource().
ale's avatar
ale committed
516
517
type MoveResourceRequest struct {
	RequestBase
518
519
	ResourceID ResourceID `json:"resource_id"`
	Shard      string     `json:"shard"`
ale's avatar
ale committed
520
521
}

ale's avatar
ale committed
522
// MoveResourceResponse is the response type for AccountService.MoveResource().
ale's avatar
ale committed
523
524
525
526
type MoveResourceResponse struct {
	MovedIDs []string `json:"moved_ids"`
}

ale's avatar
ale committed
527
528
529
530
// MoveResource 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.
531
func (s *AccountService) MoveResource(ctx context.Context, tx TX, req *MoveResourceRequest) (*MoveResourceResponse, error) {
ale's avatar
ale committed
532
	var resp MoveResourceResponse
533
	err := s.handleUserRequest(ctx, tx, req, s.authAdmin(req.RequestBase), func(ctx context.Context, user *User) error {
534
535
536
		// Collect all related resources, as they should all be moved at once.
		r, err := tx.GetResource(ctx, req.ResourceID)
		if err != nil {
ale's avatar
ale committed
537
			return newBackendError(err)
538
539
540
541
542
543
		}
		var resources []*Resource
		if r.Group != "" {
			resources = append(resources, user.GetResourcesByGroup(r.Group)...)
		} else {
			resources = []*Resource{r}
ale's avatar
ale committed
544
545
		}

546
547
548
		for _, r := range resources {
			r.Shard = req.Shard
			if err := tx.UpdateResource(ctx, r); err != nil {
ale's avatar
ale committed
549
				return newBackendError(err)
550
551
552
553
554
555
			}
			resp.MovedIDs = append(resp.MovedIDs, r.ID.String())
		}
		return nil
	})
	return &resp, err
ale's avatar
ale committed
556
557
}

ale's avatar
ale committed
558
// EnableOTPRequest is the request type for AccountService.EnableOTP().
ale's avatar
ale committed
559
560
type EnableOTPRequest struct {
	RequestBase
ale's avatar
ale committed
561
562
563
	TOTPSecret string `json:"totp_secret"`
}

ale's avatar
ale committed
564
// Validate the request.
565
func (r *EnableOTPRequest) Validate(_ context.Context, _ *AccountService) error {
ale's avatar
ale committed
566
567
568
569
570
	// TODO: the length here is bogus, replace with real value.
	if r.TOTPSecret != "" && len(r.TOTPSecret) != 32 {
		return errors.New("bad totp_secret value")
	}
	return nil
ale's avatar
ale committed
571
572
}

ale's avatar
ale committed
573
// EnableOTPResponse is the response type for AccountService.EnableOTP().
ale's avatar
ale committed
574
575
576
577
type EnableOTPResponse struct {
	TOTPSecret string `json:"totp_secret"`
}

ale's avatar
ale committed
578
579
580
581
582
// EnableOTP enables OTP-based two-factor authentication for a
// user. The caller can generate the TOTP secret itself if needed
// (useful for UX that confirms that the user is able to login first),
// or it can let the server generate a new secret by passing an empty
// totp_secret.
583
func (s *AccountService) EnableOTP(ctx context.Context, tx TX, req *EnableOTPRequest) (*EnableOTPResponse, error) {
584
	var resp EnableOTPResponse
585
	err := s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) (err error) {
586
587
588
589
590
591
592
		// Replace or initialize the TOTP secret.
		if req.TOTPSecret == "" {
			req.TOTPSecret, err = generateTOTPSecret()
			if err != nil {
				return err
			}
		}
ale's avatar
ale committed
593

594
595
		if err := tx.SetUserTOTPSecret(ctx, user, req.TOTPSecret); err != nil {
			return newBackendError(err)
ale's avatar
ale committed
596
		}
ale's avatar
ale committed
597

598
		resp.TOTPSecret = req.TOTPSecret
ale's avatar
ale committed
599
		s.audit.Log(ctx, ResourceID{}, "totp enabled")
600
601
602
		return nil
	})
	return &resp, err
ale's avatar
ale committed
603
604
}

ale's avatar
ale committed
605
// DisableOTPRequest is the request type for AccountService.DisableOTP().
ale's avatar
ale committed
606
607
608
609
type DisableOTPRequest struct {
	RequestBase
}

ale's avatar
ale committed
610
// DisableOTP disables two-factor authentication for a user.
611
func (s *AccountService) DisableOTP(ctx context.Context, tx TX, req *DisableOTPRequest) error {
612
613
	return s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
		// Delete the TOTP secret (if present).
614
615
616
		if err := tx.DeleteUserTOTPSecret(ctx, user); err != nil {
			return newBackendError(err)
		}
ale's avatar
ale committed
617
		s.audit.Log(ctx, ResourceID{}, "totp disabled")
618
619
		return nil
	})
ale's avatar
ale committed
620
621
}

ale's avatar
ale committed
622
623
624
625
626
627
628
629
// AddEmailAliasRequest is the request type for AccountService.AddEmailAlias().
type AddEmailAliasRequest struct {
	ResourceRequestBase
	Addr string `json:"addr"`
}

// Validate the request.
func (r *AddEmailAliasRequest) Validate(ctx context.Context, s *AccountService) error {
630
631
632
	if r.ResourceID.Type() != ResourceTypeEmail {
		return errors.New("this operation only works on email resources")
	}
633
	return s.fieldValidators.email(ctx, r.Addr)
ale's avatar
ale committed
634
635
636
637
638
639
}

const maxEmailAliases = 5

// AddEmailAlias adds an alias (additional address) to an email resource.
func (s *AccountService) AddEmailAlias(ctx context.Context, tx TX, req *AddEmailAliasRequest) error {
640
	return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
ale's avatar
ale committed
641
642
643
644
645
646
		// Allow at most 5 aliases.
		if len(r.Email.Aliases) >= maxEmailAliases {
			return errors.New("too many aliases")
		}

		r.Email.Aliases = append(r.Email.Aliases, req.Addr)
647
648
649
650
651
652
		if err := tx.UpdateResource(ctx, r); err != nil {
			return err
		}

		s.audit.Log(ctx, r.ID, fmt.Sprintf("added alias %s", req.Addr))
		return nil
ale's avatar
ale committed
653
654
655
656
657
658
659
660
661
	})
}

// DeleteEmailAliasRequest is the request type for AccountService.DeleteEmailAlias().
type DeleteEmailAliasRequest struct {
	ResourceRequestBase
	Addr string `json:"addr"`
}

662
663
664
665
666
667
668
669
// Validate the request.
func (r *DeleteEmailAliasRequest) Validate(ctx context.Context, s *AccountService) error {
	if r.ResourceID.Type() != ResourceTypeEmail {
		return errors.New("this operation only works on email resources")
	}
	return nil
}

ale's avatar
ale committed
670
671
// DeleteEmailAlias removes an alias from an email resource.
func (s *AccountService) DeleteEmailAlias(ctx context.Context, tx TX, req *DeleteEmailAliasRequest) error {
672
	return s.handleResourceRequest(ctx, tx, req, s.authResource(req.ResourceRequestBase), func(ctx context.Context, r *Resource) error {
ale's avatar
ale committed
673
674
675
676
677
678
679
		var aliases []string
		for _, a := range r.Email.Aliases {
			if a != req.Addr {
				aliases = append(aliases, a)
			}
		}
		r.Email.Aliases = aliases
680
		if err := tx.UpdateResource(ctx, r); err != nil {
ale's avatar
ale committed
681
			return newBackendError(err)
682
683
684
685
		}

		s.audit.Log(ctx, r.ID, fmt.Sprintf("removed alias %s", req.Addr))
		return nil
ale's avatar
ale committed
686
687
688
	})
}

689
690
691
692
693
694
695
696
// CreateResourcesRequest is the request type for AccountService.CreateResources().
type CreateResourcesRequest struct {
	SSO       string      `json:"sso"`
	Resources []*Resource `json:"resources"`
}

// CreateResourcesResponse is the response type for AccountService.CreateResources().
type CreateResourcesResponse struct {
ale's avatar
ale committed
697
698
699
700
701
	// Resources to create. All must either be global resources
	// (no user ownership), or belong to the same user.
	Resources []*Resource `json:"resources"`
}

ale's avatar
ale committed
702
// ApplyTemplate fills in default values for the resources in the request.
ale's avatar
ale committed
703
func (req *CreateResourcesRequest) ApplyTemplate(ctx context.Context, _ TX, s *AccountService, user *User) error {
ale's avatar
ale committed
704
705
706
	for _, r := range req.Resources {
		s.resourceTemplates.applyTemplate(ctx, r, user)
	}
ale's avatar
ale committed
707
	return nil
ale's avatar
ale committed
708
709
}

710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
// ValidationUser returns the user to be used for validation purposes.
func (req *CreateResourcesRequest) ValidationUser(ctx context.Context, tx TX) *User {
	// 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(req.Resources) > 0 {
		if username := req.Resources[0].ID.User(); username != "" {
			u, err := getUserOrDie(ctx, tx, username)
			if err != nil {
				return nil
			}
			user := *u
			user.Resources = mergeResources(u.Resources, req.Resources)
			return &user
		}
	}
	return nil
}

ale's avatar
ale committed
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
// Validate the request.
func (req *CreateResourcesRequest) Validate(ctx context.Context, s *AccountService, user *User) error {
	var owner string
	if user != nil {
		owner = user.Name
	}
	for _, r := range req.Resources {
		// Check same-user ownership.
		if r.ID.User() != owner {
			return errors.New("resources owned by different users")
		}

		// Validate the resource.
		if err := s.resourceValidator.validateResource(ctx, r, user); err != nil {
			log.Printf("validation error while creating resource %+v: %v", r, err)
			return err
		}
	}
	return nil
751
752
753
754
755
}

// CreateResources can create one or more resources.
func (s *AccountService) CreateResources(ctx context.Context, tx TX, req *CreateResourcesRequest) (*CreateResourcesResponse, error) {
	var resp CreateResourcesResponse
756
	err := s.handleAdminRequest(ctx, tx, req, req.SSO, func(ctx context.Context) error {
757
		for _, r := range req.Resources {
ale's avatar
ale committed
758
			if err := tx.CreateResource(ctx, r); err != nil {
ale's avatar
ale committed
759
				return newBackendError(err)
760
			}
ale's avatar
ale committed
761
			s.audit.Log(ctx, r.ID, "resource created")
ale's avatar
ale committed
762
			resp.Resources = append(resp.Resources, r)
763
764
765
766
767
768
		}
		return nil
	})
	return &resp, err
}

ale's avatar
ale committed
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
// 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 is the request type for AccountService.CreateUser().
ale's avatar
ale committed
785
786
787
788
789
type CreateUserRequest struct {
	SSO  string `json:"sso"`
	User *User  `json:"user"`
}

790
791
792
793
794
// NewContext returns a new Context providing log context data.
func (req *CreateUserRequest) NewContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, userCtxKey, req.User.Name)
}

ale's avatar
ale committed
795
// ApplyTemplate fills in default values for the resources in the request.
ale's avatar
ale committed
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
func (req *CreateUserRequest) ApplyTemplate(ctx context.Context, tx TX, s *AccountService, _ *User) error {
	// Some fields should be unset because there are specific
	// methods to modify those attributes.
	req.User.Has2FA = false
	req.User.HasEncryptionKeys = false
	req.User.PasswordRecoveryHint = ""
	req.User.AppSpecificPasswords = nil
	if req.User.Lang == "" {
		req.User.Lang = "en"
	}

	// Allocate a new user ID.
	uid, err := tx.NextUID(ctx)
	if err != nil {
		return err
	}
	req.User.UID = uid

	// Apply templates to all resources in the request.
ale's avatar
ale committed
815
	for _, r := range req.User.Resources {
ale's avatar
ale committed
816
		s.resourceTemplates.applyTemplate(ctx, r, req.User)
ale's avatar
ale committed
817
	}
ale's avatar
ale committed
818
	return nil
ale's avatar
ale committed
819
820
}

ale's avatar
ale committed
821
// Validate the request.
822
func (req *CreateUserRequest) Validate(ctx context.Context, s *AccountService, _ *User) error {
ale's avatar
ale committed
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
	// Validate the user *and* all resources.
	if err := s.userValidator(ctx, req.User); err != nil {
		log.Printf("validation error while creating user %+v: %v", req.User, err)
		return err
	}
	for _, r := range req.User.Resources {
		if err := s.resourceValidator.validateResource(ctx, r, req.User); err != nil {
			log.Printf("validation error while creating resource %+v: %v", r, err)
			return err
		}
	}

	return nil
}

ale's avatar
ale committed
838
// CreateUserResponse is the response type for AccountService.CreateUser().
ale's avatar
ale committed
839
type CreateUserResponse struct {
840
841
	User     *User  `json:"user,omitempty"`
	Password string `json:"password"`
ale's avatar
ale committed
842
843
}

ale's avatar
ale committed
844
// CreateUser creates a new user along with the associated resources.
ale's avatar
ale committed
845
846
func (s *AccountService) CreateUser(ctx context.Context, tx TX, req *CreateUserRequest) (*CreateUserResponse, error) {
	var resp CreateUserResponse
847
	err := s.handleAdminRequest(ctx, tx, req, req.SSO, func(ctx context.Context) (err error) {
848
		// Create the user first, along with all the resources.
ale's avatar
ale committed
849
		if err := tx.CreateUser(ctx, req.User); err != nil {
ale's avatar
ale committed
850
			return newBackendError(err)
ale's avatar
ale committed
851
852
		}
		resp.User = req.User
853
854
855
856
857
858
859
860
861
862
863

		// 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).
		newPassword := randomPassword()
		if err := s.changeUserPasswordAndResetEncryptionKeys(ctx, tx, req.User, newPassword); err != nil {
			return err
		}
		resp.Password = newPassword

ale's avatar
ale committed
864
		s.audit.Log(ctx, ResourceID{}, "user created")
865
		for _, r := range req.User.Resources {
ale's avatar
ale committed
866
			s.audit.Log(ctx, r.ID, "resource created")
867
868
869
870
871
872
873
874
			if resourceHasPassword(r) {
				if _, err := s.doResetResourcePassword(ctx, tx, r); err != nil {
					// Just log, don't fail.
					log.Printf("can't set random password for resource %s: %v", r.ID, err)
				}
			}
		}

ale's avatar
ale committed
875
876
877
878
879
		return nil
	})
	return &resp, err
}

880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
// UpdateUserRequest is the request type for AccountService.UpdateUser().
type UpdateUserRequest struct {
	RequestBase

	Lang             string              `json:"lang,omitempty"`
	U2FRegistrations []*u2f.Registration `json:"u2f_registrations,omitempty"`
}

// Validate the request.
func (r *UpdateUserRequest) Validate(_ context.Context, _ *AccountService) error {
	if len(r.U2FRegistrations) > 20 {
		return errors.New("too many U2F registrations")
	}

	// TODO: better validation of the language code!
	if len(r.Lang) > 2 {
		return errors.New("invalid language code")
	}

	return nil
}

// UpdateUser allows the caller to update a (very limited) selected
// set of fields on a User object. It is a catch-all function for very
// simple changes that don't justify their own specialized method.
func (s *AccountService) UpdateUser(ctx context.Context, tx TX, req *UpdateUserRequest) (*User, error) {
	var resp *User
	err := s.handleUserRequest(ctx, tx, req, s.authUser(req.RequestBase), func(ctx context.Context, user *User) error {
		if req.Lang != "" {
			user.Lang = req.Lang
		}

		// TODO: check if this allows the caller to use an
		// empty list to unset the field completely.
		if req.U2FRegistrations != nil {
			user.U2FRegistrations = req.U2FRegistrations
		}

		resp = user
		return tx.UpdateUser(ctx, user)
	})
	return resp, err
}

924
func randomBase64(n int) string {
925
	b := make([]byte, n*3/4)
926
927
928
929
	_, err := rand.Read(b[:])
	if err != nil {
		panic(err)
	}
930
931
932
933
934
935
	return base64.StdEncoding.EncodeToString(b[:])[:n]
}

func randomPassword() string {
	// Create a 16-character password with 4 digits and 2 symbols.
	return password.MustGenerate(16, 4, 2, false, false)
936
937
}

ale's avatar
ale committed
938
func randomAppSpecificPassword() string {
939
940
	// Create a 64-character password with 10 digits and 10 symbols.
	return password.MustGenerate(64, 10, 10, false, false)
ale's avatar
ale committed
941
942
}

943
944
const appSpecificPasswordIDLen = 4

ale's avatar
ale committed
945
func randomAppSpecificPasswordID() string {
946
	return randomBase64(appSpecificPasswordIDLen)
ale's avatar
ale committed
947
}
ale's avatar
ale committed
948
949
950
951
952
953
954
955

func generateTOTPSecret() (string, error) {
	key, err := totp.Generate(totp.GenerateOpts{})
	if err != nil {
		return "", err
	}
	return key.Secret(), nil
}