lib.rs (15824B)
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::{AssertSqlSafe, 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(AssertSqlSafe(sql)) 188 .execute(&db) 189 .await 190 .unwrap(); 191 }; 192 let execute_fs = async |file: &str| { 193 dbg!(file); 194 sqlx::raw_sql(AssertSqlSafe( 195 std::fs::read_to_string(format!("../database-versioning/{file}")).unwrap(), 196 )) 197 .execute(&db) 198 .await 199 .unwrap(); 200 }; 201 202 // Drop current schemas 203 execute_fs("libeufin-bank-drop.sql").await; 204 execute_fs("libeufin-nexus-drop.sql").await; 205 206 // Versioning 207 execute_fs("versioning.sql").await; 208 209 // libeufin-bank 210 execute_fs("libeufin-bank-0001.sql").await; 211 execute(" 212 INSERT INTO libeufin_bank.customers (login, password_hash) VALUES 213 ('account_1', 'fack_hash'), 214 ('account_2', 'fack_hash'), 215 ('account_3', 'fack_hash'), 216 ('account_4', 'fack_hash'); 217 INSERT INTO libeufin_bank.bank_accounts (internal_payto_uri, owning_customer_id) VALUES 218 ('payto_1', 1), 219 ('payto_2', 2), 220 ('payto_3', 3), 221 ('payto_4', 4); 222 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 223 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1), 224 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1), 225 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1); 226 INSERT INTO libeufin_bank.taler_exchange_incoming(reserve_pub, bank_transaction) VALUES 227 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1), 228 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2); 229 INSERT INTO libeufin_bank.challenges(code, creation_date, expiration_date, retry_counter) VALUES 230 ('secret_code', 42, 42, 42), 231 ('secret_code', 42, 42, 42); 232 INSERT INTO libeufin_bank.cashout_operations(request_uid, amount_debit, amount_credit, subject, creation_time, bank_account, challenge, local_transaction) VALUES 233 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 1, 1), 234 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 2, NULL); 235 INSERT INTO libeufin_bank.taler_withdrawal_operations(withdrawal_uuid, amount, reserve_pub, wallet_bank_account) VALUES 236 (gen_random_uuid(), (0, 0), '\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1), 237 (gen_random_uuid(), (0, 0), '\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2); 238 ").await; 239 execute_fs("libeufin-bank-0002.sql").await; 240 execute_fs("libeufin-bank-0003.sql").await; 241 execute_fs("libeufin-bank-0004.sql").await; 242 execute( 243 " 244 UPDATE libeufin_bank.bank_accounts SET min_cashout=(0, 1) WHERE bank_account_id=2; 245 UPDATE libeufin_bank.bank_accounts SET min_cashout=(2, 300) WHERE bank_account_id IN (3, 4); 246 ", 247 ) 248 .await; 249 execute_fs("libeufin-bank-0005.sql").await; 250 execute_fs("libeufin-bank-0006.sql").await; 251 execute_fs("libeufin-bank-0007.sql").await; 252 execute_fs("libeufin-bank-0008.sql").await; 253 execute_fs("libeufin-bank-0009.sql").await; 254 execute_fs("libeufin-bank-0010.sql").await; 255 execute_fs("libeufin-bank-0011.sql").await; 256 execute_fs("libeufin-bank-0012.sql").await; 257 execute( 258 r#" 259 INSERT INTO libeufin_bank.config(key, value) VALUES 260 ('cashin_ratio', '{"val": 1, "frac": 2}'::jsonb), 261 ('cashin_fee', '{"val": 3, "frac": 4}'::jsonb), 262 ('cashin_tiny_amount', '{"val": 5, "frac": 6}'::jsonb), 263 ('cashin_min_amount', '{"val": 7, "frac": 8}'::jsonb), 264 ('cashin_rounding_mode', '{"mode": "zero"}'::jsonb), 265 ('cashout_ratio', '{"val": 9, "frac": 10}'::jsonb), 266 ('cashout_fee', '{"val": 11, "frac": 12}'::jsonb), 267 ('cashout_tiny_amount', '{"val": 13, "frac": 14}'::jsonb), 268 ('cashout_min_amount', '{"val": 15, "frac": 16}'::jsonb), 269 ('cashout_rounding_mode', '{"mode": "nearest"}'::jsonb); 270 "#, 271 ) 272 .await; 273 execute_fs("libeufin-bank-0013.sql").await; 274 assert!( 275 sqlx::query_scalar::<_, bool>( 276 r#" 277 SELECT value='{ 278 "cashin": { 279 "fee": { 280 "val": 3, 281 "frac": 4 282 }, 283 "ratio": { 284 "val": 1, 285 "frac": 2 286 }, 287 "min_amount": { 288 "val": 7, 289 "frac": 8 290 }, 291 "tiny_amount": { 292 "val": 5, 293 "frac": 6 294 }, 295 "rounding_mode": "zero" 296 }, 297 "cashout": { 298 "fee": { 299 "val": 11, 300 "frac": 12 301 }, 302 "ratio": { 303 "val": 9, 304 "frac": 10 305 }, 306 "min_amount": { 307 "val": 15, 308 "frac": 16 309 }, 310 "tiny_amount": { 311 "val": 13, 312 "frac": 14 313 }, 314 "rounding_mode": "nearest" 315 } 316 }'::jsonb FROM libeufin_bank.config WHERE key='conversion_rate' 317 "# 318 ) 319 .fetch_one(&db) 320 .await 321 .unwrap() 322 ); 323 execute_fs("libeufin-bank-0014.sql").await; 324 execute_fs("libeufin-bank-0015.sql").await; 325 326 // libeufin-nexus 327 execute_fs("libeufin-nexus-0001.sql").await; 328 execute(r#" 329 INSERT INTO libeufin_nexus.outgoing_transactions(amount, execution_time, message_id) VALUES 330 ((0, 0), 42, 'id'); 331 INSERT INTO libeufin_nexus.initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid) VALUES 332 ((0, 0), 'subject', 42, 'payto_0', 1, 'request_uid'); 333 "#, 334 ) 335 .await; 336 execute_fs("libeufin-nexus-0002.sql").await; 337 execute_fs("libeufin-nexus-0003.sql").await; 338 execute_fs("libeufin-nexus-0004.sql").await; 339 execute_fs("libeufin-nexus-0005.sql").await; 340 execute_fs("libeufin-nexus-0006.sql").await; 341 execute(r#" 342 INSERT INTO initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid, order_id) VALUES 343 ((42, 0), 'subject', 0, 'credit_payto', NULL, 'TX0', 'ORDER0'), 344 ((41, 0), 'subject', 0, 'credit_payto', NULL, 'TX1', NULL); 345 "#, 346 ) 347 .await; 348 execute_fs("libeufin-nexus-0007.sql").await; 349 execute_fs("libeufin-nexus-0008.sql").await; 350 execute_fs("libeufin-nexus-0009.sql").await; 351 execute(r#" 352 INSERT INTO libeufin_nexus.incoming_transactions(amount, subject, execution_time, debit_payto, bank_id) VALUES 353 ((1, 0), 'simple', 42, 'debit_payto', 'first'), 354 ((2, 0), 'reserve', 42, 'debit_payto', 'second'), 355 ((3, 0), 'kyc', 42, 'debit_payto', 'third'), 356 ((4, 0), 'simple', 42, 'debit_payto', gen_random_uuid()::text), 357 ((5, 0), 'reserve', 42, 'debit_payto', gen_random_uuid()::text), 358 ((6, 0), 'kyc', 42, 'debit_payto', gen_random_uuid()::text); 359 INSERT INTO libeufin_nexus.talerable_incoming_transactions(incoming_transaction_id, type, reserve_public_key, account_pub) VALUES 360 (2, 'reserve', '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, null), 361 (3, 'kyc', null, '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea); 362 "#, 363 ) 364 .await; 365 execute_fs("libeufin-nexus-0010.sql").await; 366 execute_fs("libeufin-nexus-0011.sql").await; 367 execute_fs("libeufin-nexus-0012.sql").await; 368 execute_fs("libeufin-nexus-0013.sql").await; 369 execute_fs("libeufin-nexus-0014.sql").await; 370 } 371 }