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 }