async def connect_institution( institution: ConnectInstitution ) -> ConnectInstitutionResponse: client = OFXClient( url=institution.url, appid=institution.app_id, appver=institution.app_ver, language=institution.language, userid=institution.username, org=institution.fi_org, fid=institution.fi_id, version=institution.ofx_version, bankid=institution.bank_id, brokerid=institution.broker_id, clientuid=institution.client_uuid, close_elements=institution.close_elements ) data = (client.request_accounts( password=institution.password, dtacctup=institution.datetime_request ).read()).decode('UTF-8') return ConnectInstitutionResponse(ofx_data=data)
def testSignonVersion102(self): # CLIENTUID wasn't defined until OFX version 1.0.3, # so it returns None if if initialized. client = OFXClient("http://example.com", userid="porkypig", clientuid="DEADBEEF", version=102) signon = client.signon("t0ps3kr1t") self.assertIsInstance(signon, SIGNONMSGSRQV1) signon = signon.sonrq self.assertIsNone(signon.clientuid)
def fetch(config, fileobj): username, password, accts = config client = OFXClient('https://ofx.fidelity.com/ftgw/OFX/clients/download', userid=username.value, org='fidelity.com', fid='7776', brokerid='fidelity.com') accts = accts.value.split(',') resp = client.request_statements( password.value, *[InvStmtRq(acctid=acct) for acct in accts]) fileobj.write(resp.read().decode())
def testRequestStatementsPrettyprint(self): client = OFXClient( "https://example.com/ofx", userid="elmerfudd", org="FIORG", fid="FID", version=103, prettyprint=True, bankid="123456789", brokerid="example.com", ) data = self._testRequest(client.request_statements, "t0ps3kr1t", self.stmtRq0) request = ("OFXHEADER:100\r\n" "DATA:OFXSGML\r\n" "VERSION:103\r\n" "SECURITY:NONE\r\n" "ENCODING:USASCII\r\n" "CHARSET:NONE\r\n" "COMPRESSION:NONE\r\n" "OLDFILEUID:NONE\r\n" "NEWFILEUID:DEADBEEF\r\n" "\r\n" "<OFX>\n" " <SIGNONMSGSRQV1>\n" " <SONRQ>\n" " <DTCLIENT>20170401000000.000[0:GMT]</DTCLIENT>\n" " <USERID>elmerfudd</USERID>\n" " <USERPASS>t0ps3kr1t</USERPASS>\n" " <LANGUAGE>ENG</LANGUAGE>\n" " <FI>\n" " <ORG>FIORG</ORG>\n" " <FID>FID</FID>\n" " </FI>\n" " <APPID>{appid}</APPID>\n" " <APPVER>{appver}</APPVER>\n" " </SONRQ>\n" " </SIGNONMSGSRQV1>\n" " <BANKMSGSRQV1>\n" " <STMTTRNRQ>\n" " <TRNUID>DEADBEEF</TRNUID>\n" " <STMTRQ>\n" " <BANKACCTFROM>\n" " <BANKID>123456789</BANKID>\n" " <ACCTID>111111</ACCTID>\n" " <ACCTTYPE>CHECKING</ACCTTYPE>\n" " </BANKACCTFROM>\n" " <INCTRAN>\n" " <DTSTART>20170101000000.000[0:GMT]</DTSTART>\n" " <DTEND>20170331000000.000[0:GMT]</DTEND>\n" " <INCLUDE>Y</INCLUDE>\n" " </INCTRAN>\n" " </STMTRQ>\n" " </STMTTRNRQ>\n" " </BANKMSGSRQV1>\n" "</OFX>\n").format(appid=DEFAULT_APPID, appver=DEFAULT_APPVER) self.assertEqual(data, request)
def client(self): client = OFXClient( "https://example.com/ofx", userid="elmerfudd", org="FIORG", fid="FID", version=103, bankid="123456789", brokerid="example.com", persist_cookies= False, # TESTME - need to test `persist_cookies`=True ) # Mock out OFXClient._get_service_urls(), which hits the Internet. client._get_service_urls = Mock( return_value={StmtRq: "https://example.com/ofx"}) return client
def _scan_profile( url: str, org: Optional[str], fid: Optional[str], useragent: Optional[str], gen_newfileuid: Optional[bool], max_workers: Optional[int] = None, timeout: Optional[float] = None, ) -> ScanResults: """ Report permutations of OFX version/prettyprint/unclosedelements that successfully download OFX profile from server. Returns a 3-tuple of (OFXv1 results, OFXv2 results, signoninfo), each type(dict). OFX results provide ``ofxget`` configs that will work to make a basic OFX connection. SIGNONINFO reports further information that may be helpful to authenticate successfully. """ logger.info((f"Scanning url={url} org={org} fid={fid} " f"max_workers={max_workers} timeout={timeout}")) client = OFXClient(url, org=org, fid=fid, useragent=useragent) futures = _queue_scans(client, gen_newfileuid, max_workers, timeout) # The primary data we keep is actually the metadata (i.e. connection # parameters - OFX version; prettyprint; unclosedelements) tagged on # the Future by _queue_scans() that gave us a successful OFX connection. success_params: FormatMap = defaultdict(list) # If possible, we also parse out some data from SIGNONINFO included in # the PROFRS. signoninfo: SignoninfoReport = {} # Assume that SIGNONINFO is the same for each successful OFX PROFRS. # Tell _read_scan_response() to stop parsing out SIGNONINFO once # it's successfully extracted one. for future in concurrent.futures.as_completed(futures): version, format = futures[future] valid, signoninfo_ = _read_scan_response(future, not signoninfo) if not valid: continue if not signoninfo and signoninfo_: signoninfo = signoninfo_ logger.debug( (f"OFX connection success, version={version}, format={format}")) success_params[version].append(format) v1_result, v2_result = [ collate_scan_results(ver) for ver in utils.partition( lambda it: it[0] >= 200, success_params.items()) ] # V2 always has closing tags for elements; just report prettyprint for fmt in v2_result["formats"]: assert not fmt["unclosedelements"] del fmt["unclosedelements"] results = (v1_result, v2_result, signoninfo) logger.info(f"Scan results: {results}") return results
def ofx_client(self): return OFXClient(self.ofx_endpoint, userid=self.user_id, org=self.org, fid=self.fid, version=self.version, bankid=self.bank_id)
def testSignonEmptyFIORG(self): client = OFXClient("http://example.com", userid="porkypig") with patch("ofxtools.Client.OFXClient.dtclient") as mock_dtclient: mock_dtclient.return_value = datetime(2017, 4, 1, tzinfo=UTC) signon = client.signon("t0ps3kr1t") self.assertIsInstance(signon, SIGNONMSGSRQV1) signon = signon.sonrq self.assertIsInstance(signon, SONRQ) self.assertEqual(signon.dtclient, datetime(2017, 4, 1, tzinfo=UTC)) self.assertEqual(signon.userid, "porkypig") self.assertEqual(signon.userpass, "t0ps3kr1t") self.assertEqual(signon.language, "ENG") self.assertIsNone(signon.fi) self.assertIsNone(signon.sesscookie) self.assertEqual(signon.appid, client.appid) self.assertEqual(signon.appver, client.appver) self.assertIsNone(signon.clientuid)
def client(self): return OFXClient( "https://example.com/ofx", org="FIORG", fid="FID", version=103, bankid="123456789", brokerid="example.com", )
def testRequestStatementsUnclosedTagsOFXv2(self): # OFX version 2 (XML) doesn't allow unclosed tags. # This is already checked by OFXClient.__init__(), so to test this # illegal state we have to be sneaky and change the attribute after # instantiation client = OFXClient( "https://example.com/ofx", userid="elmerfudd", org="FIORG", fid="FID", version=203, close_elements=True, bankid="123456789", brokerid="example.com", ) client.close_elements = False with self.assertRaises(ValueError): self._testRequest(client.request_statements, "t0ps3kr1t", self.stmtRq0)
def testUnclosedTagsOFXv2(self): """ OFXv2 (XML) doesn't support unclosed tags """ with self.assertRaises(ValueError): OFXClient( "https://example.com/ofx", userid="elmerfudd", org="FIORG", fid="FID", version=203, close_elements=False, bankid="123456789", brokerid="example.com", )
def testRequestProfile(self): client = OFXClient( "https://example.com/ofx", org="FIORG", fid="FID", version=103, bankid="123456789", brokerid="example.com", ) data = self._testRequest(client.request_profile) request = ( "OFXHEADER:100\r\n" "DATA:OFXSGML\r\n" "VERSION:103\r\n" "SECURITY:NONE\r\n" "ENCODING:USASCII\r\n" "CHARSET:NONE\r\n" "COMPRESSION:NONE\r\n" "OLDFILEUID:NONE\r\n" "NEWFILEUID:DEADBEEF\r\n" "\r\n" "<OFX>" "<SIGNONMSGSRQV1>" "<SONRQ>" "<DTCLIENT>20170401000000.000[0:GMT]</DTCLIENT>" "<USERID>anonymous00000000000000000000000</USERID>" "<USERPASS>anonymous00000000000000000000000</USERPASS>" "<LANGUAGE>ENG</LANGUAGE>" "<FI>" "<ORG>FIORG</ORG>" "<FID>FID</FID>" "</FI>" "<APPID>{appid}</APPID>" "<APPVER>{appver}</APPVER>" "</SONRQ>" "</SIGNONMSGSRQV1>" "<PROFMSGSRQV1>" "<PROFTRNRQ>" "<TRNUID>DEADBEEF</TRNUID>" "<PROFRQ>" "<CLIENTROUTING>NONE</CLIENTROUTING>" "<DTPROFUP>19900101000000.000[0:GMT]</DTPROFUP>" "</PROFRQ>" "</PROFTRNRQ>" "</PROFMSGSRQV1>" "</OFX>" ).format(appid=DEFAULT_APPID, appver=DEFAULT_APPVER) self.assertEqual(data, request)
def download(self, config): """Setup account info and credentials.""" client = OFXClient(config['url'], config['org'], config['fid'], version=config['version'], appid=config['appid'], appver=config['appver']) account = [BankAcct(config['fid'], config['acctnum'], config['type'])] kwargs = make_date_kwargs(config) request = client.statement_request(config['ofxuser'], config['ofxpswd'], account, **kwargs) response = client.download(request) fname = '{}_{}.ofx'.format(config['fid'], config['acctnum']) with open(fname, 'w') as ofxfile: print(response.text, file=ofxfile) return fname
def init_client(args: ArgsType) -> OFXClient: """ Initialize OFXClient with connection info from args """ client = OFXClient( args["url"], userid=args["user"] or None, clientuid=args["clientuid"] or None, org=args["org"] or None, fid=args["fid"] or None, version=args["version"], appid=args["appid"] or None, appver=args["appver"] or None, language=args["language"] or None, prettyprint=args["pretty"], close_elements=not args["unclosedelements"], bankid=args["bankid"] or None, brokerid=args["brokerid"] or None, ) logger.debug(f"Initialized {client}") return client
def testRequestStatementsUnclosedTags(self): client = OFXClient( "https://example.com/ofx", userid="elmerfudd", org="FIORG", fid="FID", version=103, close_elements=False, bankid="123456789", brokerid="example.com", persist_cookies=False, ) # Mock out OFXClient._get_service_urls(), which hits the Internet. client._get_service_urls = Mock( return_value={StmtRq: "https://example.com/ofx"}) data = self._testRequest(client.request_statements, "t0ps3kr1t", self.stmtRq0) request = ("OFXHEADER:100\r\n" "DATA:OFXSGML\r\n" "VERSION:103\r\n" "SECURITY:NONE\r\n" "ENCODING:USASCII\r\n" "CHARSET:NONE\r\n" "COMPRESSION:NONE\r\n" "OLDFILEUID:NONE\r\n" "NEWFILEUID:DEADBEEF\r\n" "\r\n" "<OFX>" "<SIGNONMSGSRQV1>" "<SONRQ>" "<DTCLIENT>20170401000000.000[0:GMT]" "<USERID>elmerfudd" "<USERPASS>t0ps3kr1t" "<LANGUAGE>ENG" "<FI>" "<ORG>FIORG" "<FID>FID" "</FI>" "<APPID>{appid}" "<APPVER>{appver}" "</SONRQ>" "</SIGNONMSGSRQV1>" "<BANKMSGSRQV1>" "<STMTTRNRQ>" "<TRNUID>DEADBEEF" "<STMTRQ>" "<BANKACCTFROM>" "<BANKID>123456789" "<ACCTID>111111" "<ACCTTYPE>CHECKING" "</BANKACCTFROM>" "<INCTRAN>" "<DTSTART>20170101000000.000[0:GMT]" "<DTEND>20170331000000.000[0:GMT]" "<INCLUDE>Y" "</INCTRAN>" "</STMTRQ>" "</STMTTRNRQ>" "</BANKMSGSRQV1>" "</OFX>").format(appid=DEFAULT_APPID, appver=DEFAULT_APPVER) self.assertEqual(data, request)