diff --git a/.env.x b/.env.x
index e9ba6508..b78c0f2a 100644
Binary files a/.env.x and b/.env.x differ
diff --git a/src/acct/accounting_flow.py b/src/acct/accounting_flow.py
index 46a9ff71..8465469f 100644
--- a/src/acct/accounting_flow.py
+++ b/src/acct/accounting_flow.py
@@ -37,7 +37,7 @@ def accounting_handler(cls, request: AcctRequest, acct_user_profile: AcctUserPro
current_session = AccountingSession.put(acct_user_profile.packet.outer_username, acct_user_profile.packet.user_mac)
if current_session > 1 and account.role != Account.Role.PLATFORM_OWNER.value:
text = f'{acct_user_profile.packet.outer_username} 账号多拨!'
- Feishu.send_groud_msg(receiver_id=Feishu.FEISHU_SESSION_CHAT_ID, text=text)
+ Feishu.send_group_msg(receiver_id=Feishu.FEISHU_SESSION_CHAT_ID, text=text)
# cls.disconnect(user_name=acct_user_profile.packet.outer_username, user_mac=acct_user_profile.packet.user_mac)
# cls.push_metric(username=account.username, request=request)
diff --git a/src/auth/mac_flow.py b/src/auth/mac_flow.py
index c80f3336..c7150ea9 100644
--- a/src/auth/mac_flow.py
+++ b/src/auth/mac_flow.py
@@ -35,7 +35,7 @@ def mac_auth(cls, request: AuthRequest, session: BaseSession):
# notify
notify_url = f'{API_URL}/mac-account?username={session.auth_user_profile.packet.outer_username}&ssid={request.ssid}&ap_mac={request.ap_mac}'
text = f'设备首次请求放通:\nMAC: {session.auth_user_profile.packet.user_mac}\nSSID: {request.ssid}\n若允许访问, 请点击: {notify_url}'
- Feishu.send_groud_msg(receiver_id=Feishu.FEISHU_MAC_CHAT_ID, text=text)
+ Feishu.send_group_msg(receiver_id=Feishu.FEISHU_MAC_CHAT_ID, text=text)
# mac Flow: 用户不存在则创建
account = MacAccount.get_(username=session.auth_user_profile.packet.outer_username)
@@ -53,7 +53,7 @@ def mac_auth(cls, request: AuthRequest, session: BaseSession):
expired_at=expired_at, created_at=created_at,
)
text = f'新增放通 MAC 设备, MAC: {session.auth_user_profile.packet.user_mac}, SSID: {request.ssid}'
- Feishu.send_groud_msg(receiver_id=Feishu.FEISHU_MAC_CHAT_ID, text=text)
+ Feishu.send_group_msg(receiver_id=Feishu.FEISHU_MAC_CHAT_ID, text=text)
redis.delete(enable_flag_key)
if not account.is_enable:
log.warning(f'account is disabled')
diff --git a/src/library/crypto.py b/src/library/crypto.py
index 1336f3b6..5673138d 100644
--- a/src/library/crypto.py
+++ b/src/library/crypto.py
@@ -6,15 +6,15 @@
import ctypes
from loguru import logger as log
# 项目库
-from utils.config import config
+from utils.config import settings
# HOSTAPD 动态库
-HOSTAPD_LIBRARY = config('HOSTAPD_LIBRARY')
-CA_CERT = config('CA_CERT')
-CLIENT_CERT = config('CLIENT_CERT')
-PRIVATE_KEY = config('PRIVATE_KEY')
-PRIVATE_KEY_PASSWORD = str(config('PRIVATE_KEY_PASSWORD'))
-DH_FILE = config('DH_FILE')
+HOSTAPD_LIBRARY = settings.get('HOSTAPD_LIBRARY')
+CA_CERT = settings.get('CA_CERT')
+CLIENT_CERT = settings.get('CLIENT_CERT')
+PRIVATE_KEY = settings.get('PRIVATE_KEY')
+PRIVATE_KEY_PASSWORD = str(settings.get('PRIVATE_KEY_PASSWORD'))
+DH_FILE = settings.get('DH_FILE')
class EapCryptoError(Exception):
diff --git a/src/processor/auth_processor.py b/src/processor/auth_processor.py
index e46fd4e2..879b1baa 100755
--- a/src/processor/auth_processor.py
+++ b/src/processor/auth_processor.py
@@ -21,11 +21,11 @@
from settings import RADIUS_DICTIONARY_DIR, RADIUS_SECRET, RADIUS_LISTEN_IP, RADIUS_LISTEN_PORT
from loguru import logger as log
from controls.user import AuthUserProfile
-from utils.config import config
+from utils.config import settings
from library.crypto import libhostapd
-if config('USE_GTC', default=False, cast='@bool'):
+if settings.get('USE_GTC', default=False, cast='@bool'):
log.info('## PEAP-GTC mode ##')
USE_GTC = True
else:
diff --git a/src/processor/dae_processor.py b/src/processor/dae_processor.py
index f7cae8e7..1fd84ee7 100755
--- a/src/processor/dae_processor.py
+++ b/src/processor/dae_processor.py
@@ -96,7 +96,7 @@ def run(self):
# 发送报文
try:
self.socket.sendto(request.RequestPacket(), request.address)
- res_data, from_address = self.socket.recvfrom(__bufsize=1024)
+ res_data, from_address = self.socket.recvfrom(1024) # buf size: 1024
except Exception as e:
log.error(traceback.format_exc())
return False
diff --git a/src/settings.py b/src/settings.py
index 2b8c74f9..4ff384ca 100644
--- a/src/settings.py
+++ b/src/settings.py
@@ -3,12 +3,12 @@
# 第三方库
import sentry_sdk
# 项目库
-from utils.config import config
+from utils.config import settings
from loguru import logger as log
-SENTRY_DSN = config('SENTRY_DSN', mandatory=False)
-SENTRY_PROXY = config('SENTRY_PROXY', default='')
+SENTRY_DSN = settings.get('SENTRY_DSN', mandatory=False)
+SENTRY_PROXY = settings.get('SENTRY_PROXY', default='')
sentry_sdk.init(
dsn=SENTRY_DSN,
debug=False,
@@ -16,24 +16,24 @@
https_proxy=SENTRY_PROXY,
)
-RADIUS_DICTIONARY_DIR = config('RADIUS_DICTIONARY_DIR')
-RADIUS_SECRET: bytes = str.encode(config('RADIUS_SECRET'))
-RADIUS_LISTEN_IP = config('RADIUS_LISTEN_IP', default='')
-RADIUS_LISTEN_PORT = config('RADIUS_LISTEN_PORT', default='')
-ACCOUNTING_INTERVAL = config('ACCOUNTING_INTERVAL', default=60, cast='@int')
-API_URL = config('API_URL')
+RADIUS_DICTIONARY_DIR = settings.get('RADIUS_DICTIONARY_DIR')
+RADIUS_SECRET: bytes = str.encode(settings.get('RADIUS_SECRET'))
+RADIUS_LISTEN_IP = settings.get('RADIUS_LISTEN_IP', default='')
+RADIUS_LISTEN_PORT = settings.get('RADIUS_LISTEN_PORT', default='')
+ACCOUNTING_INTERVAL = settings.get('ACCOUNTING_INTERVAL', default=60, cast='@int')
+API_URL = settings.get('API_URL')
# DB
-DATABASE_URI = config('DATABASE_URI')
+DATABASE_URI = settings.get('DATABASE_URI')
# Redis
-REDIS_HOST = config('REDIS_HOST')
-REDIS_PORT = config('REDIS_PORT')
-REDIS_PASSWORD = config('REDIS_PASSWORD')
-REDIS_DB = config('REDIS_DB')
+REDIS_HOST = settings.get('REDIS_HOST')
+REDIS_PORT = settings.get('REDIS_PORT')
+REDIS_PASSWORD = settings.get('REDIS_PASSWORD')
+REDIS_DB = settings.get('REDIS_DB')
# Log
-LOG_LEVEL = config('LOG_LEVEL')
+LOG_LEVEL = settings.get('LOG_LEVEL')
# 初始化日志
log.remove() # workaround: https://github.com/Delgan/loguru/issues/208
# log_console_format = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}"
diff --git a/src/utils/config.py b/src/utils/config.py
index d71462db..4a72c0c8 100644
--- a/src/utils/config.py
+++ b/src/utils/config.py
@@ -10,11 +10,12 @@ class Config(object):
dotenv_override=False, # 设置.env配置是否覆盖环境变量
)
- def __call__(self, key, default=None, cast=None, mandatory=True, fresh=False, dotted_lookup=True, parent=None):
- value = self._settings.get(key, default=default, cast=cast, fresh=fresh, dotted_lookup=dotted_lookup, parent=None)
+ @classmethod
+ def get(cls, key, default=None, cast=None, mandatory=True, fresh=False, dotted_lookup=True, parent=None):
+ value = cls._settings.get(key, default=default, cast=cast, fresh=fresh, dotted_lookup=dotted_lookup, parent=None)
if mandatory and value is None:
raise Exception(f'config key: {key} is missing')
return value
-config = Config()
+settings = Config
diff --git a/src/utils/dingding.py b/src/utils/dingding.py
new file mode 100644
index 00000000..1ff665d4
--- /dev/null
+++ b/src/utils/dingding.py
@@ -0,0 +1,93 @@
+import requests
+import time
+# 第三方库
+from utils.config import settings
+from loguru import logger as log
+
+
+class Dingding(object):
+ class Token(object):
+ def __init__(self, token='', ttl=-1):
+ self.token = token
+ self.expired_at = int(time.time()) + ttl
+
+ # Must:
+ DINGDING_APP_ID = settings.get('DINGDING_APP_ID')
+ DINGDING_APP_SECRET = settings.get('DINGDING_APP_SECRET')
+ DINGDING_ROBOT_CODE = settings.get('DINGDING_ROBOT_CODE', default='dingqnettcbcq4tpecq7')
+ # Optional:
+ DINGDING_MAC_CHAT_ID = settings.get('DINGDING_MAC_CHAT_ID', default='cidVhnIuNh9n5Q0MoN8ddMqNw==') # MAC请求放通群
+ DINGDING_SESSION_CHAT_ID = settings.get('DINGDING_SESSION_CHAT_ID', default='cidVhnIuNh9n5Q0MoN8ddMqNw==') # 多拨告警群
+ #
+ _ACCESS_TOKEN = Token()
+
+ """
+ 获取access_token
+ https://open.dingtalk.com/document/development/obtain-the-access-token-of-an-internal-app
+
+ POST /v1.0/oauth2/accessToken HTTP/1.1
+ Host:api.dingtalk.com
+ Content-Type:application/json
+
+ {
+ "appKey" : "dingeqqpkv3xxxxxx",
+ "appSecret" : "GT-lsu-taDAxxxsTsxxxx"
+ }
+
+ :return:
+ {
+ "accessToken" : "fw8ef8we8f76e6f7s8dxxxx",
+ "expireIn" : 7200
+ }
+ """
+ @classmethod
+ def get_access_token(cls) -> str:
+ if int(time.time()) > cls._ACCESS_TOKEN.expired_at:
+ data = {
+ 'appKey': cls.DINGDING_APP_ID,
+ 'appSecret': cls.DINGDING_APP_SECRET,
+ }
+ response = requests.post('https://api.dingtalk.com/v1.0/oauth2/accessToken', json=data)
+ body = response.json()
+ log.debug(f'API get_access_token: {body}')
+ cls._ACCESS_TOKEN = cls.Token(token=body['accessToken'], ttl=body['expireIn'])
+ log.debug(f'fetched access token: {cls._ACCESS_TOKEN.token}')
+ return cls._ACCESS_TOKEN.token
+
+ """
+ 发送应用消息
+ https://open.dingtalk.com/document/development/the-robot-sends-a-group-message
+
+ POST /v1.0/robot/groupMessages/send HTTP/1.1
+ Host:api.dingtalk.com
+ x-acs-dingtalk-access-token:nvosnghskaknz8xxxxxx
+ Content-Type:application/json
+
+ {
+ "msgParam" : "{\"content\":\"钉钉,让进步发生\"}",
+ "msgKey" : "sampleText",
+ "openConversationId" : "cid6KeBBLoveMJOGXoYKF5xxxxxxx==",
+ "robotCode" : "dingue4kfzdxbynxxxxxx",
+ "coolAppCode" : "COOLAPP-1-10182EEDD1AC0BA60xxxxxx"
+ }
+
+ :return:
+ {
+ "processQueryKey" : "jkasdfb8va9hnxxxxxx"
+ }
+ """
+ @classmethod
+ def send_group_msg(cls, receiver_id: str, text: str):
+ headers = {
+ 'x-acs-dingtalk-access-token': cls.get_access_token(),
+ }
+ data = {
+ 'msgParam': f'{{"content":"{text}"}}',
+ 'msgKey': 'sampleText',
+ 'openConversationId': receiver_id,
+ 'robotCode': cls.DINGDING_ROBOT_CODE,
+ }
+ response = requests.post('https://api.dingtalk.com/v1.0/robot/groupMessages/send', json=data, headers=headers)
+ body = response.json()
+ log.debug(f'API send_group_msg: {body}')
+ assert response.ok
diff --git a/src/utils/feishu.py b/src/utils/feishu.py
index 6ebca277..16bb3943 100644
--- a/src/utils/feishu.py
+++ b/src/utils/feishu.py
@@ -1,41 +1,152 @@
-import json
import requests
+import json
+import time
# 第三方库
-from utils.config import config
+from utils.config import settings
+from loguru import logger as log
class Feishu(object):
- FEISHU_APP_ID = config('FEISHU_APP_ID')
- FEISHU_APP_SECRET = config('FEISHU_APP_SECRET')
- FEISHU_CHARGE_CHAT_ID = config('FEISHU_CHARGE_CHAT_ID', default='oc_a4bc2f10dd9ec84f08f2bbcaa82e08cd')
- FEISHU_MAC_CHAT_ID = config('FEISHU_MAC_CHAT_ID', default='oc_3a7065d01efdb36d949088341aada466')
- FEISHU_SESSION_CHAT_ID = config('FEISHU_SESSION_CHAT_ID', default='oc_19b2404bb0917fc066cce1b3a58c3558')
+ class Token(object):
+ def __init__(self, token='', ttl=-1):
+ self.token = token
+ self.expired_at = int(time.time()) + ttl
+
+ # Must:
+ FEISHU_APP_ID = settings.get('FEISHU_APP_ID', default='')
+ FEISHU_APP_SECRET = settings.get('FEISHU_APP_SECRET', default='')
+ # Optional:
+ FEISHU_MAC_CHAT_ID = settings.get('FEISHU_MAC_CHAT_ID', default='oc_3a7065d01efdb36d949088341aada466') # MAC请求放通群
+ FEISHU_SESSION_CHAT_ID = settings.get('FEISHU_SESSION_CHAT_ID', default='oc_19b2404bb0917fc066cce1b3a58c3558') # 多拨告警群
+ #
+ _ACCESS_TOKEN = Token()
+
+ """
+ 获取access_token
+ https://feishu.apifox.cn/api-58156651
+
+ POST /open-apis/auth/v3/tenant_access_token/internal HTTP/1.1
+ Host: open.feishu.cn
+ Authorization: Bearer
+ Content-Type: application/json
+ Content-Length: 81
+
+ {
+ "app_id": "cli_slkdjalaxxxxxx",
+ "app_secret": "dskLLdkasdxxxxxx"
+ }
+ :return:
+ {
+ "code": 0,
+ "msg": "ok",
+ "tenant_access_token": "t-caecc734c2e3328a62489fe0648c4xxxxxx",
+ "expire": 7200
+ }
+ """
@classmethod
- def send_groud_msg(cls, receiver_id: str, text: str):
- data = {
- 'app_id': cls.FEISHU_APP_ID,
- 'app_secret': cls.FEISHU_APP_SECRET,
+ def get_access_token(cls) -> str:
+ assert cls.FEISHU_APP_ID and cls.FEISHU_APP_SECRET
+ if int(time.time()) > cls._ACCESS_TOKEN.expired_at:
+ data = {
+ 'app_id': cls.FEISHU_APP_ID,
+ 'app_secret': cls.FEISHU_APP_SECRET,
+ }
+ response = requests.post('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/', json=data)
+ assert response.ok
+ body = response.json()
+ log.debug(f'API get_access_token: {body}')
+ if body['code'] != 0:
+ raise Exception('飞书获取access_token失败')
+ cls._ACCESS_TOKEN = cls.Token(token=body['tenant_access_token'], ttl=body['expire'])
+ log.debug(f'fetched access token: {cls._ACCESS_TOKEN.token}')
+ return cls._ACCESS_TOKEN.token
+
+ """
+ 发送消息
+ https://feishu.apifox.cn/api-58348294
+
+ POST /open-apis/im/v1/messages?receive_id_type=chat_id HTTP/1.1
+ Host: open.feishu.cn
+ Authorization: Bearer
+ Content-Type: application/json
+ Content-Length: 189
+
+ {
+ "receive_id": "ou_7d8a6e6df7621556ce0d21922bxxxxxx",
+ "msg_type": "text",
+ "content": "{\"text\":\"test content\"}",
+ "uuid": "a0d69e20-1dd1-458b-k525-dfecaxxxxxx"
+ }
+
+ :return:
+ {
+ "code": 0,
+ "msg": "success",
+ "data": {
+ "message_id": "om_dc13264520392913993dd05xxxxxx",
+ "root_id": "om_40eb06e7b84dc71c03e009ad3cxxxxxx",
+ "parent_id": "om_d4be107c616aed9c1da8ed8xxxxxx",
+ "msg_type": "card",
+ "create_time": "1615380573411",
+ "update_time": "1615380573411",
+ "deleted": false,
+ "updated": false,
+ "chat_id": "oc_5ad11d72b830411d72xxxxxx",
+ "sender": {
+ "id": "cli_9f427eec54ae901b",
+ "id_type": "app_id",
+ "sender_type": "app",
+ "tenant_key": "736588c926xxxxxx"
+ },
+ "body": {
+ "content": "text:测试消息"
+ },
+ "mentions": [
+ {
+ "key": "@_user_1",
+ "id": "ou_155184d1e73cbfb8973e5a9exxxxxx",
+ "id_type": "open_id",
+ "name": "Tom",
+ "tenant_key": "736588c9260xxxxxx"
+ }
+ ],
+ "upper_message_id": "om_40eb06e7b84dc71c03e009adxxxxxx"
}
- response = requests.post('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/', json=data)
- assert response.ok
- body = json.loads(response.text)
- if body['code'] != 0:
- raise Exception('飞书获取access_token失败')
- access_token = response.json()['tenant_access_token']
+ }
+ """
+ @classmethod
+ def send_group_msg(cls, receiver_id: str, text: str):
+ access_token = cls.get_access_token()
#
headers = {
'Authorization': f'Bearer {access_token}'
}
data = {
- 'chat_id': receiver_id,
+ 'receive_id': receiver_id,
+ 'msg_type': 'text',
+ 'content': json.dumps({
+ 'text': text,
+ })
+ }
+ response = requests.post('https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id', json=data, headers=headers)
+ body = response.json()
+ log.debug(f'API send_group_msg: {body}')
+ assert response.ok
+ if body['code'] != 0:
+ raise Exception('飞书群消息发送失败')
+
+ @classmethod
+ def send_webhook_msg(cls, webhook_url: str, text: str):
+ data = {
'msg_type': 'text',
'content': {
'text': text,
}
}
- response = requests.post('https://open.feishu.cn/open-apis/message/v4/send/', json=data, headers=headers)
+ response = requests.post(webhook_url, json=data)
assert response.ok
- body = json.loads(response.text)
+ body = response.json()
+ log.debug(f'API send_webhook_msg: {body}')
if body['code'] != 0:
- raise Exception('信息发送到飞书败')
+ raise Exception('飞书webhook消息发送失败')
diff --git a/src/utils/prometheus.py b/src/utils/prometheus.py
index 17ddd6e2..3eb1e0e2 100644
--- a/src/utils/prometheus.py
+++ b/src/utils/prometheus.py
@@ -1,10 +1,10 @@
import requests
# 项目库
-from utils.config import config
+from utils.config import settings
class Prometheus(object):
- ENDPOINT = config('PROMETHEUS_ENDPOINT', default='http://metric:8428/prometheus/api/v1/import/prometheus')
+ ENDPOINT = settings.get('PROMETHEUS_ENDPOINT', default='http://metric:8428/prometheus/api/v1/import/prometheus')
@classmethod
def push_metric(cls, metrics: list):