fix: 更新OIDC配置时,将keycloak配置转换为openid (#6893)

* fix: 修改磁盘使用等key值

* fix: 更新OIDC配置时,将keycloak配置转换为openid

Co-authored-by: Michael Bai <baijiangjie@gmail.com>
pull/6894/head
fit2bot 3 years ago committed by GitHub
parent a784a33203
commit c26ca20ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,6 +14,7 @@ import types
import errno import errno
import json import json
import yaml import yaml
import copy
from importlib import import_module from importlib import import_module
from django.urls import reverse_lazy from django.urls import reverse_lazy
from urllib.parse import urljoin, urlparse from urllib.parse import urljoin, urlparse
@ -348,7 +349,8 @@ class Config(dict):
'HEALTH_CHECK_TOKEN': '', 'HEALTH_CHECK_TOKEN': '',
} }
def compatible_auth_openid_of_key(self): @staticmethod
def convert_keycloak_to_openid(keycloak_config):
""" """
兼容OpenID旧配置 ( version <= 1.5.8) 兼容OpenID旧配置 ( version <= 1.5.8)
因为旧配置只支持OpenID协议的Keycloak实现, 因为旧配置只支持OpenID协议的Keycloak实现,
@ -356,65 +358,79 @@ class Config(dict):
构造出新配置中标准OpenID协议中所需的Endpoint即可 构造出新配置中标准OpenID协议中所需的Endpoint即可
(Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/) (Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/)
""" """
if self.AUTH_OPENID and not self.AUTH_OPENID_REALM_NAME:
self['AUTH_OPENID_KEYCLOAK'] = False
if not self.AUTH_OPENID: openid_config = copy.deepcopy(keycloak_config)
return
auth_openid = openid_config.get('AUTH_OPENID')
auth_openid_realm_name = openid_config.get('AUTH_OPENID_REALM_NAME')
auth_openid_server_url = openid_config.get('AUTH_OPENID_SERVER_URL')
realm_name = self.AUTH_OPENID_REALM_NAME if not auth_openid:
if realm_name is None:
return return
compatible_keycloak_config = [ if auth_openid and not auth_openid_realm_name:
( # 开启的是标准 OpenID 配置,关掉 Keycloak 配置
'AUTH_OPENID_PROVIDER_ENDPOINT', openid_config.update({
self.AUTH_OPENID_SERVER_URL 'AUTH_OPENID_KEYCLOAK': False
), })
(
'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT',
'/realms/{}/protocol/openid-connect/auth'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT',
'/realms/{}/protocol/openid-connect/token'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT',
'/realms/{}/protocol/openid-connect/certs'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT',
'/realms/{}/protocol/openid-connect/userinfo'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT',
'/realms/{}/protocol/openid-connect/logout'.format(realm_name)
)
]
for key, value in compatible_keycloak_config:
self[key] = value
def compatible_auth_openid_of_value(self): if auth_openid_realm_name is None:
"""
兼容值的绝对路径相对路径
(key AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置)
"""
if not self.AUTH_OPENID:
return return
base = self.AUTH_OPENID_PROVIDER_ENDPOINT # # convert key # #
config = list(self.items()) compatible_config = {
for key, value in config: 'AUTH_OPENID_PROVIDER_ENDPOINT': auth_openid_server_url,
'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT': '/realms/{}/protocol/openid-connect/auth'
''.format(auth_openid_realm_name),
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT': '/realms/{}/protocol/openid-connect/token'
''.format(auth_openid_realm_name),
'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT': '/realms/{}/protocol/openid-connect/certs'
''.format(auth_openid_realm_name),
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT': '/realms/{}/protocol/openid-connect/userinfo'
''.format(auth_openid_realm_name),
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT': '/realms/{}/protocol/openid-connect/logout'
''.format(auth_openid_realm_name)
}
for key, value in compatible_config.items():
openid_config[key] = value
# # convert value # #
""" 兼容值的绝对路径、相对路径 (key 为 AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置) """
base = openid_config.get('AUTH_OPENID_PROVIDER_ENDPOINT')
for key, value in openid_config.items():
result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key) result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key)
if result is None: if result is None:
continue continue
if value is None: if value is None:
# None 在 url 中有特殊含义 (比如对于: end_session_endpoint) # None 在 url 中有特殊含义 (比如对于: end_session_endpoint)
continue continue
value = build_absolute_uri(base, value) value = build_absolute_uri(base, value)
openid_config[key] = value
return openid_config
def get_keycloak_config(self):
keycloak_config = {
'AUTH_OPENID': self.AUTH_OPENID,
'AUTH_OPENID_REALM_NAME': self.AUTH_OPENID_REALM_NAME,
'AUTH_OPENID_SERVER_URL': self.AUTH_OPENID_SERVER_URL,
'AUTH_OPENID_PROVIDER_ENDPOINT': self.AUTH_OPENID_PROVIDER_ENDPOINT
}
return keycloak_config
def set_openid_config(self, openid_config):
for key, value in openid_config.items():
self[key] = value self[key] = value
def compatible_auth_openid(self, keycloak_config=None):
if keycloak_config is None:
keycloak_config = self.get_keycloak_config()
openid_config = self.convert_keycloak_to_openid(keycloak_config)
if openid_config:
self.set_openid_config(openid_config)
def compatible(self): def compatible(self):
""" """
对配置做兼容处理 对配置做兼容处理
@ -424,14 +440,8 @@ class Config(dict):
处理顺序要保持先对key做处理, 再对value做处理, 处理顺序要保持先对key做处理, 再对value做处理,
因为处理value的时候只根据最新版本支持的key进行 因为处理value的时候只根据最新版本支持的key进行
""" """
parts = ['key', 'value'] # 兼容 OpenID 配置
targets = ['auth_openid'] self.compatible_auth_openid()
for part in parts:
for target in targets:
method_name = 'compatible_{}_of_{}'.format(target, part)
method = getattr(self, method_name, None)
if method is not None:
method()
def convert_type(self, k, v): def convert_type(self, k, v):
default_value = self.defaults.get(k) default_value = self.defaults.get(k)

@ -84,6 +84,7 @@ class Setting(models.Model):
getattr(self.__class__, f'refresh_{self.name}')() getattr(self.__class__, f'refresh_{self.name}')()
else: else:
setattr(settings, self.name, self.cleaned_value) setattr(settings, self.name, self.cleaned_value)
self.refresh_keycloak_to_openid_if_need()
@classmethod @classmethod
def refresh_authentications(cls, name): def refresh_authentications(cls, name):
@ -129,6 +130,41 @@ class Setting(models.Model):
def refresh_AUTH_OPENID(cls): def refresh_AUTH_OPENID(cls):
cls.refresh_authentications('AUTH_OPENID') cls.refresh_authentications('AUTH_OPENID')
def refresh_keycloak_to_openid_if_need(self):
watch_config_names = [
'AUTH_OPENID', 'AUTH_OPENID_REALM_NAME', 'AUTH_OPENID_SERVER_URL',
'AUTH_OPENID_PROVIDER_ENDPOINT', 'AUTH_OPENID_KEYCLOAK'
]
if self.name not in watch_config_names:
# 不在监听的配置中, 不需要刷新
return
auth_keycloak = self.__class__.objects.filter(name='AUTH_OPENID_KEYCLOAK').first()
if not auth_keycloak or not auth_keycloak.cleaned_value:
# 关闭 Keycloak 方式的配置, 不需要刷新
return
from jumpserver.conf import Config
config_names = [
'AUTH_OPENID', 'AUTH_OPENID_REALM_NAME',
'AUTH_OPENID_SERVER_URL', 'AUTH_OPENID_PROVIDER_ENDPOINT'
]
# 获取当前 keycloak 配置
keycloak_config = {}
for name in config_names:
setting = self.__class__.objects.filter(name=name).first()
if not setting:
continue
value = setting.cleaned_value
keycloak_config[name] = value
# 转化 keycloak 配置为 openid 配置
openid_config = Config.convert_keycloak_to_openid(keycloak_config)
if not openid_config:
return
# 刷新 settings
for key, value in openid_config.items():
setattr(settings, key, value)
@classmethod @classmethod
def refresh_AUTH_RADIUS(cls): def refresh_AUTH_RADIUS(cls):
cls.refresh_authentications('AUTH_RADIUS') cls.refresh_authentications('AUTH_RADIUS')

Loading…
Cancel
Save