112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package authorization
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
crand "crypto/rand"
|
|
"crypto/sha256"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/influxdata/influxdb/v2"
|
|
"github.com/influxdata/influxdb/v2/kit/platform"
|
|
)
|
|
|
|
// An implementation of influxdb.PasswordsService that will perform
|
|
// ComparePassword requests at a reduced cost under certain
|
|
// conditions. See ComparePassword for further information.
|
|
//
|
|
// The cache is only valid for the duration of the process.
|
|
type CachingPasswordsService struct {
|
|
inner influxdb.PasswordsService
|
|
|
|
mu sync.RWMutex // protects concurrent access to authCache
|
|
authCache map[platform.ID]authUser
|
|
}
|
|
|
|
func NewCachingPasswordsService(inner influxdb.PasswordsService) *CachingPasswordsService {
|
|
return &CachingPasswordsService{inner: inner, authCache: make(map[platform.ID]authUser)}
|
|
}
|
|
|
|
var _ influxdb.PasswordsService = (*CachingPasswordsService)(nil)
|
|
|
|
func (c *CachingPasswordsService) SetPassword(ctx context.Context, id platform.ID, password string) error {
|
|
err := c.inner.SetPassword(ctx, id, password)
|
|
if err == nil {
|
|
c.mu.Lock()
|
|
delete(c.authCache, id)
|
|
c.mu.Unlock()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ComparePassword will attempt to perform the comparison using a lower cost hashing function
|
|
// if influxdb.ContextHasPasswordCacheOption returns true for ctx.
|
|
func (c *CachingPasswordsService) ComparePassword(ctx context.Context, id platform.ID, password string) error {
|
|
c.mu.RLock()
|
|
au, ok := c.authCache[id]
|
|
c.mu.RUnlock()
|
|
if ok {
|
|
// verify the password using the cached salt and hash
|
|
if bytes.Equal(c.hashWithSalt(au.salt, password), au.hash) {
|
|
return nil
|
|
}
|
|
|
|
// fall through to requiring a full bcrypt hash for invalid passwords
|
|
}
|
|
|
|
err := c.inner.ComparePassword(ctx, id, password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if salt, hashed, err := c.saltedHash(password); err == nil {
|
|
c.mu.Lock()
|
|
c.authCache[id] = authUser{salt: salt, hash: hashed}
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *CachingPasswordsService) CompareAndSetPassword(ctx context.Context, id platform.ID, old, new string) error {
|
|
err := c.inner.CompareAndSetPassword(ctx, id, old, new)
|
|
if err == nil {
|
|
c.mu.Lock()
|
|
delete(c.authCache, id)
|
|
c.mu.Unlock()
|
|
}
|
|
return err
|
|
}
|
|
|
|
// NOTE(sgc): This caching implementation was lifted from the 1.x source
|
|
// https://github.com/influxdata/influxdb/blob/c1e11e732e145fc1a356535ddf3dcb9fb732a22b/services/meta/client.go#L390-L406
|
|
|
|
const (
|
|
// SaltBytes is the number of bytes used for salts.
|
|
SaltBytes = 32
|
|
)
|
|
|
|
type authUser struct {
|
|
salt []byte
|
|
hash []byte
|
|
}
|
|
|
|
// hashWithSalt returns a salted hash of password using salt.
|
|
func (c *CachingPasswordsService) hashWithSalt(salt []byte, password string) []byte {
|
|
hasher := sha256.New()
|
|
hasher.Write(salt)
|
|
hasher.Write([]byte(password))
|
|
return hasher.Sum(nil)
|
|
}
|
|
|
|
// saltedHash returns a salt and salted hash of password.
|
|
func (c *CachingPasswordsService) saltedHash(password string) (salt, hash []byte, err error) {
|
|
salt = make([]byte, SaltBytes)
|
|
if _, err := io.ReadFull(crand.Reader, salt); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return salt, c.hashWithSalt(salt, password), nil
|
|
}
|