Adding mw40v APi client

This commit is contained in:
2023-10-19 20:19:10 +02:00
parent f09d0823b9
commit ccce1f1248
4 changed files with 157 additions and 12 deletions

View File

@@ -1,2 +1,15 @@
# network-roaming # Network Roaming
Api that allows to switch a router from landline to 4G and back
## Alcatel MW40V configuration
### Find the encryption key
in default.html:
```<meta name="header-meta" content="here_is_the_encryption_key">```
### Find the verification key
in js/sdk.js:
```headers['_TclRequestVerificationKey'] = "here_is_the_verification_keys";```

66
main.py
View File

@@ -9,6 +9,8 @@ import configparser
from fastapi import FastAPI, Depends, HTTPException, status from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
from mw40v import Mw40V
app = FastAPI() app = FastAPI()
security = HTTPBasic() security = HTTPBasic()
@@ -20,9 +22,38 @@ ON_FILENAME = 'dhcpcd-usb-on.conf'
OFF_FILENAME = 'dhcpcd-usb-off.conf' OFF_FILENAME = 'dhcpcd-usb-off.conf'
class Config:
config = None
@classmethod
def get(cls):
if cls.config is None:
cls.config = configparser.ConfigParser()
cls.config.read('./config.ini')
return cls.config
class AirModem:
airModem = None
@classmethod
def get(cls):
if cls.airModem is None:
config = Config.get()
cls.airModem = Mw40V(
config['MW40V']['username'],
config['MW40V']['password'],
config['MW40V']['encrypt_key'],
config['MW40V']['verification_key'],
)
return cls.airModem
def get_username(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): def get_username(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):
config = configparser.ConfigParser() config = Config.get()
config.read('./config.ini')
if credentials.username != config['credentials']['username']: if credentials.username != config['credentials']['username']:
raise_unauthorized() raise_unauthorized()
@@ -65,13 +96,10 @@ def is_usb_on():
def switch_status(turn_on): def switch_status(turn_on):
if turn_on: if turn_on:
# call usb modem api to turn it on
update_usb_modem(turn_on) update_usb_modem(turn_on)
update_dhcp_conf(turn_on)
update_dhcp_conf(turn_on) else:
update_dhcp_conf(turn_on)
if not turn_on:
# call usb modem api to turn it off
update_usb_modem(turn_on) update_usb_modem(turn_on)
@@ -82,21 +110,37 @@ def update_dhcp_conf(turn_on):
def update_usb_modem(turn_on): def update_usb_modem(turn_on):
pass if turn_on:
AirModem.get().connect()
else:
AirModem.get().disconnect()
def initialize_app(): def initialize_app():
script_dir = Path(__file__).parent.absolute() script_dir = Path(__file__).parent.absolute()
credentials_file_path = str(script_dir) + '/config.ini' credentials_file_path = str(script_dir) + '/config.ini'
if not os.path.isfile(credentials_file_path): if not os.path.isfile(credentials_file_path):
username = input("Enter a username")
password = input("Enter a password")
config = configparser.ConfigParser() config = configparser.ConfigParser()
username = input("Enter a username for the API")
password = input("Enter a password for the API")
config['credentials'] = { config['credentials'] = {
'username': username, 'username': username,
'password_hash': PasswordHasher().hash(password) 'password_hash': PasswordHasher().hash(password)
} }
username = input("Enter the username for MW40V")
password = input("Enter the password for MW40V")
encrypt_key = input("Enter the encrypt_key for MW40V")
verification_key = input("Enter the verification_key for MW40V")
config['MW40V'] = {
'username': username,
'password': PasswordHasher().hash(password),
'encrypt_key': encrypt_key,
'verification_key': verification_key
}
with open(credentials_file_path, 'w') as configfile: with open(credentials_file_path, 'w') as configfile:
config.write(configfile) config.write(configfile)

87
mw40v.py Normal file
View File

@@ -0,0 +1,87 @@
import requests
import json
class Mw40V:
def __init__(self, username, password, encrypt_key, verification_key):
self.username = username
self.password = password
self.encrypt_key = encrypt_key
self.verification_key = verification_key
self.login_token = None
def request(self, method, params, id):
body = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": id
}
r = requests.post(
f"http://bbox.nomad/jrd/webapi?api={method}",
data=json.dumps(body),
headers=self.headers(method)
)
return r.json()['result']
def headers(self, method):
return {
'Referer': 'http://bbox.nomad/index.html',
'_TclRequestVerificationKey': self.verification_key,
'_TclRequestVerificationToken': self.encrypt(str(self.login_token)) or "null"
}
def login(self, user, password):
result = self.request(
"Login",
{
"UserName": self.encrypt(user),
"Password": self.encrypt(password)
},
"1.1"
)
if 'token' not in result:
raise Exception("Error while login to nomad bbox")
self.login_token = result['token']
def connect(self):
self.login(self.username, self.password)
result = self.request(
"Connect",
None,
"3.2"
)
return result
def disconnect(self):
self.login(self.username, self.password)
result = self.request(
"DisConnect",
None,
"3.3"
)
return result
def encrypt(self, subject):
if not str:
return ""
subject = str(subject)
result_list = []
for i, char_i in enumerate(subject):
num_char_i = ord(char_i)
x = (ord(self.encrypt_key[i % len(self.encrypt_key)]) & 0xf0) \
| ((num_char_i & 0xf) ^ (ord(self.encrypt_key[i % len(self.encrypt_key)]) & 0xf))
y = (ord(self.encrypt_key[i % len(self.encrypt_key)]) & 0xf0) \
| ((num_char_i >> 4) ^ (ord(self.encrypt_key[i % len(self.encrypt_key)]) & 0xf))
result_list.append(chr(x))
result_list.append(chr(y))
return ''.join(result_list)

View File

@@ -1,3 +1,4 @@
fastapi fastapi
uvicorn uvicorn
argon2-cffi argon2-cffi
requests