session: functional options for NewSession

For a cleaner interface, adjust the NewSession method of the Session
store such that all optional arguments are functional options.
This commit is contained in:
Elle Mouton 2025-02-27 07:24:27 +02:00
parent 889cdfea71
commit b81742fe9d
No known key found for this signature in database
GPG key ID: D7D916376026F177
5 changed files with 147 additions and 45 deletions

View file

@ -120,10 +120,13 @@ type Session struct {
// buildSession creates a new session with the given user-defined parameters.
func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
created, expiry time.Time, serverAddr string, devServer bool,
perms []bakery.Op, caveats []macaroon.Caveat,
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
flags PrivacyFlags) (*Session, error) {
created, expiry time.Time, serverAddr string,
options ...Option) (*Session, error) {
opts := defaultSessionOptions()
for _, o := range options {
o(opts)
}
_, pairingSecret, err := mailbox.NewPassphraseEntropy()
if err != nil {
@ -135,10 +138,10 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
// The group ID will by default be the same as the Session ID
// unless this session links to a previous session.
groupID := id
if linkedGroupID != nil {
if opts.linkedGroupID != nil {
// If this session is linked to a previous session, then the
// group ID is the same as the linked session's group ID.
groupID = *linkedGroupID
groupID = *opts.linkedGroupID
}
sess := &Session{
@ -149,29 +152,109 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
Expiry: expiry.UTC(),
CreatedAt: created.UTC(),
ServerAddr: serverAddr,
DevServer: devServer,
DevServer: opts.devServer,
MacaroonRootKey: macRootKey,
PairingSecret: pairingSecret,
LocalPrivateKey: localPrivKey,
LocalPublicKey: localPrivKey.PubKey(),
RemotePublicKey: nil,
WithPrivacyMapper: privacy,
PrivacyFlags: flags,
WithPrivacyMapper: opts.privacy,
PrivacyFlags: opts.privacyFlags,
GroupID: groupID,
MacaroonRecipe: opts.macaroonRecipe,
}
if perms != nil || caveats != nil {
sess.MacaroonRecipe = &MacaroonRecipe{
if len(opts.featureConfig) != 0 {
sess.FeatureConfig = &opts.featureConfig
}
return sess, nil
}
// sessionOptions defines various options that can be tweaked via functional
// parameters for session creation.
type sessionOptions struct {
// privacy indicates if a privacy map should be used with this session.
privacy bool
// privacyFlags to use in combination with the session's privacy mapper.
privacyFlags PrivacyFlags
// featureConfig holds any feature configuration bytes to use for this
// session.
featureConfig FeaturesConfig
// linkedGroupID is the ID of the group that this session is linked
// to. By default, a session is not linked to another group.
linkedGroupID *ID
// devServer is true if TLS should be skipped when connecting to the
// mailbox server.
devServer bool
// macaroonRecipe holds the permissions and caveats that should be used
// to bake the macaroon to be used with this session.
macaroonRecipe *MacaroonRecipe
}
// defaultSessionOptions returns a new sessionOptions struct with default
// values set.
func defaultSessionOptions() *sessionOptions {
return &sessionOptions{
privacy: false,
privacyFlags: PrivacyFlags{},
featureConfig: FeaturesConfig{},
linkedGroupID: nil,
devServer: false,
}
}
// Option defines the signature of a functional option that can be used to
// tweak various session creation options.
type Option func(*sessionOptions)
// WithPrivacy can be used to enable the privacy mapper for this session.
// An optional set of privacy flags can be provided to further customize the
// privacy mapper.
func WithPrivacy(flags PrivacyFlags) Option {
return func(o *sessionOptions) {
o.privacy = true
o.privacyFlags = flags
}
}
// WithFeatureConfig can be used to set the feature configuration bytes for
// this session.
func WithFeatureConfig(config FeaturesConfig) Option {
return func(o *sessionOptions) {
o.featureConfig = config
}
}
// WithLinkedGroupID can be used to link this session to a previous session.
func WithLinkedGroupID(groupID *ID) Option {
return func(o *sessionOptions) {
o.linkedGroupID = groupID
}
}
// WithDevServer can be used to set if TLS verification should be skipped when
// connecting to the mailbox server.
func WithDevServer() Option {
return func(o *sessionOptions) {
o.devServer = true
}
}
// WithMacaroonRecipe can be used to set the permissions and caveats that
// should be used to bake the macaroon for a session.
func WithMacaroonRecipe(caveats []macaroon.Caveat, perms []bakery.Op) Option {
return func(o *sessionOptions) {
o.macaroonRecipe = &MacaroonRecipe{
Permissions: perms,
Caveats: caveats,
}
}
if len(featureConfig) != 0 {
sess.FeatureConfig = &featureConfig
}
return sess, nil
}
// IDToGroupIndex defines an interface for the session ID to group ID index.
@ -191,9 +274,7 @@ type Store interface {
// parameters. The session will remain in the StateReserved state until
// ShiftState is called to update the state.
NewSession(label string, typ Type, expiry time.Time, serverAddr string,
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
flags PrivacyFlags) (*Session, error)
opts ...Option) (*Session, error)
// GetSession fetches the session with the given key.
GetSession(key *btcec.PublicKey) (*Session, error)

View file

@ -13,8 +13,6 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/clock"
"go.etcd.io/bbolt"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
var (
@ -188,9 +186,7 @@ func getSessionKey(session *Session) []byte {
//
// NOTE: this is part of the Store interface.
func (db *BoltStore) NewSession(label string, typ Type, expiry time.Time,
serverAddr string, devServer bool, perms []bakery.Op,
caveats []macaroon.Caveat, featureConfig FeaturesConfig, privacy bool,
linkedGroupID *ID, flags PrivacyFlags) (*Session, error) {
serverAddr string, opts ...Option) (*Session, error) {
var session *Session
err := db.Update(func(tx *bbolt.Tx) error {
@ -206,8 +202,7 @@ func (db *BoltStore) NewSession(label string, typ Type, expiry time.Time,
session, err = buildSession(
id, localPrivKey, label, typ, db.clock.Now(), expiry,
serverAddr, devServer, perms, caveats, featureConfig,
privacy, linkedGroupID, flags,
serverAddr, opts...,
)
if err != nil {
return err

View file

@ -374,8 +374,10 @@ func reserveSession(db Store, label string,
return db.NewSession(label, opts.sessType,
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
"foo.bar.baz:1234", true, nil, nil, nil, true, opts.groupID,
[]PrivacyFlag{ClearPubkeys},
"foo.bar.baz:1234",
WithDevServer(),
WithPrivacy(PrivacyFlags{ClearPubkeys}),
WithLinkedGroupID(opts.groupID),
)
}

View file

@ -133,10 +133,12 @@ func TestSerializeDeserializeSession(t *testing.T) {
id, priv, test.name, test.sessType,
time.Now(),
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
"foo.bar.baz:1234", true, test.perms,
test.caveats, test.featureConfig, true,
test.linkedGroupID,
[]PrivacyFlag{ClearPubkeys},
"foo.bar.baz:1234",
WithDevServer(),
WithPrivacy(PrivacyFlags{ClearPubkeys}),
WithMacaroonRecipe(test.caveats, test.perms),
WithFeatureConfig(test.featureConfig),
WithLinkedGroupID(test.linkedGroupID),
)
require.NoError(t, err)
@ -188,8 +190,8 @@ func TestGroupIDForOlderSessions(t *testing.T) {
id, priv, "test-session", TypeMacaroonAdmin,
time.Now(),
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
"foo.bar.baz:1234", true, nil, nil, nil, false, nil,
PrivacyFlags{},
"foo.bar.baz:1234",
WithDevServer(),
)
require.NoError(t, err)
@ -224,8 +226,8 @@ func TestGroupID(t *testing.T) {
id, priv, "test-session", TypeMacaroonAdmin,
time.Now(),
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
"foo.bar.baz:1234", true, nil, nil, nil, false, nil,
PrivacyFlags{},
"foo.bar.baz:1234",
WithDevServer(),
)
require.NoError(t, err)
@ -239,8 +241,9 @@ func TestGroupID(t *testing.T) {
id, priv, "test-session", TypeMacaroonAdmin,
time.Now(),
time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC),
"foo.bar.baz:1234", true, nil, nil, nil, false,
&session1.GroupID, PrivacyFlags{},
"foo.bar.baz:1234",
WithLinkedGroupID(&session1.ID),
WithDevServer(),
)
require.NoError(t, err)

View file

@ -308,10 +308,16 @@ func (s *sessionRpcServer) AddSession(ctx context.Context,
}
}
sessOpts := []session.Option{
session.WithMacaroonRecipe(caveats, uniquePermissions),
}
if req.DevServer {
sessOpts = append(sessOpts, session.WithDevServer())
}
sess, err := s.cfg.db.NewSession(
req.Label, typ, expiry, req.MailboxServerAddr,
req.DevServer, uniquePermissions, caveats, nil, false, nil,
session.PrivacyFlags{},
req.Label, typ, expiry, req.MailboxServerAddr, sessOpts...,
)
if err != nil {
return nil, fmt.Errorf("error creating new session: %v", err)
@ -1125,10 +1131,25 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context,
caveats = append(caveats, firewall.MetaPrivacyCaveat)
}
// Construct the functional options that will be used to create the
// session.
sessOpts := []session.Option{
session.WithMacaroonRecipe(caveats, perms),
session.WithFeatureConfig(clientConfig),
session.WithLinkedGroupID(linkedGroupID),
}
if req.DevServer {
sessOpts = append(sessOpts, session.WithDevServer())
}
if privacy {
sessOpts = append(sessOpts, session.WithPrivacy(privacyFlags))
}
sess, err := s.cfg.db.NewSession(
req.Label, session.TypeAutopilot, expiry,
req.MailboxServerAddr, req.DevServer, perms, caveats,
clientConfig, privacy, linkedGroupID, privacyFlags,
req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr,
sessOpts...,
)
if err != nil {
return nil, fmt.Errorf("error creating new session: %v", err)