Handle authentication errors better.
[fulbank.git] / netbank
CommitLineData
7f511828
FT
1#!/usr/bin/python3
2
6532632d 3import sys, os, getopt, pwd, operator
7f511828
FT
4from fulbank import auth
5
6sesstype = None
7sess = None
8
6532632d
FT
9def pfxmatch(pfx, item):
10 return str(item)[:len(pfx)] == pfx
11
12class ambiguous(LookupError):
13 def __init__(self, a, b):
14 super().__init__("ambigous match: %s and %s" % (a, b))
15 self.a = a
16 self.b = b
17
18def find(seq, *, item=None, test=None, match=None, key=None, default=LookupError):
7f511828
FT
19 if key is None:
20 key = lambda o: o
21 if match is None and item is not None:
6532632d
FT
22 match = lambda o: test(item, o)
23 if test is None:
24 test = operator.eq
25 found = None
7f511828
FT
26 for thing in seq:
27 if match(key(thing)):
6532632d
FT
28 if found is None:
29 found = thing
30 else:
31 if default is LookupError:
32 raise ambiguous(key(found), key(thing))
33 else:
34 return default
35 if found is not None:
36 return found
7f511828
FT
37 if default is LookupError:
38 raise LookupError()
39 else:
40 return default
41
42def usage(out):
df34dfd3 43 out.write("usage: netbank [-h] BANK-ID COMMAND [ARGS...]\n")
7f511828
FT
44
45def requiresess(fn):
46 def wrap(cmd, args):
47 if sess is None:
48 sys.stderr.write("netbank: %s: no current session\n" % (cmd))
49 sys.exit(1)
50 return fn(cmd, args)
51 return wrap
52
53commands = {}
54
55def cmd_login(cmd, args):
56 global sess
57 if len(args) < 1:
58 sys.stderr.write("usage: login TYPE\n")
59 sys.exit(1)
60 sess = sesstype.create()
61 if args[0] == "bankid":
62 if len(args) < 2:
63 sys.stderr.write("usage: login bankid USER-ID\n")
64 sys.exit(1)
65 with auth.ttyconv() as conv:
f99c3f74
FT
66 try:
67 sess.auth_bankid(args[1], conv)
68 except auth.autherror as err:
69 sys.stderr.write("netbank: authentication failed: %s\n" % err)
70 sys.exit(1)
7f511828
FT
71 else:
72 sys.stderr.write("netbank: %s: unknown authentication type\n" % (args[0]))
73 sys.exit(1)
74commands["login"] = cmd_login
75
76@requiresess
77def cmd_logout(cmd, args):
d6d73d98 78 global sess
7f511828
FT
79 if sess is not None:
80 sess.close()
81 sess = None
82commands["logout"] = cmd_logout
83
84@requiresess
85def cmd_ping(cmd, args):
86 sess.keepalive()
87commands["ping"] = cmd_ping
88
89@requiresess
90def cmd_lsacct(cmd, args):
91 for acct in sess.accounts:
92 sys.stdout.write("%s (%s): %s\n" % (acct.number, acct.name, acct.balance))
93commands["lsacct"] = cmd_lsacct
94
95@requiresess
96def cmd_lstxn(cmd, args):
97 opts, args = getopt.getopt(args, "n:")
98 num = 10
99 for o, a in opts:
100 if o == "-n":
101 num = int(a)
102 if len(args) < 1:
103 sys.stderr.write("usage: lstxn [-n NUM] ACCOUNT\n")
104 sys.exit(1)
105 try:
6532632d
FT
106 acct = find(sess.accounts, item=args[0], key=lambda acct: acct.number, test=pfxmatch)
107 except ambiguous as exc:
108 sys.stderr.write("netbank: %s: ambiguous match between %s and %s\n" % (args[0], exc.a, exc.b))
109 sys.exit(1)
7f511828
FT
110 except LookupError:
111 sys.stderr.write("netbank: %s: no such account\n" % (args[0]))
112 sys.exit(1)
113 for i, txn in zip(range(num), acct.transactions()):
114 sys.stdout.write("%s %s: %s\n" % (txn.date.isoformat(), txn.value, txn.message))
115commands["lstxn"] = cmd_lstxn
116
117def main():
118 global sess, sesstype
119
120 opts, args = getopt.getopt(sys.argv[1:], "h")
121 for o, a in opts:
122 if o == "-h":
123 usage(sys.stdout)
124 sys.exit(0)
125 if len(args) < 2:
126 usage(sys.stderr)
127 sys.exit(1)
128
129 if args[0] == "fsb":
130 import fulbank.fsb
131 sesstype = fulbank.fsb.session
132 else:
133 sys.stderr.write("netbank: %s: unknown bank id\n" % (args[0]))
134 sys.exit(1)
135 sesspath = os.path.join(pwd.getpwuid(os.getuid()).pw_dir, ".cache/fulbank", args[0])
136 cmd = args[1]
137 args = args[2:]
138
139 if os.path.exists(sesspath):
140 sess = sesstype.load(sesspath)
141 else:
142 sess = None
143 if cmd in commands:
144 commands[cmd](cmd, args)
145 else:
146 sys.stderr.write("netbank: %s: unknown command\n" % (cmd))
147 sys.exit(1)
148 if sess is not None:
149 sessdir = os.path.dirname(sesspath)
150 if not os.path.isdir(sessdir):
151 os.makedirs(sessdir)
152 sess.save(sesspath)
153 else:
154 if os.path.exists(sesspath):
155 os.unlink(sesspath)
156
157try:
158 if __name__ == "__main__":
159 main()
160except KeyboardInterrupt:
161 sys.exit(1)