lib.rs (15727B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use taler_common::config::Config; 21 22 #[cfg(test)] 23 mod integration; 24 25 pub fn full_cfg(base: &str, mem: &str) -> Config { 26 let mut parser = taler_common::config::parser::Parser::empty(); 27 parser 28 .load_env(libeufin_bank::constants::CONFIG_SOURCE) 29 .unwrap(); 30 parser 31 .load_env(libeufin_nexus::constants::CONFIG_SOURCE) 32 .unwrap(); 33 parser 34 .load_env(libeufin_ebisync::constants::CONFIG_SOURCE) 35 .unwrap(); 36 parser.parse_file(base.into(), 0).unwrap(); 37 parser.parse_str(mem).unwrap(); 38 parser.finish() 39 } 40 41 #[cfg(test)] 42 mod test { 43 use std::{fs::Permissions, io::Write, os::unix::fs::PermissionsExt as _, path::PathBuf}; 44 45 use libeufin_ebics::keys::{BankKeys, ClientKeys, persist_bank_keys, persist_client_keys}; 46 use libeufin_nexus::config::{NexusCfg, NexusKeysCfg}; 47 use sqlx::PgPool; 48 use taler_test_utils::cli::clap_parse; 49 50 use crate::full_cfg; 51 52 /** Test error format related to the keying process */ 53 #[tokio::test] 54 async fn keys() { 55 let nexus_cmds = ["ebics-submit", "ebics-fetch"]; 56 let nexus_all_cmds = ["ebics-submit", "ebics-fetch", "ebics-setup"]; 57 let sync_cmds = ["fetch"]; 58 let sync_all_cmds = ["fetch", "setup"]; 59 let cfg = full_cfg("conf/cli.conf", ""); 60 let cfg = NexusCfg::parse(&cfg).unwrap(); 61 let NexusKeysCfg { client, bank } = cfg.keys().unwrap(); 62 63 let c_path = &PathBuf::from(client); 64 let b_path = &PathBuf::from(bank); 65 std::fs::create_dir_all(c_path.parent().unwrap()).unwrap(); 66 std::fs::create_dir_all(b_path.parent().unwrap()).unwrap(); 67 68 let is_root = nix::unistd::Uid::effective().is_root(); 69 70 let check_cmds = async |msg: &str| { 71 for cmd in nexus_cmds { 72 let tmp: libeufin_nexus::Args = clap_parse(cmd).unwrap(); 73 let res = libeufin_nexus::run(&cfg.cfg, tmp.cmd).await.unwrap_err(); 74 assert_eq!( 75 res.to_string(), 76 msg.replace("SETUPCMD", "libeufin-nexus ebics-setup") 77 ); 78 } 79 for cmd in sync_cmds { 80 let tmp: libeufin_ebisync::Args = clap_parse(cmd).unwrap(); 81 let res = libeufin_ebisync::run(&cfg.cfg, tmp.cmd).await.unwrap_err(); 82 assert_eq!( 83 res.to_string(), 84 msg.replace("SETUPCMD", "libeufin-ebisync setup") 85 ); 86 } 87 }; 88 let check_all_cmds = async |msg: &str| { 89 for cmd in nexus_all_cmds { 90 let tmp: libeufin_nexus::Args = clap_parse(cmd).unwrap(); 91 let res = libeufin_nexus::run(&cfg.cfg, tmp.cmd).await.unwrap_err(); 92 assert_eq!( 93 res.to_string(), 94 msg.replace("SETUPCMD", "libeufin-nexus ebics-setup") 95 ); 96 } 97 for cmd in sync_all_cmds { 98 let tmp: libeufin_ebisync::Args = clap_parse(cmd).unwrap(); 99 let res = libeufin_ebisync::run(&cfg.cfg, tmp.cmd).await.unwrap_err(); 100 assert_eq!( 101 res.to_string(), 102 msg.replace("SETUPCMD", "libeufin-ebisync setup") 103 ); 104 } 105 }; 106 107 // Missing client keys 108 std::fs::remove_file(c_path).ok(); 109 check_cmds(&format!( 110 "Missing client private keys file at '{client}', run 'SETUPCMD' first" 111 )) 112 .await; 113 // Empty client file 114 let mut cf = std::fs::File::create(c_path).unwrap(); 115 check_all_cmds(&format!( 116 "Could not read client private keys at '{client}': unexpected end of file" 117 )) 118 .await; 119 // Bad client json 120 cf.write_all(b"CORRUPTION").unwrap(); 121 check_all_cmds(&format!( 122 "Could not read client private keys at '{client}': invalid data" 123 )) 124 .await; 125 if !is_root { 126 // Missing permission 127 cf.set_permissions(Permissions::from_mode(0o000)).unwrap(); 128 check_all_cmds(&format!( 129 "Could not read client private keys at '{client}': permission denied" 130 )) 131 .await; 132 } 133 // Unfinished client 134 let client_keys = ClientKeys::generate().unwrap(); 135 persist_client_keys(&client_keys, c_path).unwrap(); 136 check_cmds("Unsubmitted client private keys, run 'SETUPCMD' first").await; 137 138 // Missing bank keys 139 persist_client_keys( 140 &ClientKeys { 141 submitted_ini: true, 142 submitted_hia: true, 143 ..client_keys 144 }, 145 c_path, 146 ) 147 .unwrap(); 148 std::fs::remove_file(b_path).ok(); 149 check_cmds(&format!( 150 "Missing bank public keys file at '{bank}', run 'SETUPCMD' first" 151 )) 152 .await; 153 // Empty bank file 154 let mut bf = std::fs::File::create(b_path).unwrap(); 155 check_all_cmds(&format!( 156 "Could not read bank public keys at '{bank}': unexpected end of file" 157 )) 158 .await; 159 // Bad bank json 160 bf.write_all(b"CORRUPTION").unwrap(); 161 check_all_cmds(&format!( 162 "Could not read bank public keys at '{bank}': invalid data" 163 )) 164 .await; 165 if !is_root { 166 // Missing permission 167 bf.set_permissions(Permissions::from_mode(0o000)).unwrap(); 168 check_all_cmds(&format!( 169 "Could not read bank public keys at '{bank}': permission denied" 170 )) 171 .await; 172 } 173 // Unfinished bank 174 let bank_keys = BankKeys::generate(); 175 persist_bank_keys(&bank_keys, b_path).unwrap(); 176 check_cmds("Unaccepted bank public keys, run 'SETUPCMD' until accepting the bank keys") 177 .await; 178 } 179 180 #[tokio::test] 181 async fn migration() { 182 let db = PgPool::connect("postgres:///taler_rust_check") 183 .await 184 .unwrap(); 185 186 let execute = async |sql: &str| { 187 sqlx::raw_sql(sql).execute(&db).await.unwrap(); 188 }; 189 let execute_fs = async |file: &str| { 190 dbg!(file); 191 sqlx::raw_sql( 192 &std::fs::read_to_string(format!("../database-versioning/{file}")).unwrap(), 193 ) 194 .execute(&db) 195 .await 196 .unwrap(); 197 }; 198 199 // Drop current schemas 200 execute_fs("libeufin-bank-drop.sql").await; 201 execute_fs("libeufin-nexus-drop.sql").await; 202 203 // Versioning 204 execute_fs("versioning.sql").await; 205 206 // libeufin-bank 207 execute_fs("libeufin-bank-0001.sql").await; 208 execute(" 209 INSERT INTO libeufin_bank.customers (login, password_hash) VALUES 210 ('account_1', 'fack_hash'), 211 ('account_2', 'fack_hash'), 212 ('account_3', 'fack_hash'), 213 ('account_4', 'fack_hash'); 214 INSERT INTO libeufin_bank.bank_accounts (internal_payto_uri, owning_customer_id) VALUES 215 ('payto_1', 1), 216 ('payto_2', 2), 217 ('payto_3', 3), 218 ('payto_4', 4); 219 INSERT INTO libeufin_bank.bank_account_transactions(creditor_payto_uri, creditor_name, debtor_payto_uri, debtor_name, subject, amount, transaction_date, direction, bank_account_id) VALUES 220 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1), 221 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1), 222 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1); 223 INSERT INTO libeufin_bank.taler_exchange_incoming(reserve_pub, bank_transaction) VALUES 224 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1), 225 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2); 226 INSERT INTO libeufin_bank.challenges(code, creation_date, expiration_date, retry_counter) VALUES 227 ('secret_code', 42, 42, 42), 228 ('secret_code', 42, 42, 42); 229 INSERT INTO libeufin_bank.cashout_operations(request_uid, amount_debit, amount_credit, subject, creation_time, bank_account, challenge, local_transaction) VALUES 230 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 1, 1), 231 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 2, NULL); 232 INSERT INTO libeufin_bank.taler_withdrawal_operations(withdrawal_uuid, amount, reserve_pub, wallet_bank_account) VALUES 233 (gen_random_uuid(), (0, 0), '\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1), 234 (gen_random_uuid(), (0, 0), '\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2); 235 ").await; 236 execute_fs("libeufin-bank-0002.sql").await; 237 execute_fs("libeufin-bank-0003.sql").await; 238 execute_fs("libeufin-bank-0004.sql").await; 239 execute( 240 " 241 UPDATE libeufin_bank.bank_accounts SET min_cashout=(0, 1) WHERE bank_account_id=2; 242 UPDATE libeufin_bank.bank_accounts SET min_cashout=(2, 300) WHERE bank_account_id IN (3, 4); 243 ", 244 ) 245 .await; 246 execute_fs("libeufin-bank-0005.sql").await; 247 execute_fs("libeufin-bank-0006.sql").await; 248 execute_fs("libeufin-bank-0007.sql").await; 249 execute_fs("libeufin-bank-0008.sql").await; 250 execute_fs("libeufin-bank-0009.sql").await; 251 execute_fs("libeufin-bank-0010.sql").await; 252 execute_fs("libeufin-bank-0011.sql").await; 253 execute_fs("libeufin-bank-0012.sql").await; 254 execute( 255 r#" 256 INSERT INTO libeufin_bank.config(key, value) VALUES 257 ('cashin_ratio', '{"val": 1, "frac": 2}'::jsonb), 258 ('cashin_fee', '{"val": 3, "frac": 4}'::jsonb), 259 ('cashin_tiny_amount', '{"val": 5, "frac": 6}'::jsonb), 260 ('cashin_min_amount', '{"val": 7, "frac": 8}'::jsonb), 261 ('cashin_rounding_mode', '{"mode": "zero"}'::jsonb), 262 ('cashout_ratio', '{"val": 9, "frac": 10}'::jsonb), 263 ('cashout_fee', '{"val": 11, "frac": 12}'::jsonb), 264 ('cashout_tiny_amount', '{"val": 13, "frac": 14}'::jsonb), 265 ('cashout_min_amount', '{"val": 15, "frac": 16}'::jsonb), 266 ('cashout_rounding_mode', '{"mode": "nearest"}'::jsonb); 267 "#, 268 ) 269 .await; 270 execute_fs("libeufin-bank-0013.sql").await; 271 assert!( 272 sqlx::query_scalar::<_, bool>( 273 r#" 274 SELECT value='{ 275 "cashin": { 276 "fee": { 277 "val": 3, 278 "frac": 4 279 }, 280 "ratio": { 281 "val": 1, 282 "frac": 2 283 }, 284 "min_amount": { 285 "val": 7, 286 "frac": 8 287 }, 288 "tiny_amount": { 289 "val": 5, 290 "frac": 6 291 }, 292 "rounding_mode": "zero" 293 }, 294 "cashout": { 295 "fee": { 296 "val": 11, 297 "frac": 12 298 }, 299 "ratio": { 300 "val": 9, 301 "frac": 10 302 }, 303 "min_amount": { 304 "val": 15, 305 "frac": 16 306 }, 307 "tiny_amount": { 308 "val": 13, 309 "frac": 14 310 }, 311 "rounding_mode": "nearest" 312 } 313 }'::jsonb FROM libeufin_bank.config WHERE key='conversion_rate' 314 "# 315 ) 316 .fetch_one(&db) 317 .await 318 .unwrap() 319 ); 320 execute_fs("libeufin-bank-0014.sql").await; 321 execute_fs("libeufin-bank-0015.sql").await; 322 323 // libeufin-nexus 324 execute_fs("libeufin-nexus-0001.sql").await; 325 execute(r#" 326 INSERT INTO libeufin_nexus.outgoing_transactions(amount, execution_time, message_id) VALUES 327 ((0, 0), 42, 'id'); 328 INSERT INTO libeufin_nexus.initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid) VALUES 329 ((0, 0), 'subject', 42, 'payto_0', 1, 'request_uid'); 330 "#, 331 ) 332 .await; 333 execute_fs("libeufin-nexus-0002.sql").await; 334 execute_fs("libeufin-nexus-0003.sql").await; 335 execute_fs("libeufin-nexus-0004.sql").await; 336 execute_fs("libeufin-nexus-0005.sql").await; 337 execute_fs("libeufin-nexus-0006.sql").await; 338 execute(r#" 339 INSERT INTO initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid, order_id) VALUES 340 ((42, 0), 'subject', 0, 'credit_payto', NULL, 'TX0', 'ORDER0'), 341 ((41, 0), 'subject', 0, 'credit_payto', NULL, 'TX1', NULL); 342 "#, 343 ) 344 .await; 345 execute_fs("libeufin-nexus-0007.sql").await; 346 execute_fs("libeufin-nexus-0008.sql").await; 347 execute_fs("libeufin-nexus-0009.sql").await; 348 execute(r#" 349 INSERT INTO libeufin_nexus.incoming_transactions(amount, subject, execution_time, debit_payto, bank_id) VALUES 350 ((1, 0), 'simple', 42, 'debit_payto', 'first'), 351 ((2, 0), 'reserve', 42, 'debit_payto', 'second'), 352 ((3, 0), 'kyc', 42, 'debit_payto', 'third'), 353 ((4, 0), 'simple', 42, 'debit_payto', gen_random_uuid()::text), 354 ((5, 0), 'reserve', 42, 'debit_payto', gen_random_uuid()::text), 355 ((6, 0), 'kyc', 42, 'debit_payto', gen_random_uuid()::text); 356 INSERT INTO libeufin_nexus.talerable_incoming_transactions(incoming_transaction_id, type, reserve_public_key, account_pub) VALUES 357 (2, 'reserve', '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, null), 358 (3, 'kyc', null, '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea); 359 "#, 360 ) 361 .await; 362 execute_fs("libeufin-nexus-0010.sql").await; 363 execute_fs("libeufin-nexus-0011.sql").await; 364 execute_fs("libeufin-nexus-0012.sql").await; 365 execute_fs("libeufin-nexus-0013.sql").await; 366 execute_fs("libeufin-nexus-0014.sql").await; 367 } 368 }