From e7dfe6c5730258fcdc52f2a8bb62fdf41882436d Mon Sep 17 00:00:00 2001 From: pycook Date: Thu, 14 Dec 2023 19:49:31 +0800 Subject: [PATCH] feat(api): ldap and OAuth2.0 --- cmdb-api/api/lib/perm/acl/resp_format.py | 1 + cmdb-api/api/lib/perm/auth.py | 3 +++ .../lib/perm/authentication/cas/routing.py | 2 +- cmdb-api/api/lib/perm/authentication/ldap.py | 21 ++++++++++++++----- .../lib/perm/authentication/oauth2/routing.py | 8 +++---- cmdb-api/api/views/acl/login.py | 5 ++++- docker-compose.yml | 4 ++-- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/cmdb-api/api/lib/perm/acl/resp_format.py b/cmdb-api/api/lib/perm/acl/resp_format.py index 5767716..9ea6c5b 100644 --- a/cmdb-api/api/lib/perm/acl/resp_format.py +++ b/cmdb-api/api/lib/perm/acl/resp_format.py @@ -5,6 +5,7 @@ from api.lib.resp_format import CommonErrFormat class ErrFormat(CommonErrFormat): login_succeed = "登录成功" + ldap_connection_failed = "连接LDAP服务失败" invalid_password = "密码验证失败" auth_only_with_app_token_failed = "应用 Token验证失败" session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)" diff --git a/cmdb-api/api/lib/perm/auth.py b/cmdb-api/api/lib/perm/auth.py index 76e2481..c00f8b0 100644 --- a/cmdb-api/api/lib/perm/auth.py +++ b/cmdb-api/api/lib/perm/auth.py @@ -93,6 +93,9 @@ def _auth_with_token(): def _auth_with_ip_white_list(): + if request.url.endswith("acl/users/info"): + return False + ip = request.headers.get('X-Real-IP') or request.remote_addr key = request.values.get('_key') secret = request.values.get('_secret') diff --git a/cmdb-api/api/lib/perm/authentication/cas/routing.py b/cmdb-api/api/lib/perm/authentication/cas/routing.py index 906d9e8..271010b 100644 --- a/cmdb-api/api/lib/perm/authentication/cas/routing.py +++ b/cmdb-api/api/lib/perm/authentication/cas/routing.py @@ -57,7 +57,7 @@ def login(): if request.args.get('ticket'): if validate(request.args['ticket']): - redirect_url = session.get("next") or config.get("cas_after_login") + redirect_url = session.get("next") or config.get("cas_after_login") or "/" username = session.get("CAS_USERNAME") user = UserCache.get(username) login_user(user) diff --git a/cmdb-api/api/lib/perm/authentication/ldap.py b/cmdb-api/api/lib/perm/authentication/ldap.py index 2d6da88..a747ae6 100644 --- a/cmdb-api/api/lib/perm/authentication/ldap.py +++ b/cmdb-api/api/lib/perm/authentication/ldap.py @@ -2,6 +2,7 @@ import uuid +from flask import abort from flask import current_app from flask import session from ldap3 import ALL @@ -10,20 +11,25 @@ from ldap3 import Connection from ldap3 import Server from ldap3.core.exceptions import LDAPBindError from ldap3.core.exceptions import LDAPCertificateError +from ldap3.core.exceptions import LDAPSocketOpenError +from api.lib.common_setting.common_data import AuthenticateDataCRUD +from api.lib.common_setting.const import AuthenticateType from api.lib.perm.acl.audit import AuditCRUD from api.lib.perm.acl.resp_format import ErrFormat from api.models.acl import User def authenticate_with_ldap(username, password): - server = Server(current_app.config.get('LDAP').get('ldap_server'), get_info=ALL, connect_timeout=3) + config = AuthenticateDataCRUD(AuthenticateType.CAS).get() + + server = Server(config.get('LDAP').get('ldap_server'), get_info=ALL, connect_timeout=3) if '@' in username: email = username - who = current_app.config['LDAP'].get('ldap_user_dn').format(username.split('@')[0]) + who = config['LDAP'].get('ldap_user_dn').format(username.split('@')[0]) else: - who = current_app.config['LDAP'].get('ldap_user_dn').format(username) - email = "{}@{}".format(who, current_app.config['LDAP'].get('ldap_domain')) + who = config['LDAP'].get('ldap_user_dn').format(username) + email = "{}@{}".format(who, config['LDAP'].get('ldap_domain')) username = username.split('@')[0] user = User.query.get_by_username(username) @@ -35,7 +41,7 @@ def authenticate_with_ldap(username, password): conn = Connection(server, user=who, password=password, auto_bind=AUTO_BIND_NO_TLS) except LDAPBindError: conn = Connection(server, - user=f"{username}@{current_app.config['LDAP'].get('ldap_domain')}", + user=f"{username}@{config['LDAP'].get('ldap_domain')}", password=password, auto_bind=AUTO_BIND_NO_TLS) @@ -51,6 +57,11 @@ def authenticate_with_ldap(username, password): user = UserCRUD.add(username=username, email=email, password=uuid.uuid4().hex) return user, True + except LDAPBindError as e: current_app.logger.info(e) return user, False + + except LDAPSocketOpenError as e: + current_app.logger.info(e) + return abort(403, ErrFormat.ldap_connection_failed) diff --git a/cmdb-api/api/lib/perm/authentication/oauth2/routing.py b/cmdb-api/api/lib/perm/authentication/oauth2/routing.py index 0d1c518..828855e 100644 --- a/cmdb-api/api/lib/perm/authentication/oauth2/routing.py +++ b/cmdb-api/api/lib/perm/authentication/oauth2/routing.py @@ -36,7 +36,7 @@ def login(auth_type): qs = urlencode({ 'client_id': config['client_id'], - 'redirect_uri': url_for('oauth2.callback', _external=True), + 'redirect_uri': url_for('oauth2.callback', auth_type=auth_type.lower(), _external=True), 'response_type': current_app.config[f'{auth_type}_RESPONSE_TYPE'], 'scope': ' '.join(config['scopes'] or []), 'state': session[f'{auth_type.lower()}_state'], @@ -50,7 +50,7 @@ def callback(auth_type): auth_type = auth_type.upper() config = AuthenticateDataCRUD(auth_type).get() - redirect_url = session.get("next") or config.get('after_login') + redirect_url = session.get("next") or config.get('after_login') or '/' if request.values['state'] != session.get(f'{auth_type.lower()}_state'): return abort(401, "state is invalid") @@ -63,7 +63,7 @@ def callback(auth_type): 'client_secret': config['client_secret'], 'code': request.values['code'], 'grant_type': current_app.config[f'{auth_type}_GRANT_TYPE'], - 'redirect_uri': url_for('oauth2.callback', _external=True), + 'redirect_uri': url_for('oauth2.callback', auth_type=auth_type.lower(), _external=True), }, headers={'Accept': 'application/json'}) if response.status_code != 200: current_app.logger.error(response.text) @@ -123,7 +123,7 @@ def logout(auth_type): f'{auth_type}_state' in session and session.pop(f'{auth_type}_state') "next" in session and session.pop("next") - redirect_url = url_for('oauth2.login', _external=True, next=request.referrer) + redirect_url = url_for('oauth2.login', auth_type=auth_type, _external=True, next=request.referrer) logout_user() diff --git a/cmdb-api/api/views/acl/login.py b/cmdb-api/api/views/acl/login.py index ca18e23..7164d54 100644 --- a/cmdb-api/api/views/acl/login.py +++ b/cmdb-api/api/views/acl/login.py @@ -11,6 +11,8 @@ from flask import session from flask_login import login_user from flask_login import logout_user +from api.lib.common_setting.common_data import AuthenticateDataCRUD +from api.lib.common_setting.const import AuthenticateType from api.lib.decorator import args_required from api.lib.decorator import args_validate from api.lib.perm.acl.acl import ACLManager @@ -36,7 +38,8 @@ class LoginView(APIView): username = request.values.get("username") or request.values.get("email") password = request.values.get("password") _role = None - if current_app.config.get('LDAP', {}).get('enabled'): + config = AuthenticateDataCRUD(AuthenticateType.LDAP).get() + if config.get('LDAP', {}).get('enabled'): from api.lib.perm.authentication.ldap import authenticate_with_ldap user, authenticated = authenticate_with_ldap(username, password) else: diff --git a/docker-compose.yml b/docker-compose.yml index 9465697..7d33251 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.5' services: cmdb-db: - image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:3.0 + image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-db:2.3 container_name: cmdb-db environment: TZ: Asia/Shanghai @@ -22,7 +22,7 @@ services: - '23306:3306' cmdb-cache: - image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:3.0 + image: registry.cn-hangzhou.aliyuncs.com/veops/cmdb-cache:2.3 container_name: cmdb-cache environment: TZ: Asia/Shanghai