diff --git a/bemani/common/__init__.py b/bemani/common/__init__.py index ad4ebd5..fc6d207 100644 --- a/bemani/common/__init__.py +++ b/bemani/common/__init__.py @@ -9,7 +9,7 @@ from bemani.common.constants import ( BroadcastConstants, RegionConstants, ) -from bemani.common.card import CardCipher, CardCipherException +from bemani.common.card import CardCipher, CardCipherException, decode_user_provided from bemani.common.decorators import debugonly from bemani.common.id import ID from bemani.common.aes import AESCipher @@ -41,5 +41,6 @@ __all__ = [ "InvalidOffsetException", "cache", "debugonly", + "decode_user_provided", "format_recovery_link", ] diff --git a/bemani/common/card.py b/bemani/common/card.py index 1cbfc26..2d5e0ab 100644 --- a/bemani/common/card.py +++ b/bemani/common/card.py @@ -1,7 +1,10 @@ -from typing import Dict, Final, List +from typing import Dict, Final, List, TYPE_CHECKING from Crypto.Cipher import DES3 +if TYPE_CHECKING: + from bemani.data import Config + class CardCipherException(Exception): pass @@ -174,3 +177,17 @@ class CardCipher: checksum = sum(divmod(checksum, 0x20)) return checksum + + +def decode_user_provided(config: "Config", cardid: str) -> str: + # First, try to decode the card ID as if it was encoded. + try: + return CardCipher.decode(cardid) + except CardCipherException: + if not config.server.allow_raw_ids: + raise + + # Now, assume it is already raw. We round trip it because + # this will check for ID validity even with the raw ID. + enc = CardCipher.encode(cardid) + return CardCipher.decode(enc) diff --git a/bemani/data/config.py b/bemani/data/config.py index 67f4722..f2af3e0 100644 --- a/bemani/data/config.py +++ b/bemani/data/config.py @@ -79,6 +79,10 @@ class Server: def pcbid_self_grant_limit(self) -> int: return int(self.__config.get("server", {}).get("pcbid_self_grant_limit", 0)) + @property + def allow_raw_ids(self) -> bool: + return bool(self.__config.get("server", {}).get("allow_raw_ids", False)) + @property def region(self) -> int: region = int(self.__config.get("server", {}).get("region", RegionConstants.USA)) diff --git a/bemani/frontend/account/account.py b/bemani/frontend/account/account.py index ba2de03..91af600 100644 --- a/bemani/frontend/account/account.py +++ b/bemani/frontend/account/account.py @@ -9,7 +9,7 @@ from flask import ( render_template, ) -from bemani.common import CardCipher, CardCipherException, AESCipher, Time +from bemani.common import CardCipher, CardCipherException, AESCipher, Time, decode_user_provided from bemani.frontend.app import ( loginrequired, loginprohibited, @@ -114,7 +114,7 @@ def recover() -> Response: elif card_number: # First, try to convert the card to a valid E004 ID try: - cardid = CardCipher.decode(card_number) + cardid = decode_user_provided(g.config, card_number) except CardCipherException: error("Invalid card number!") return recover_display(username, token, card_number) @@ -197,7 +197,7 @@ def register() -> Response: # First, try to convert the card to a valid E004 ID try: - cardid = CardCipher.decode(card_number) + cardid = decode_user_provided(g.config, card_number) except CardCipherException: error("Invalid card number!") return register_display(card_number, username, email) @@ -335,7 +335,7 @@ def addcard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) except CardCipherException: raise Exception("Invalid card number!") @@ -361,7 +361,7 @@ def removecard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) except CardCipherException: raise Exception("Invalid card number!") diff --git a/bemani/frontend/admin/admin.py b/bemani/frontend/admin/admin.py index be9a1ec..6d947d0 100644 --- a/bemani/frontend/admin/admin.py +++ b/bemani/frontend/admin/admin.py @@ -11,6 +11,7 @@ from bemani.common import ( ValidatedDict, Profile, ID, + decode_user_provided, format_recovery_link, ) from bemani.data import Arcade, Machine, User, UserID, News, Event, Server, Client @@ -816,7 +817,7 @@ def removecard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) except CardCipherException: raise Exception("Invalid card number!") @@ -839,7 +840,7 @@ def addcard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card["number"]) + cardid = decode_user_provided(g.config, card["number"]) except CardCipherException: raise Exception("Invalid card number!") @@ -1098,7 +1099,7 @@ def removeusercard(userid: int) -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) except CardCipherException: raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) @@ -1153,7 +1154,7 @@ def addusercard(userid: int) -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) except CardCipherException: raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) diff --git a/bemani/frontend/arcade/arcade.py b/bemani/frontend/arcade/arcade.py index a7249a2..ee6825b 100644 --- a/bemani/frontend/arcade/arcade.py +++ b/bemani/frontend/arcade/arcade.py @@ -4,11 +4,11 @@ from flask import Blueprint, request, Response, abort, url_for from bemani.backend.base import Base from bemani.common import ( - CardCipher, CardCipherException, ValidatedDict, GameConstants, RegionConstants, + decode_user_provided, ) from bemani.data import Arcade, ArcadeID, Event, Machine from bemani.frontend.app import loginrequired, jsonify, render_react, valid_pin @@ -186,7 +186,7 @@ def addbalance(arcadeid: int) -> Dict[str, Any]: raise Exception("You don't own this arcade, refusing to update!") try: - cardid = CardCipher.decode(card) + cardid = decode_user_provided(g.config, card) userid = g.data.local.user.from_cardid(cardid) except CardCipherException: userid = None diff --git a/config/server.yaml b/config/server.yaml index 897904f..cac384e 100644 --- a/config/server.yaml +++ b/config/server.yaml @@ -36,6 +36,11 @@ server: # page. Note that this setting is irrelevant if PCBID enforcing is off. # Set to 0 or delete this setting to disable self-granting PCBIDs. pcbid_self_grant_limit: 0 + # Whether frontend text boxes that accept a Card ID will accept a raw ID + # starting with E004 or 0XXX. With this enabled, while the frontend will + # still display Card IDs as they appear in-game and on the back of actual + # cards, users can type in the raw ID as a convenience. + allow_raw_ids: False # Default region for this network (set to USA by default). See RegionConstants # for details on acceptible values. The range of accepted values is 1-56 matching # the 56 normal regions found in RegionConstants, and 1000 for "Europe" and