Search code examples
pythonflaskpython-requestspython-multithreading

I'm getting the wrong request sent to wrong account when using python requests


I'm using a Python requests library for a Flask app, which is hosted on Heroku. I implemented a task using threading which will fetch the data from the other app indefinitely until the user stops it by themselves. In theory, each user has their own access token, and refresh token used for the api call, but somehow, sometimes the api request is sent to wrong account. For example, if the user A and B both run the app, then the user A got an email saying they got an item X, but when the user A checks on the other app, it doesn't show item X, but the user B got that item X instead. What could possibly be wrong here? I suspect the way I use the requests, but I can't think of any reason why. The confusing part is it uses correct user setting to fetch the items, and only "try" to get the item that satisfy the settings, but the Item is just sent to the wrong account.

def process_search():
    user = request.user
    user.update_user_running_status(True)
    user.set_running_session()

    user.refresh()
    
    # Custom class I made to take the user info, which has token information for the other app
    itemHandler = Handler(user)

    thread = Thread(target=itemHandler.start)
    thread.daemon = True
    thread.start()

    running_status = user.running_status
    current_running_session = user.current_running_session

    return jsonify({'is_running': running_status, "current_running_session": current_running_session}), 200
class Handler:
    allHeaders = {
        "TokenRequest": {
            "x-acme-identity-auth-domain": "api.acme.com",
            "User-Agent": "Acme Device Acme/0.0/iOS/15.2/iPhone",
        },
        "AllRequest": {
            "Accept": "application/json",
            "x-acme-access-token": None,
            "X-Acme-Date": None,
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "en-US",
            "Content-Type": "application/json",
            "User-Agent": "iOS/16.1 (iPhone Darwin) Model/iPhone Platform/iPhone14,2 AcmeOS/2.112.2",
            "Connection": "keep-alive",
        },
    }

    def __init__(self, user: User) -> None:
        self.user = user
       
        self.requestHeaders = Handler.allHeaders.get("AllRequest")
        self.refreshToken = user.acme_refresh_token
        self.accessToken = user.acme_access_token

        if self.refreshToken == "":
            self.registerAccount()

        self.requestHeaders["x-acme-access-token"] = self.accessToken
        self.requestHeaders["X-Acme-Date"] = self.getDate()

    def registerAccount(self):
        auth_response = self.user.auth_response

        parsed_query = parse_qs(urlparse(auth_response).query)
        reg_access_token = unquote(parsed_query['openid.oa2.access_token'][0])
        device_id = secrets.token_hex(16)
        acme_reg_data = {
            "auth_data": {"access_token": reg_access_token},
            "cookies": {"domain": ".acme.com", "website_cookies": []},
            "device_metadata": {
                "android_id": "52aee8aecab31ee3",
                "device_os_family": "android",
                "device_serial": device_id,
                "device_type": "A1MPSLFC7L5AFK",
                "mac_address": secrets.token_hex(64).upper(),
                "manufacturer": MANUFACTURER,
                "model": DEVICE_NAME,
                "os_version": "30",
                "product": DEVICE_NAME,
            },
            "registration_data": {
                "app_name": APP_NAME,
                "app_version": APP_VERSION,
                "device_model": DEVICE_NAME,
                "device_serial": device_id,
                "device_type": "A1MPSLFC7L5AFK",
                "domain": "Device",
                "os_version": OS_VERSION,
                "software_version": "130050002",
            },
            "requested_extensions": ["device_info", "customer_info"],
            "requested_token_type": [
                "bearer",
                "mac_dms",
                "store_authentication_cookie",
                "website_cookies",
            ],
            "user_context_map": {"frc": self.generate_frc(device_id)},
        }

        reg_headers = {
            "Content-Type": "application/json",
            "Accept-Charset": "utf-8",
            "x-acme-identity-auth-domain": "api.acme.com",
            "Connection": "keep-alive",
            "Accept": "*/*",
            "Accept-Language": "en-US",
        }
        res = requests.post(
            Handler.routes.get("GetAuthToken"),
            json=acme_reg_data,
            headers=reg_headers,
            verify=True,
        )
        if res.status_code != 200:
            print("login failed")
            exit(1)

        res = res.json()
        tokens = res['response']['success']['tokens']['bearer']
        self.accessToken = tokens['access_token']
        self.refreshToken = tokens['refresh_token']
     
        self.user.update_acme_token(self.accessToken, self.refreshToken)

    def start(self):
        while True:
            if not self.user.running_status:
                Offer(self.user).delete_temp_offers()
                break
            # fetch data
          
            response = requests.post(
                self.routes.get("getItems"),
                headers=self.requestHeaders,
                json={
                    "apiVersion": "V2",
                }
            )

I suspected the requests.Session, so I change it to regular requests, but the issue still occurs.


Solution

  • This is a classic Python bug, just hidden in a clever way.

    When you do this:

            self.requestHeaders = Handler.allHeaders.get("AllRequest")
    

    You're not making a COPY of that dictionary. Every object that does that will get a reference to that SAME dictionary. When you modify the dictionary to set the current user's token, you are modifying them all.

    If you change this to:

            self.requestHeaders = Handler.allHeaders["AllRequest"].copy()
    

    Or, better yet, just initialize the dictionary in the __init__, then your problems should go away.