2025-06-28 18:13:26 +08:00
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import requests
|
|
|
|
from jose import jwt
|
2025-06-29 21:18:20 +08:00
|
|
|
import certifi
|
2025-07-01 13:06:16 +08:00
|
|
|
from flask import current_app
|
2025-06-28 18:13:26 +08:00
|
|
|
|
|
|
|
class DashboardOIDC:
|
|
|
|
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
|
|
|
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard-oidc-providers.json')
|
|
|
|
def __init__(self):
|
|
|
|
self.providers: dict[str, dict] = None
|
|
|
|
self.__default = {
|
|
|
|
'Provider': {
|
|
|
|
'client_id': '',
|
|
|
|
'client_secret': '',
|
|
|
|
'issuer': '',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if not os.path.exists(DashboardOIDC.ConfigurationFilePath):
|
|
|
|
with open(DashboardOIDC.ConfigurationFilePath, "w+") as f:
|
|
|
|
encoder = json.JSONEncoder(indent=4)
|
|
|
|
f.write(encoder.encode(self.__default))
|
|
|
|
|
|
|
|
self.ReadFile()
|
2025-06-29 16:11:05 +08:00
|
|
|
|
|
|
|
def GetProviders(self):
|
|
|
|
providers = {}
|
|
|
|
for k in self.providers.keys():
|
|
|
|
if all([self.providers[k]['client_id'], self.providers[k]['client_secret'], self.providers[k]['issuer']]):
|
2025-07-01 13:06:16 +08:00
|
|
|
try:
|
|
|
|
oidc_config = requests.get(
|
|
|
|
f"{self.providers[k]['issuer'].strip('/')}/.well-known/openid-configuration",
|
|
|
|
verify=certifi.where()
|
|
|
|
).json()
|
|
|
|
providers[k] = {
|
|
|
|
'client_id': self.providers[k]['client_id'],
|
|
|
|
'issuer': self.providers[k]['issuer'].strip('/')
|
|
|
|
}
|
|
|
|
except Exception as e:
|
|
|
|
current_app.logger.error("Failed to request OIDC config for this provider: " + self.providers[k]['issuer'].strip('/'), exc_info=e)
|
2025-06-29 16:11:05 +08:00
|
|
|
|
|
|
|
return providers
|
2025-06-28 18:13:26 +08:00
|
|
|
|
|
|
|
def VerifyToken(self, provider, code, redirect_uri):
|
2025-06-29 16:11:05 +08:00
|
|
|
try:
|
2025-07-01 13:06:16 +08:00
|
|
|
if not all([provider, code, redirect_uri]):
|
|
|
|
return False, ""
|
|
|
|
|
|
|
|
if provider not in self.providers.keys():
|
|
|
|
return False, "Provider does not exist"
|
|
|
|
|
|
|
|
provider = self.providers.get(provider)
|
|
|
|
oidc_config = requests.get(
|
|
|
|
f"{provider.get('issuer').strip('/')}/.well-known/openid-configuration",
|
|
|
|
verify=certifi.where()
|
|
|
|
|
|
|
|
).json()
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"grant_type": "authorization_code",
|
|
|
|
"code": code,
|
|
|
|
"redirect_uri": redirect_uri,
|
|
|
|
"client_id": provider.get('client_id'),
|
|
|
|
"client_secret": provider.get('client_secret')
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
tokens = requests.post(oidc_config.get('token_endpoint'), data=data).json()
|
|
|
|
if not all([tokens.get('access_token'), tokens.get('id_token')]):
|
|
|
|
return False, tokens.get('error_description', None)
|
|
|
|
except Exception as e:
|
|
|
|
return False, str(e)
|
|
|
|
|
2025-07-02 18:45:43 +08:00
|
|
|
access_token = tokens.get('access_token')
|
2025-07-01 13:06:16 +08:00
|
|
|
id_token = tokens.get('id_token')
|
|
|
|
jwks_uri = oidc_config.get("jwks_uri")
|
|
|
|
issuer = oidc_config.get("issuer")
|
|
|
|
jwks = requests.get(jwks_uri, verify=certifi.where()).json()
|
|
|
|
|
|
|
|
headers = jwt.get_unverified_header(id_token)
|
|
|
|
kid = headers["kid"]
|
|
|
|
|
|
|
|
key = next(k for k in jwks["keys"] if k["kid"] == kid)
|
2025-07-02 18:45:43 +08:00
|
|
|
|
|
|
|
print(key)
|
|
|
|
|
2025-07-01 13:06:16 +08:00
|
|
|
payload = jwt.decode(
|
|
|
|
id_token,
|
|
|
|
key,
|
|
|
|
algorithms=[key["alg"]],
|
|
|
|
audience=provider.get('client_id'),
|
2025-07-02 18:45:43 +08:00
|
|
|
issuer=issuer,
|
|
|
|
access_token=access_token
|
2025-07-01 13:06:16 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
return True, payload
|
2025-06-29 16:11:05 +08:00
|
|
|
except Exception as e:
|
2025-07-02 18:52:49 +08:00
|
|
|
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e), provider, code, redirect_uri)
|
2025-06-29 16:11:05 +08:00
|
|
|
return False, str(e)
|
|
|
|
|
|
|
|
|
2025-06-28 18:13:26 +08:00
|
|
|
def ReadFile(self):
|
|
|
|
decoder = json.JSONDecoder()
|
2025-07-01 13:06:16 +08:00
|
|
|
try:
|
|
|
|
self.providers = decoder.decode(
|
|
|
|
open(DashboardOIDC.ConfigurationFilePath, 'r').read()
|
|
|
|
)
|
|
|
|
except Exception as e:
|
2025-07-02 18:52:49 +08:00
|
|
|
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e))
|
2025-07-01 13:06:16 +08:00
|
|
|
return False
|