code.vegaprotocol.io/vega@v0.79.0/wallet/api/connected_wallet.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package api
    17  
    18  import (
    19  	"fmt"
    20  
    21  	"code.vegaprotocol.io/vega/wallet/wallet"
    22  )
    23  
    24  // ConnectedWallet is the projection of the wallet through the permissions
    25  // and authentication system. On a regular wallet, there are no restrictions
    26  // on what we can call, which doesn't fit the model of having allowed
    27  // access, so we wrap the "regular wallet" behind the "connected wallet".
    28  type ConnectedWallet struct {
    29  	// name is the name of the wallet.
    30  	name string
    31  
    32  	// Hostname is the hostname for which the connection is set.
    33  	hostname string
    34  
    35  	// allowedKeys holds the keys that have been selected by the client
    36  	// during the permissions request.
    37  	// The order should match the order of generation in the wallet.
    38  	allowedKeys []AllowedKey
    39  
    40  	// noRestrictions is a hack to know if we should skip permission
    41  	// verification when we are connected with a long-living API token.
    42  	noRestrictions bool
    43  
    44  	canListKeys bool
    45  }
    46  
    47  func (s *ConnectedWallet) Name() string {
    48  	return s.name
    49  }
    50  
    51  // Hostname returns the hostname for which the connection has been set.
    52  // For long-living connections, the hostname is empty  as there is no
    53  // restrictions for that type of connection.
    54  func (s *ConnectedWallet) Hostname() string {
    55  	return s.hostname
    56  }
    57  
    58  // AllowedKeys returns the keys a connection has access to. If a third-party
    59  // application tries to use a keys that does not belong to this set, then the
    60  // request should fail.
    61  func (s *ConnectedWallet) AllowedKeys() []AllowedKey {
    62  	return s.allowedKeys
    63  }
    64  
    65  // RequireInteraction tells if an interaction with the user is needed for
    66  // supervision is required or not.
    67  // It is related to the type of API token that is used for this connection.
    68  // If it's a long-living token, then no interaction is required.
    69  func (s *ConnectedWallet) RequireInteraction() bool {
    70  	return !s.noRestrictions
    71  }
    72  
    73  func (s *ConnectedWallet) CanListKeys() bool {
    74  	if s.noRestrictions {
    75  		return true
    76  	}
    77  	return s.canListKeys
    78  }
    79  
    80  // CanUseKey determines if the permissions allow the specified key to be used.
    81  func (s *ConnectedWallet) CanUseKey(publicKeyToUse string) bool {
    82  	for _, allowedKey := range s.allowedKeys {
    83  		if allowedKey.PublicKey() == publicKeyToUse {
    84  			return true
    85  		}
    86  	}
    87  
    88  	return false
    89  }
    90  
    91  func (s *ConnectedWallet) RefreshFromWallet(freshWallet wallet.Wallet) error {
    92  	if s.noRestrictions {
    93  		s.allowedKeys = allUsableKeys(freshWallet)
    94  		return nil
    95  	}
    96  
    97  	rks, err := allowedKeys(freshWallet, s.hostname)
    98  	if err != nil {
    99  		return fmt.Errorf("could not resolve the allowed keys when refreshing the connection: %w", err)
   100  	}
   101  
   102  	s.canListKeys = rks != nil
   103  	s.allowedKeys = rks
   104  
   105  	return nil
   106  }
   107  
   108  type AllowedKey struct {
   109  	publicKey string
   110  	name      string
   111  }
   112  
   113  func (r AllowedKey) PublicKey() string {
   114  	return r.publicKey
   115  }
   116  
   117  func (r AllowedKey) Name() string {
   118  	return r.name
   119  }
   120  
   121  func NewDisconnectedWallet(hostname, wallet string) ConnectedWallet {
   122  	return ConnectedWallet{
   123  		name:     wallet,
   124  		hostname: hostname,
   125  	}
   126  }
   127  
   128  func NewConnectedWallet(hostname string, w wallet.Wallet) (ConnectedWallet, error) {
   129  	rks, err := allowedKeys(w, hostname)
   130  	if err != nil {
   131  		return ConnectedWallet{}, fmt.Errorf("could not resolve the allowed keys: %w", err)
   132  	}
   133  
   134  	return ConnectedWallet{
   135  		noRestrictions: false,
   136  		canListKeys:    rks != nil,
   137  		allowedKeys:    rks,
   138  		hostname:       hostname,
   139  		name:           w.Name(),
   140  	}, nil
   141  }
   142  
   143  func NewLongLivingConnectedWallet(w wallet.Wallet) ConnectedWallet {
   144  	return ConnectedWallet{
   145  		noRestrictions: true,
   146  		canListKeys:    true,
   147  		allowedKeys:    allUsableKeys(w),
   148  		hostname:       "",
   149  		name:           w.Name(),
   150  	}
   151  }
   152  
   153  func allowedKeys(w wallet.Wallet, hostname string) ([]AllowedKey, error) {
   154  	perms := w.Permissions(hostname)
   155  
   156  	if !perms.PublicKeys.Enabled() {
   157  		return nil, nil
   158  	}
   159  
   160  	if !perms.PublicKeys.HasAllowedKeys() {
   161  		// If there is no allowed keys set for this hostname, we load all valid
   162  		// keys.
   163  		return allUsableKeys(w), nil
   164  	}
   165  
   166  	allowedKeys := make([]AllowedKey, 0, len(perms.PublicKeys.AllowedKeys))
   167  	for _, pubKey := range perms.PublicKeys.AllowedKeys {
   168  		keyPair, err := w.DescribeKeyPair(pubKey)
   169  		if err != nil {
   170  			return nil, fmt.Errorf("could not load the key pair associated to the public key %q: %w", pubKey, err)
   171  		}
   172  		// There is no need to check for the tainted keys, here, as this list
   173  		// should only contain usable keys.
   174  		allowedKeys = append(allowedKeys, AllowedKey{
   175  			publicKey: keyPair.PublicKey(),
   176  			name:      keyPair.Name(),
   177  		})
   178  	}
   179  	return allowedKeys, nil
   180  }
   181  
   182  func allUsableKeys(w wallet.Wallet) []AllowedKey {
   183  	allKeyPairs := w.ListKeyPairs()
   184  	allowedKeys := make([]AllowedKey, 0, len(allKeyPairs))
   185  	for _, keyPair := range allKeyPairs {
   186  		if !keyPair.IsTainted() {
   187  			allowedKeys = append(allowedKeys, AllowedKey{
   188  				publicKey: keyPair.PublicKey(),
   189  				name:      keyPair.Name(),
   190  			})
   191  		}
   192  	}
   193  	return allowedKeys
   194  }