|
import requests |
|
import json |
|
from dataclasses import dataclass |
|
import betterproto |
|
import base64 |
|
|
|
deviceId = "REDACTED" |
|
spatula_header = 'REDACTED' |
|
tier_app_cert = '27DE32A86E091DCDAA4F1E4209D9FAB96F014520' # Signature of the Package (getSignature(mActivity.getPackageManager(), packageName);) |
|
tier_api_key = 'iPtAHWdOVLEtgkaymXoMHVVg' |
|
tier_app_name = 'com.tier.app' |
|
tier_app_id = "1:511116665713:android:949b292378442e73" |
|
tier_sender_id = "511116665713" |
|
tier_app_key = "AIzaSyDBNn1MiG3v9QQBSbM9VmPe8Jwj5pem5pQ" |
|
|
|
tierVersion="3.7.0" |
|
|
|
#Spatula Header consisting of "device_key"? |
|
#/data/app/com.google.android.gms-lu9KLM6y-q82m5WeK79rjA==/oat/arm/base.vdex:device_key |
|
#/data/dalvik-cache/arm/system@app@[email protected]@classes.vdex:device_key |
|
#/system/priv-app/GmsCore/oat/arm/GmsCore.vdex |
|
|
|
|
|
|
|
googleapis_header = { |
|
'Content-Type': 'application/x-protobuf', |
|
'X-Firebase-Locale': "", |
|
'X-Client-Version': 'Android/GmsCore/X19002000/FirebaseCore-Android', |
|
'Accept-Language': 'en-US', |
|
'X-Android-Package': tier_app_name, |
|
'X-Android-Cert': tier_app_cert, |
|
'X-Goog-Spatula': spatula_header, |
|
'User-Agent': 'Mozilla 5.0 (Linux; U; Android 8.1.0; en_US; Moto G (4); Build/OPJ28.111-22); com.google.android.gms/201216018; FastParser/1.1; ApiaryHttpClient/1.0; (gzip) (athene OPJ28.111-22); gzip' |
|
} |
|
|
|
googleapis_params = { |
|
"alt": "json", |
|
"key": tier_app_key |
|
} |
|
|
|
tier_header = { |
|
'customer-agent': 'Tier Android', |
|
'x-api-key': tier_api_key, |
|
'user-agent': f'Tier/{tierVersion} (Android/8.1.0)' |
|
} |
|
|
|
@dataclass |
|
class SimpleMessage(betterproto.Message): |
|
message: str = betterproto.string_field(1) |
|
|
|
@dataclass |
|
class VerifyPhoneNumberCode(betterproto.Message): # i guess i could have just used json, look on github of other implementations |
|
token: str = betterproto.string_field(1) |
|
code: str = betterproto.string_field(3) |
|
|
|
#https://reverseengineering.stackexchange.com/questions/23216/what-is-the-header-x-goog-spatula |
|
@dataclass |
|
class AppCertificate(betterproto.Message): |
|
appname: str = betterproto.string_field(1) |
|
appcertbase64: str = betterproto.string_field(3) |
|
|
|
@dataclass |
|
class GoogleSpatulaHeader(betterproto.Message): |
|
cert: "AppCertificate" = betterproto.message_field(1) |
|
certificatemac: str = betterproto.string_field(2) |
|
deviceId: int = betterproto.sint64_field(3) |
|
unknown3: int = betterproto.sint64_field(4) |
|
unknown4: str = betterproto.string_field(5) |
|
|
|
#https://protogen.marcgravell.com/decode |
|
spathead = GoogleSpatulaHeader() |
|
spathead.cert.appname = tier_app_name |
|
spathead.cert.appcertbase64 = base64.b64encode(bytes.fromhex(tier_app_cert)).decode("utf-8") |
|
spathead.certificatemac = "".join(["X" for i in range(32)]) #placeholder, fixed length, HMACSHA of certificate name + hash |
|
spathead.deviceId = #zigzag encoding of deviceId |
|
spathead.unknown3 = #zigzag encoding of 18 byte stuff? |
|
spathead.unknown4 = "".join(["X" for i in range(73)]) #placeholder, can apparently be of variable length |
|
#"-".join(["{:02x}".format(c).upper() for c in spathead.SerializeToString()]) # for comparing |
|
#spatula = base64.encodebytes(spathead.SerializeToString()).decode("utf-8") |
|
|
|
###END |
|
|
|
## device_key |
|
|
|
@dataclass |
|
class DeviceKey(betterproto.Message): |
|
unknown1: int = betterproto.sint64_field(1) # SecureRandom.nextLong() |
|
deviceId: int = betterproto.sint64_field(3) |
|
unknown2: str = betterproto.string_field(4) # maybe again an HMAC? |
|
unknown3: str = betterproto.string_field(5) # same layout as spathead.unknown4, maybe public key or something? |
|
|
|
def registerFirebaseApp(): # This is already done in MicroG |
|
header = { 'Authorization': 'AidLogin REDACTED:REDACTED', |
|
'content-type': 'application/x-www-form-urlencoded'} |
|
params = { "X-subtype": tier_sender_id, |
|
"sender": tier_sender_id, |
|
"X-appid":"efxEwFgbR8OWKdzhnCyfqg", # where is this from? |
|
"X-scope": "*", |
|
"X-Goog-Firebase-Installations-Auth": "REDACTED", |
|
"app": tier_app_name, |
|
"device": deviceId} |
|
ret = requests.post("https://android.clients.google.com/c2dm/register3", headers = header, params=params) |
|
return ret |
|
|
|
def getFireBaseAuthTokens(): |
|
"""Returns: |
|
{'name': 'projects/511116665713/installations/ezBSnpm_NeALYYlf_j6Sa0', |
|
'fid': 'ezBSnpm_NeALYYlf_j6Sa0', |
|
'refreshToken': 'REDACTED', |
|
'authToken': {'token': 'REDACTED', |
|
'expiresIn': '604800s'}} |
|
""" |
|
jsondata = { |
|
"fid":"cooG6FefRlmSMiSZ5jXXSv", #where is this derived? # random: https://github.com/firebase/firebase-android-sdk/blob/f3709ba3b71453ff34cd31d9f01e68af1db40659/firebase-installations/src/main/java/com/google/firebase/installations/RandomFidGenerator.java#L49 |
|
"appId":"1:511116665713:android:949b292378442e73", #google_app_id |
|
"authVersion":"FIS_v2", |
|
"sdkVersion":"a:16.0.0" |
|
} |
|
headers = { |
|
'Content-Type': 'application/json', |
|
'Accept': 'application/json', |
|
'X-Android-Package': tier_app_name, |
|
'x-firebase-client': 'fire-core/19.3.0 fire-fst/21.4.1 fire-installations/16.0.0 kotlin/1.3.61 fire-iid/20.1.1 fire-android/ fire-analytics/17.2.3 fire-auth/19.2.0', |
|
'x-firebase-client-log-type': '3', |
|
'X-Android-Cert': tier_app_cert, |
|
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 8.1.0; Moto G (4) Build/OPJ28.111-22)', |
|
} |
|
params = { |
|
'key': tier_app_key |
|
} |
|
ret = requests.post('https://firebaseinstallations.googleapis.com/v1/projects/consumer-app-release/installations', json=jsondata, headers=headers, params=params) |
|
return ret |
|
|
|
def sendVerificationCode(phoneNumber): |
|
""" |
|
Returns |
|
{"sessionInfo":""} |
|
""" |
|
data = SimpleMessage(message=phoneNumber).SerializeToString() |
|
ret = requests.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerificationCode',headers=googleapis_header, params=googleapis_params, data=data) |
|
return ret |
|
|
|
def verifyPhoneNumber(token, code): |
|
""" |
|
Returns: |
|
{'idToken': string, |
|
'refreshToken': string, |
|
'expiresIn': int as string, |
|
'localId': string, |
|
'isNewUser': bool, |
|
'phoneNumber': string} |
|
""" |
|
data = VerifyPhoneNumberCode(token=token, code=code).SerializeToString() |
|
ret = requests.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhoneNumber',headers=googleapis_header, params=googleapis_params, data=data) |
|
return ret |
|
|
|
def getAccountInfo(idToken): |
|
""" |
|
Returns: |
|
{'kind': 'identitytoolkit#GetAccountInfoResponse', |
|
'users': [{ 'localId': 'REDACTED', |
|
'providerUserInfo': [{ 'providerId': 'phone', |
|
'rawId': 'REDACTED', |
|
'phoneNumber': 'REDACTED'}], |
|
'lastLoginAt': 'REDACTED', |
|
'createdAt': 'REDACTED', |
|
'phoneNumber': 'REDACTED', |
|
'lastRefreshAt': 'REDACTED'}]} |
|
""" |
|
data = SimpleMessage(message=idToken).SerializeToString() |
|
ret = requests.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo',headers=googleapis_header, params=googleapis_params, data=data) |
|
return ret |
|
|
|
def refreshToken(token): |
|
""" |
|
Returns: |
|
{'access_token': 'REDACTED', |
|
'expires_in': '3600', |
|
'token_type': 'Bearer', |
|
'refresh_token': 'REDACTED', |
|
'user_id': 'REDACTED', |
|
'project_id': '511116665713' |
|
} |
|
|
|
""" |
|
jsondata = {"grant_type": "refresh_token", |
|
"refresh_token": token} |
|
jsonheader = googleapis_header.copy() |
|
jsonheader['Content-Type'] = 'application/json' |
|
ret = requests.post('https://securetoken.googleapis.com/v1/token',headers=jsonheader, params=googleapis_params, json=jsondata) |
|
return ret |
|
|
|
# Tier APIs |
|
|
|
# Registering: |
|
def acceptTermsAndConditions(firebaseidToken): |
|
jsondata = {"termsAndCondition": True} |
|
ret = requests.post('https://consumers.tier-services.io/customer/terms-and-conditions', headers={**tier_header, "x-firebase-auth": firebaseidToken}, json=jsondata) |
|
return ret |
|
|
|
def registerAccount(firstName, lastName, email, phoneNumber, firebaseidToken): |
|
jsondata = {"firstName":firstName, |
|
"lastName":lastName, |
|
"email":email, |
|
"phoneNumber":phoneNumber, |
|
"locale":"en-US"} |
|
ret = requests.post('https://consumers.tier-services.io/customer/registration', headers={**tier_header, "x-firebase-auth": firebaseidToken}, json=jsondata) |
|
return ret |
|
|
|
# Without Auth: |
|
|
|
def checkVersion(): |
|
ret = requests.get(f'https://platform.tier-services.io/v1/check-version/tier-consumer/android/{tierVersion}', headers=tier_header) |
|
return ret |
|
|
|
def getConfig(): |
|
ret = requests.get('https://consumers.tier-services.io/config', headers=tier_header) |
|
return ret |
|
|
|
def getZone(lat, lng): |
|
params = { |
|
"lat": lat, |
|
"lng": lng |
|
} |
|
ret = requests.get('https://platform.tier-services.io/zone', headers=tier_header, params=params) |
|
return ret |
|
|
|
def getFeatures(lat, lng, countryCode="de"): |
|
data = {"location":{"lat":lat, |
|
"lng":lng}, |
|
"traits":{ "country_code":countryCode, |
|
"device_os":"android", |
|
"app_version":tierVersion, |
|
"env":"production"}, |
|
"requestedFeatures":["after_ride_picture", |
|
"paypal", |
|
"text_recognition", |
|
"vehicle_preselection", |
|
"my_tier", |
|
"my_tier_two_buttons", |
|
"telesign", |
|
"in_app_vehicle_reporting", |
|
"incentivized_parking", |
|
"paris_parking", |
|
"pricing_v2", |
|
"user_swapping", |
|
"ride_history", |
|
"driver_license", |
|
"custom_sms_verification_service", |
|
"pause_rental", |
|
"in_app_shop", |
|
"filter_emoped"]} |
|
ret = requests.post('https://features.tier-services.io/allocation', headers={**tier_header, "authorization": "Token cAb8lATpDV1fQfYqG0oHkaZOKF90pmxa"}, params=params) |
|
return ret |
|
|
|
def getVehicles(lat, lng, radius=508, types=["escooter", "emoped"]): |
|
params = { |
|
"lat": lat, |
|
"lng": lng, |
|
"radius": radius, |
|
"type[]": types |
|
} |
|
ret = requests.get('https://platform.tier-services.io/v2/vehicle', headers=tier_header, params=params) |
|
return ret |
|
|
|
def getPricing(vehicleId): |
|
params = { |
|
"vehicleId": vehicleId |
|
} |
|
ret = requests.get('https://platform.tier-services.io/v2/pricing', headers=tier_header, params=params) |
|
return ret |
|
|
|
# With Auth: |
|
|
|
def getCustomerData(firebaseidToken): |
|
ret = requests.get('https://consumers.tier-services.io/customer', headers={**tier_header, "x-firebase-auth": firebaseidToken}) |
|
return ret |
|
|
|
def getPaymentMethod(firebaseidToken): |
|
ret = requests.get('https://platform.tier-services.io/v1/payment-method', headers={**tier_header, "x-firebase-auth": "Bearer "+firebaseidToken}) |
|
return ret |
|
|
|
def getCurrentRental(firebaseidToken): |
|
ret = requests.get('https://platform.tier-services.io/v1/rental/current', headers={**tier_header, "x-firebase-auth": "Bearer "+firebaseidToken}) |
|
return ret |
|
|
|
def getWallets(firebaseidToken): |
|
ret = requests.get('https://platform.tier-services.io/v1/wallets', headers={**tier_header, "x-firebase-auth": "Bearer "+firebaseidToken}) |
|
return ret |