github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libpages/config/config_v1.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package config
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"io"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"golang.org/x/time/rate"
    16  )
    17  
    18  // V1 defines a V1 config. Public fields are accessible by `json`
    19  // encoders and decoder.
    20  //
    21  // On first call to GetPermission* methods, it initializes an internal per-path
    22  // config reader. If the object is constructed from ParseConfig, its internal
    23  // per-path config reader is initialized automatically. Any changes to the
    24  // PerPathConfigs fields afterwards have no effect.
    25  type V1 struct {
    26  	Common
    27  
    28  	// Users is a [username -> bcrypt-hashed password] map that defines how
    29  	// users should be authenticated.
    30  	Users map[string]string `json:"users"`
    31  
    32  	users map[string]password
    33  
    34  	bcryptLimiter *rate.Limiter
    35  
    36  	// ACLs is deprecated, and kept around for back-compability. Now it serves
    37  	// as an alias to PerPathConfigs. If both ACLs and PerPathConfigs are
    38  	// present, it's a parsing error.
    39  	ACLs map[string]PerPathConfigV1 `json:"acls,omitempty"`
    40  
    41  	// PerPathConfigs is a path -> PerPathConfig map to configure parameters
    42  	// for individual paths. Configured paths apply to their sub paths too.
    43  	PerPathConfigs map[string]PerPathConfigV1 `json:"per_path_configs"`
    44  
    45  	initOnce                    sync.Once
    46  	perPathConfigsReader        *perPathConfigsReaderV1
    47  	perPathConfigsReaderInitErr error
    48  }
    49  
    50  var _ Config = (*V1)(nil)
    51  
    52  // DefaultV1 returns a default V1 config, which allows anonymous read to
    53  // everything.
    54  func DefaultV1() *V1 {
    55  	v1 := &V1{
    56  		Common: Common{
    57  			Version: Version1Str,
    58  		},
    59  		PerPathConfigs: map[string]PerPathConfigV1{
    60  			"/": {
    61  				AnonymousPermissions: "read",
    62  			},
    63  		},
    64  	}
    65  	_ = v1.EnsureInit() // TODO: check error?
    66  	return v1
    67  }
    68  
    69  const bcryptRateLimitInterval = time.Second / 2
    70  
    71  func (c *V1) checkAndRenameACLsIfNeeded() error {
    72  	if c.PerPathConfigs != nil && c.ACLs != nil {
    73  		return ErrACLsPerPathConfigsBothPresent{}
    74  	}
    75  	if c.ACLs != nil {
    76  		c.PerPathConfigs = c.ACLs
    77  		c.ACLs = nil
    78  	}
    79  	return nil
    80  }
    81  
    82  func (c *V1) init() {
    83  	c.bcryptLimiter = rate.NewLimiter(rate.Every(bcryptRateLimitInterval), 1)
    84  
    85  	c.perPathConfigsReaderInitErr = c.checkAndRenameACLsIfNeeded()
    86  	if c.perPathConfigsReaderInitErr != nil {
    87  		return
    88  	}
    89  	c.perPathConfigsReader, c.perPathConfigsReaderInitErr =
    90  		makePerPathConfigsReaderV1(c.PerPathConfigs, c.Users)
    91  	if c.perPathConfigsReaderInitErr != nil {
    92  		return
    93  	}
    94  
    95  	c.users = make(map[string]password)
    96  	for username, passwordHash := range c.Users {
    97  		c.users[username], c.perPathConfigsReaderInitErr = newPassword(passwordHash)
    98  		if c.perPathConfigsReaderInitErr != nil {
    99  			return
   100  		}
   101  	}
   102  }
   103  
   104  // EnsureInit initializes c, and returns any error encountered during the
   105  // initialization. Additionally, it also moves ACLs into PerPathConfigs if
   106  // needed.
   107  //
   108  // It is not necessary to call EnsureInit. Methods that need it do it
   109  // automatically.
   110  func (c *V1) EnsureInit() error {
   111  	c.initOnce.Do(c.init)
   112  	return c.perPathConfigsReaderInitErr
   113  }
   114  
   115  // Version implements the Config interface.
   116  func (c *V1) Version() Version {
   117  	return Version1
   118  }
   119  
   120  // Authenticate implements the Config interface.
   121  func (c *V1) Authenticate(ctx context.Context, username, cleartextPassword string) bool {
   122  	if c.EnsureInit() != nil {
   123  		return false
   124  	}
   125  
   126  	p, ok := c.users[username]
   127  	if !ok {
   128  		return false
   129  	}
   130  	match, err := p.check(ctx, c.bcryptLimiter, cleartextPassword)
   131  	return err == nil && match
   132  }
   133  
   134  // GetPermissions implements the Config interface.
   135  func (c *V1) GetPermissions(path string, username *string) (
   136  	read, list bool,
   137  	possibleRead, possibleList bool,
   138  	realm string, err error) {
   139  	if err = c.EnsureInit(); err != nil {
   140  		return false, false, false, false, "", err
   141  	}
   142  
   143  	perms, maxPerms, realm := c.perPathConfigsReader.getPermissions(path, username)
   144  	return perms.read, perms.list, maxPerms.read, maxPerms.list, realm, nil
   145  }
   146  
   147  // GetAccessControlAllowOrigin implements the Config interface.
   148  func (c *V1) GetAccessControlAllowOrigin(path string) (setting string, err error) {
   149  	if err = c.EnsureInit(); err != nil {
   150  		return "", err
   151  	}
   152  	return c.perPathConfigsReader.getSetAccessControlAllowOrigin(path), nil
   153  }
   154  
   155  // Encode implements the Config interface.
   156  func (c *V1) Encode(w io.Writer, prettify bool) error {
   157  	encoder := json.NewEncoder(w)
   158  	if prettify {
   159  		encoder.SetIndent("", strings.Repeat(" ", 2))
   160  	}
   161  	return encoder.Encode(c)
   162  }
   163  
   164  // Validate checks all public fields of c, and returns an error if any of them
   165  // is invalid, or a nil-error if they are all valid.
   166  //
   167  // Although changes to per-path config fields have no effect on per-path config
   168  // checkings once the internal per-path config reader is intialized (see
   169  // comment on V1), this method still checks the updated per-path config fields.
   170  // So it's OK to use Validate directly on a *V1 that has been modified since it
   171  // was initialized.
   172  //
   173  // As a result, unlike other methods on the type, this method is not goroutine
   174  // safe against changes to the public fields.
   175  func (c *V1) Validate() (err error) {
   176  	if err := c.checkAndRenameACLsIfNeeded(); err != nil {
   177  		return err
   178  	}
   179  	_, err = makePerPathConfigsReaderV1(c.PerPathConfigs, c.Users)
   180  	return err
   181  }
   182  
   183  // HasBcryptPasswords checks if any password hash in the config is a bcrypt
   184  // hash. This method is temporary for migration and will go away.
   185  func (c *V1) HasBcryptPasswords() (bool, error) {
   186  	if err := c.EnsureInit(); err != nil {
   187  		return false, err
   188  	}
   189  	for _, pass := range c.users {
   190  		if pass.passwordType() == passwordTypeBcrypt {
   191  			return true, nil
   192  		}
   193  	}
   194  	return false, nil
   195  }