def removeusercard(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception('Invalid card number!') user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception('Cannot find user to update!') # Remove it from the user's account g.data.local.user.destroy_card(userid, cardid) # Return new card list return { 'cards': [ CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) ], }
def addusercard(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception('Invalid card number!') user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception('Cannot find user to update!') # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: raise Exception('This card is already in use!') # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { 'cards': [ CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) ], }
def test_internal_cipher(self) -> None: test_ciphers = [ ([0x68, 0xFC, 0xA5, 0x27, 0x00, 0x01, 0x04, 0xE0], [0xC7, 0xD0, 0xB3, 0x85, 0xAD, 0x1F, 0xD9, 0x49]), ([0x2C, 0x10, 0xA6, 0x27, 0x00, 0x01, 0x04, 0xE0], [0x33, 0xC6, 0xE6, 0x2E, 0x6E, 0x33, 0x38, 0x74]), ] for pair in test_ciphers: inp = bytes(pair[0]) out = bytes(pair[1]) encoded = CardCipher._encode(inp) # type: ignore self.assertEqual(encoded, out, f"Card encode {encoded} doesn't match expected {out}") decoded = CardCipher._decode(out) # type: ignore self.assertEqual(decoded, inp, f"Card decode {decoded} doesn't match expected {inp}")
def test_external_cipher(self) -> None: test_cards = [ ("S6E523E30ZK7ML1P", "E004010027A5FC68"), ("78B592HZSM9E6712", "E004010027A6102C"), ] for card in test_cards: back = card[0] db = card[1] decoded = CardCipher.decode(back) self.assertEqual(decoded, db, f"Card DB {decoded} doesn't match expected {db}") encoded = CardCipher.encode(db) self.assertEqual(encoded, back, f"Card back {encoded} doesn't match expected {back}")
def addcard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card['number']) except CardCipherException: raise Exception('Invalid card number!') # Make sure it is our card userid = g.data.local.user.from_username(card['owner']) if userid is None: raise Exception('Cannot find user to add card to!') # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: raise Exception('This card is already in use!') # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { 'cards': [format_card(card) for card in g.data.local.user.get_all_cards()], }
def searchusers() -> Dict[str, Any]: # Grab card, convert it searchdetails = request.get_json()['user_search'] if len(searchdetails['card']) > 0: try: cardid = CardCipher.decode(searchdetails['card']) actual_userid = g.data.local.user.from_cardid(cardid) if actual_userid is None: # Force a non-match below actual_userid = UserID(-1) except CardCipherException: actual_userid = UserID(-1) else: actual_userid = None def match(user: User) -> bool: if actual_userid is not None: return user.id == actual_userid else: return True return { 'users': [ format_user(user) for user in g.data.local.user.get_all_users() if match(user) ], }
def listcards() -> Dict[str, Any]: # Return new card list cards = [ CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID) ] return { 'cards': cards, }
def main() -> None: parser = argparse.ArgumentParser( description= "A utility to convert between card IDs and back-of-card characters.") parser.add_argument( "number", help="card ID or back-of-card characters to convert.", type=str, ) args = parser.parse_args() try: print(CardCipher.decode(args.number)) except CardCipherException: try: back = CardCipher.encode(args.number) print(" ".join([back[i:(i + 4)] for i in range(0, len(back), 4)])) except CardCipherException: print('Bad card ID or back-of-card characters!')
def handle_system_convcardnumber_request(self, request: Node) -> Node: cardid = request.child_value('data/card_id') cardnumber = CardCipher.encode(cardid) system = Node.void('system') data = Node.void('data') system.add_child(data) system.add_child(Node.s32('result', 0)) data.add_child(Node.string('card_number', cardnumber)) return system
def addcard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception('Invalid card number!') # See if it is already claimed userid = g.data.local.user.from_cardid(cardid) if userid is not None: raise Exception('This card is already in use!') # Add it to this user's account g.data.local.user.add_card(g.userID, cardid) # Return new card list cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return { 'cards': cards, }
def removecard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception('Invalid card number!') # Make sure it is our card userid = g.data.local.user.from_cardid(cardid) if userid != g.userID: raise Exception('This card is not yours to delete!') # Remove it from this user's account g.data.local.user.destroy_card(g.userID, cardid) # Return new card list cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return { 'cards': cards, }
def viewcards() -> Response: cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return render_react( 'Card Management', 'account/cards.react.js', { 'cards': cards, }, { 'addcard': url_for('account_pages.addcard'), 'removecard': url_for('account_pages.removecard'), 'listcards': url_for('account_pages.listcards'), }, )
def addbalance(arcadeid: int) -> Dict[str, Any]: credits = request.get_json()['credits'] card = request.get_json()['card'] # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None: raise Exception('Unable to find arcade to update!') if g.userID not in arcade.owners: raise Exception('You don\'t own this arcade, refusing to update!') try: cardid = CardCipher.decode(card) userid = g.data.local.user.from_cardid(cardid) except CardCipherException: userid = None if userid is None: raise Exception('Unable to find user by this card!') # Update balance balance = g.data.local.user.update_balance(userid, arcadeid, credits) if balance is not None: g.data.local.network.put_event( 'paseli_transaction', { 'delta': credits, 'balance': balance, 'reason': 'arcade operator adjustment', }, userid=userid, arcadeid=arcadeid, ) return { 'balances': { balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid) }, 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, 'events': [ format_event(event) for event in g.data.local.network.get_events( arcadeid=arcadeid, event='paseli_transaction') ], }
def format_card(card: Tuple[str, Optional[UserID]]) -> Dict[str, Any]: owner = None if card[1] is not None: user = g.data.local.user.get_user(card[1]) if user is not None: owner = user.username try: return { 'number': CardCipher.encode(card[0]), 'owner': owner, 'id': card[1], } except CardCipherException: return { 'number': '????????????????', 'owner': owner, 'id': card[1], }
def removecard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()['card'] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception('Invalid card number!') # Make sure it is our card userid = g.data.local.user.from_cardid(cardid) # Remove it from the user's account g.data.local.user.destroy_card(userid, cardid) # Return new card list return { 'cards': [format_card(card) for card in g.data.local.user.get_all_cards()], }
def verify_gametop_get_pdata(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node gametop = Node.void('gametop') call.add_child(gametop) gametop.set_attribute('method', 'get_pdata') retry = Node.s32('retry', 0) gametop.add_child(retry) data = Node.void('data') gametop.add_child(data) player = Node.void('player') data.add_child(player) player.add_child(Node.string('refid', ref_id)) player.add_child(Node.string('datid', ref_id)) player.add_child(Node.string('uid', card_id)) player.add_child(Node.string('card_no', CardCipher.encode(card_id))) # Swap with server resp = self.exchange('', call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp)
def handle_eacoin_request(self, request: Node) -> Optional[Node]: """ Handle PASELI requests. The game will check out a session at the beginning of the game, make PASELI purchases against that session, and then close it ad the end of of a game. This handler ensures that this works for all games. """ method = request.attribute('method') if not self.config['paseli']['enabled']: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") root = Node.void('eacoin') root.set_attribute('status', str(Status.NOT_ALLOWED)) return root if method == 'checkin': root = Node.void('eacoin') cardid = request.child_value('cardid') pin = request.child_value('passwd') if cardid is None or pin is None: # Refuse to return anything print("Invalid eacoin checkin request, missing cardid or pin") root.set_attribute('status', str(Status.NO_PROFILE)) return root userid = self.data.local.user.from_cardid(cardid) if userid is None: # Refuse to do anything print("No user for eacoin checkin request") root.set_attribute('status', str(Status.NO_PROFILE)) return root valid = self.data.local.user.validate_pin(userid, pin) if not valid: # Refuse to do anything print("User entered invalid pin for eacoin checkin request") root.set_attribute('status', str(Status.INVALID_PIN)) return root session = self.data.local.user.create_session(userid) if self.config['paseli']['infinite']: balance = PASELIHandler.INFINITE_PASELI_AMOUNT else: if self.config['machine']['arcade'] is None: # There's no arcade for this machine, but infinite is not # enabled, so there's no way to find a balance. balance = 0 else: balance = self.data.local.user.get_balance(userid, self.config['machine']['arcade']) root.add_child(Node.s16('sequence', 0)) root.add_child(Node.u8('acstatus', 0)) root.add_child(Node.string('acid', 'DUMMY_ID')) root.add_child(Node.string('acname', 'DUMMY_NAME')) root.add_child(Node.s32('balance', balance)) root.add_child(Node.string('sessid', session)) return root if method == 'opcheckin': root = Node.void('eacoin') passwd = request.child_value('passwd') if passwd is None: # Refuse to return anything print("Invalid eacoin checkin request, missing passwd") root.set_attribute('status', str(Status.NO_PROFILE)) return root if self.config['machine']['arcade'] is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") root.set_attribute('status', str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: # Refuse to do anything print("No arcade for operator checkin request") root.set_attribute('status', str(Status.NO_PROFILE)) return root if arcade.pin != passwd: # Refuse to do anything print("User entered invalid pin for operator checkin request") root.set_attribute('status', str(Status.INVALID_PIN)) return root session = self.data.local.machine.create_session(arcade.id) root.add_child(Node.string('sessid', session)) return root elif method == 'consume': def make_resp(status: int, balance: int) -> Node: root = Node.void('eacoin') root.add_child(Node.u8('acstatus', status)) root.add_child(Node.u8('autocharge', 0)) root.add_child(Node.s32('balance', balance)) return root session = request.child_value('sessid') payment = request.child_value('payment') service = request.child_value('service') details = request.child_value('detail') if session is None or payment is None: # Refuse to do anything print("Invalid eacoin consume request, missing sessid or payment") return make_resp(2, 0) userid = self.data.local.user.from_session(session) if userid is None: # Refuse to do anything print("Invalid session for eacoin consume request") return make_resp(2, 0) if self.config['paseli']['infinite']: balance = PASELIHandler.INFINITE_PASELI_AMOUNT - payment else: if self.config['machine']['arcade'] is None: # There's no arcade for this machine, but infinite is not # enabled, so there's no way to find a balance, assume failed # consume payment. balance = None else: # Look up the new balance based on this delta. If there isn't enough, # we will end up returning None here and exit without performing. balance = self.data.local.user.update_balance(userid, self.config['machine']['arcade'], -payment) if balance is None: print("Not enough balance for eacoin consume request") return make_resp(1, self.data.local.user.get_balance(userid, self.config['machine']['arcade'])) else: self.data.local.network.put_event( 'paseli_transaction', { 'delta': -payment, 'balance': balance, 'service': -service, 'reason': details, 'pcbid': self.config['machine']['pcbid'], }, userid=userid, arcadeid=self.config['machine']['arcade'], ) return make_resp(0, balance) elif method == 'getlog': root = Node.void('eacoin') sessid = request.child_value('sessid') logtype = request.child_value('logtype') target = request.child_value('target') limit = request.child_value('perpage') offset = request.child_value('offset') # Try to determine whether its a user or an arcade session userid = self.data.local.user.from_session(sessid) if userid is None: arcadeid = self.data.local.machine.from_session(sessid) else: arcadeid = None # Bail out if we don't have any idea what session this is if userid is None and arcadeid is None: print("Unable to determine session type") return root # If we're a user session, also look up the current arcade # so we display only entries that happened on this arcade. if userid is not None: arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: print("Machine doesn't belong to an arcade") return root arcadeid = arcade.id # Now, look up all transactions for this specific group events = self.data.local.network.get_events( userid=userid, arcadeid=arcadeid, event='paseli_transaction', ) # Further filter it down to the current PCBID events = [event for event in events if event.data.get('pcbid') == target] # Grab the end of day today as a timestamp end_of_today = Time.end_of_today() time_format = '%Y-%m-%d %H:%M:%S' date_format = '%Y-%m-%d' # Set up common structure lognode = Node.void(logtype) topic = Node.void('topic') lognode.add_child(topic) summary = Node.void('summary') lognode.add_child(summary) # Display what day we are summed to topic.add_child(Node.string('sumdate', Time.format(Time.now(), date_format))) if logtype == 'last7days': # We show today in the today total, last 7 days prior in the week total beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_week = beginning_of_today beginning_of_week = end_of_week - Time.SECONDS_IN_WEEK topic.add_child(Node.string('sumfrom', Time.format(beginning_of_week, date_format))) topic.add_child(Node.string('sumto', Time.format(end_of_week, date_format))) today_total = sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_today and event.timestamp < end_of_today ]) today_total = sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_today and event.timestamp < end_of_today ]) week_txns = [ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_week and event.timestamp < end_of_week ] week_total = sum(week_txns) if len(week_txns) > 0: week_avg = int(sum(week_txns) / len(week_txns)) else: week_avg = 0 # We display the totals for each day starting with yesterday and up through 7 days prior. # Index starts at 0 = yesterday, 1 = the day before, etc... items = [] for days in range(0, 7): end_of_day = end_of_week - (days * Time.SECONDS_IN_DAY) start_of_day = end_of_day - Time.SECONDS_IN_DAY items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= start_of_day and event.timestamp < end_of_day ])) topic.add_child(Node.s32('today', today_total)) topic.add_child(Node.s32('average', week_avg)) topic.add_child(Node.s32('total', week_total)) summary.add_child(Node.s32_array('items', items)) if logtype == 'last52weeks': # Start one week back, since the operator can look at last7days for newer stuff. beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_52_weeks = beginning_of_today - Time.SECONDS_IN_WEEK topic.add_child(Node.string('sumfrom', Time.format(end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format))) topic.add_child(Node.string('sumto', Time.format(end_of_52_weeks, date_format))) # We index backwards, where index 0 = the first week back, 1 = the next week back after that, etc... items = [] for weeks in range(0, 52): end_of_range = end_of_52_weeks - (weeks * Time.SECONDS_IN_WEEK) beginning_of_range = end_of_range - Time.SECONDS_IN_WEEK items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= beginning_of_range and event.timestamp < end_of_range ])) summary.add_child(Node.s32_array('items', items)) if logtype == 'eachday': start_ts = Time.now() end_ts = Time.now() weekdays = [0] * 7 for event in events: event_day = Time.days_into_week(event.timestamp) weekdays[event_day] = weekdays[event_day] - event.data.get_int('delta') if event.timestamp < start_ts: start_ts = event.timestamp topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) summary.add_child(Node.s32_array('items', weekdays)) if logtype == 'eachhour': start_ts = Time.now() end_ts = Time.now() hours = [0] * 24 for event in events: event_hour = int((event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR) hours[event_hour] = hours[event_hour] - event.data.get_int('delta') if event.timestamp < start_ts: start_ts = event.timestamp topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) summary.add_child(Node.s32_array('items', hours)) if logtype == 'detail': history = Node.void('history') lognode.add_child(history) # Respect details paging if offset is not None: events = events[offset:] if limit is not None: events = events[:limit] # Output the details themselves for event in events: card_no = '' if event.userid is not None: user = self.data.local.user.get_user(event.userid) if user is not None: cards = self.data.local.user.get_cards(user.id) if len(cards) > 0: card_no = CardCipher.encode(cards[0]) item = Node.void('item') history.add_child(item) item.add_child(Node.string('date', Time.format(event.timestamp, time_format))) item.add_child(Node.s32('consume', -event.data.get_int('delta'))) item.add_child(Node.s32('service', -event.data.get_int('service'))) item.add_child(Node.string('cardtype', '')) item.add_child(Node.string('cardno', ' ' * self.paseli_padding + card_no)) item.add_child(Node.string('title', '')) item.add_child(Node.string('systemid', '')) if logtype == 'lastmonths': year, month, _ = Time.todays_date() this_month = Time.timestamp_from_date(year, month) last_month = Time.timestamp_from_date(year, month - 1) month_before = Time.timestamp_from_date(year, month - 2) topic.add_child(Node.string('sumfrom', Time.format(month_before, date_format))) topic.add_child(Node.string('sumto', Time.format(this_month, date_format))) for (start, end) in [(month_before, last_month), (last_month, this_month)]: year, month, _ = Time.date_from_timestamp(start) items = [] for day in range(0, 31): begin_ts = start + (day * Time.SECONDS_IN_DAY) end_ts = begin_ts + Time.SECONDS_IN_DAY if begin_ts >= end: # Passed the end of this month items.append(0) else: # Sum up all the txns for this day items.append(sum([ -event.data.get_int('delta') for event in events if event.timestamp >= begin_ts and event.timestamp < end_ts ])) item = Node.void('item') summary.add_child(item) item.add_child(Node.s32('year', year)) item.add_child(Node.s32('month', month)) item.add_child(Node.s32_array('items', items)) root.add_child(Node.u8('processing', 0)) root.add_child(lognode) return root elif method == 'opchpass': root = Node.void('eacoin') oldpass = request.child_value('passwd') newpass = request.child_value('newpasswd') if oldpass is None or newpass is None: # Refuse to return anything print("Invalid eacoin pass change request, missing passwd") root.set_attribute('status', str(Status.NO_PROFILE)) return root if self.config['machine']['arcade'] is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") root.set_attribute('status', str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config['machine']['arcade']) if arcade is None: # Refuse to do anything print("No arcade for operator pass change request") root.set_attribute('status', str(Status.NO_PROFILE)) return root if arcade.pin != oldpass: # Refuse to do anything print("User entered invalid pin for operator pass change request") root.set_attribute('status', str(Status.INVALID_PIN)) return root arcade.pin = newpass self.data.local.machine.put_arcade(arcade) return root elif method == 'checkout': session = request.child_value('sessid') if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.user.destroy_session(session) root = Node.void('eacoin') return root elif method == 'opcheckout': session = request.child_value('sessid') if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.machine.destroy_session(session) root = Node.void('eacoin') return root # Invalid method return None
def register() -> Response: card_number = request.form['card_number'] pin = request.form['pin'] username = request.form['username'] email = request.form['email'] password1 = request.form['password1'] password2 = request.form['password2'] # First, try to convert the card to a valid E004 ID try: cardid = CardCipher.decode(card_number) except CardCipherException: error('Invalid card number!') return register_display(card_number, username, email) # Now, see if this card ID exists already userid = g.data.local.user.from_cardid(cardid) if userid is None: error('This card has not been used on the network yet!') return register_display(card_number, username, email) # Now, make sure this user doesn't already have an account user = g.data.local.user.get_user(userid) if user.username is not None or user.email is not None: error('This card is already in use!') return register_display(card_number, username, email) # Now, see if the pin is correct if not g.data.local.user.validate_pin(userid, pin): error('The entered PIN does not match the PIN on the card!') return register_display(card_number, username, email) # Now, see if the username is valid if not valid_username(username): error('Invalid username!') return register_display(card_number, username, email) # Now, check whether the username is already in use if g.data.local.user.from_username(username) is not None: error('The chosen username is already in use!') return register_display(card_number, username, email) # Now, see if the email address is valid if not valid_email(email): error('Invalid email address!') return register_display(card_number, username, email) # Now, make sure that the passwords match if password1 != password2: error('Passwords do not match each other!') return register_display(card_number, username, email) # Now, make sure passwords are long enough if len(password1) < 6: error('Password is not long enough!') return register_display(card_number, username, email) # Now, create the account. user.username = username user.email = email g.data.local.user.put_user(user) g.data.local.user.update_password(userid, password1) # Now, log them into that created account! aes = AESCipher(g.config['secret_key']) sessionID = g.data.local.user.create_session(userid) success('Successfully registered account!') response = make_response(redirect(url_for('home_pages.viewhome'))) response.set_cookie('SessionID', aes.encrypt(sessionID)) return response
def __format_card(card: str) -> str: try: return CardCipher.encode(card) except CardCipherException: return '????????????????'