From bfd1174186ad98ed02f2362a1b924c1cab88e175 Mon Sep 17 00:00:00 2001 From: normal-wls <1158341873@qq.com> Date: Wed, 27 Jan 2021 15:22:23 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20=E5=8D=87=E7=BA=A7blueapps=3D=3D3.3.?= =?UTF-8?q?1,=20tastypie=3D=3D0.14.3,=20django=3D=3D2.2.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .flake8 | 1 + blueapps/__init__.py | 3 +- blueapps/account/__init__.py | 27 +-- blueapps/account/admin.py | 32 +-- blueapps/account/apps.py | 4 +- blueapps/account/backends.py | 8 +- .../account/components/bk_jwt/backends.py | 77 +++---- .../account/components/bk_jwt/middlewares.py | 17 +- .../account/components/bk_token/backends.py | 104 ++++----- .../components/bk_token/middlewares.py | 66 +++--- .../account/components/bk_token/models.py | 6 +- blueapps/account/components/null/__init__.py | 1 - .../account/components/weixin/backends.py | 31 ++- .../account/components/weixin/middlewares.py | 65 ++---- blueapps/account/conf.py | 29 ++- blueapps/account/decorators.py | 2 + blueapps/account/forms.py | 3 +- blueapps/account/handlers/response.py | 25 ++- blueapps/account/middlewares.py | 23 +- blueapps/account/migrations/0001_initial.py | 155 +++++++++---- .../account/migrations/0002_init_superuser.py | 13 +- .../account/migrations/0003_verifyinfo.py | 15 +- .../migrations/0004_create_cache_table.py | 20 ++ blueapps/account/models.py | 176 +++++++-------- blueapps/account/sites/default.py | 9 +- blueapps/account/static/account/login.js | 30 ++- .../account/templates/account/login_page.html | 15 +- .../templates/account/login_success.html | 5 +- blueapps/account/urls.py | 10 +- blueapps/account/utils/__init__.py | 2 +- blueapps/account/utils/http.py | 56 ++--- blueapps/account/utils/sms.py | 11 +- blueapps/account/views.py | 29 +-- blueapps/conf/__init__.py | 13 +- blueapps/conf/database.py | 32 +-- blueapps/conf/default_settings.py | 45 ++-- blueapps/conf/environ.py | 79 ++++--- blueapps/conf/log.py | 210 ++++++++---------- .../contrib/bk_commands/management/app.py | 7 + .../contrib/bk_commands/management/base.py | 146 ++++++++++++ .../management/commands/__init__.py | 9 + .../bk_commands/management/commands/celery.py | 32 +++ .../management/commands/celerybeat.py | 36 +++ .../bk_commands/management/commands/init.py | 153 +++++++------ .../commands/migrate_from_djcelery.py | 53 +++++ .../management/commands/startexample.py | 205 +++++++++-------- .../management/commands/startweixin.py | 158 +++++++------ .../management/commands/startwxapp.py | 67 +++--- .../management/handlers/__init__.py | 0 .../handlers/migrate_from_djcelery_handler.py | 128 +++++++++++ .../bk_commands/management/templates.py | 123 +++++----- blueapps/contrib/bk_commands/test.py | 18 ++ blueapps/core/celery/__init__.py | 4 +- blueapps/core/celery/celery.py | 10 +- blueapps/core/exceptions/__init__.py | 38 +++- blueapps/core/exceptions/base.py | 42 ++-- blueapps/core/exceptions/middleware.py | 35 +-- blueapps/core/handler/wsgi.py | 14 +- blueapps/core/sites/middleware.py | 48 ++-- blueapps/core/wsgi.py | 1 + blueapps/middleware/bkui/middlewares.py | 6 +- blueapps/middleware/xss/decorators.py | 14 +- blueapps/middleware/xss/middlewares.py | 80 +++---- blueapps/middleware/xss/pxfilter.py | 138 +++++++++--- blueapps/middleware/xss/utils.py | 80 ++++--- blueapps/patch/log.py | 137 ++++-------- blueapps/patch/settings_open_saas.py | 61 +++-- blueapps/patch/settings_paas_services.py | 50 +++-- blueapps/template/backends/mako.py | 47 ++-- blueapps/template/context_processors.py | 52 ++--- blueapps/utils/__init__.py | 44 ++-- blueapps/utils/cache.py | 164 -------------- blueapps/utils/esbclient.py | 88 ++++---- blueapps/utils/logger.py | 6 +- blueapps/utils/request_provider.py | 44 ++-- blueapps/utils/sites/clouds/__init__.py | 38 ++++ blueapps/utils/sites/ieod/__init__.py | 38 ++++ blueapps/utils/sites/open/__init__.py | 6 +- blueapps/utils/sites/qcloud/__init__.py | 38 ++++ blueapps/utils/sites/tencent/__init__.py | 38 ++++ blueapps/utils/tools.py | 17 ++ blueapps/utils/unique.py | 6 +- blueapps/utils/view_decorators.py | 71 ------ config/default.py | 4 + config/dev.py | 2 + config/prod.py | 5 +- config/stag.py | 5 + gcloud/commons/tastypie/resources.py | 5 +- gcloud/commons/template/models.py | 2 +- gcloud/commons/template/resources.py | 6 +- gcloud/contrib/admin/resources.py | 2 +- .../appmaker/migrations/0001_initial.py | 64 ++++-- .../migrations/0002_auto_20180209_1510.py | 57 ++--- gcloud/contrib/appmaker/models.py | 4 +- gcloud/contrib/appmaker/resources.py | 2 +- .../function/migrations/0001_initial.py | 55 +++-- gcloud/contrib/function/models.py | 4 +- gcloud/contrib/function/resources.py | 2 +- gcloud/core/migrations/0001_initial.py | 58 +++-- .../migrations/0021_auto_20210125_1943.py | 27 +++ gcloud/core/models.py | 10 +- gcloud/core/resources.py | 16 +- gcloud/core/urls.py | 8 +- gcloud/core/views.py | 29 ++- .../periodictask/migrations/0001_initial.py | 85 +++++-- gcloud/periodictask/models.py | 10 +- gcloud/periodictask/resources.py | 6 +- .../migrations/0013_auto_20210125_1943.py | 18 ++ gcloud/taskflow3/mixins.py | 2 +- gcloud/taskflow3/resources.py | 4 +- gcloud/tasktmpl3/mixins.py | 2 +- gcloud/tasktmpl3/resources.py | 4 +- {blueapps => gcloud}/utils/managermixins.py | 17 +- pipeline_web/core/models.py | 89 ++++---- pipeline_web/label/models.py | 48 ++-- requirements.txt | 11 +- 116 files changed, 2677 insertions(+), 2025 deletions(-) create mode 100644 blueapps/account/migrations/0004_create_cache_table.py create mode 100644 blueapps/contrib/bk_commands/management/app.py create mode 100644 blueapps/contrib/bk_commands/management/base.py create mode 100644 blueapps/contrib/bk_commands/management/commands/celery.py create mode 100644 blueapps/contrib/bk_commands/management/commands/celerybeat.py create mode 100644 blueapps/contrib/bk_commands/management/commands/migrate_from_djcelery.py create mode 100644 blueapps/contrib/bk_commands/management/handlers/__init__.py create mode 100644 blueapps/contrib/bk_commands/management/handlers/migrate_from_djcelery_handler.py create mode 100644 blueapps/contrib/bk_commands/test.py delete mode 100644 blueapps/utils/cache.py create mode 100644 blueapps/utils/sites/clouds/__init__.py create mode 100644 blueapps/utils/sites/ieod/__init__.py create mode 100644 blueapps/utils/sites/qcloud/__init__.py create mode 100644 blueapps/utils/sites/tencent/__init__.py create mode 100644 blueapps/utils/tools.py delete mode 100644 blueapps/utils/view_decorators.py create mode 100644 gcloud/core/migrations/0021_auto_20210125_1943.py create mode 100644 gcloud/taskflow3/migrations/0013_auto_20210125_1943.py rename {blueapps => gcloud}/utils/managermixins.py (77%) diff --git a/.flake8 b/.flake8 index e6d1325f92..85c458ef2a 100644 --- a/.flake8 +++ b/.flake8 @@ -13,6 +13,7 @@ exclude = */templates_module*, */bin/*, local/*, + blueapps/*, local_settings.py, max-line-length = 120 max-complexity = 12 diff --git a/blueapps/__init__.py b/blueapps/__init__.py index d5746bfdc0..ecd9468cfa 100644 --- a/blueapps/__init__.py +++ b/blueapps/__init__.py @@ -11,7 +11,7 @@ specific language governing permissions and limitations under the License. """ -VERSION = '2.5.1' +VERSION = "3.3.1" __version__ = VERSION @@ -20,6 +20,7 @@ def get_run_ver(): from django.conf import settings + try: return settings.RUN_VER except AttributeError: diff --git a/blueapps/account/__init__.py b/blueapps/account/__init__.py index d8994e0faa..369c6d4edb 100644 --- a/blueapps/account/__init__.py +++ b/blueapps/account/__init__.py @@ -13,8 +13,8 @@ from django.conf import settings +from blueapps.account.conf import AUTH_USER_MODEL, ConfFixture from blueapps.account.utils import load_backend -from blueapps.account.conf import ConfFixture, AUTH_USER_MODEL def get_user_model(): @@ -24,28 +24,9 @@ def get_user_model(): return load_backend(ConfFixture.USER_MODEL) -def get_bk_login_ticket(request): - form_cls = 'AuthenticationForm' - context = [request.COOKIES, request.GET] - - if request.is_rio(): - form_cls = 'RioAuthenticationForm' - context.insert(0, request.META) - - elif request.is_wechat(): - form_cls = 'WeixinAuthenticationForm' - - AuthenticationForm = load_backend("forms.{}".format(form_cls)) - - for form in (AuthenticationForm(c) for c in context): - if form.is_valid(): - return form.cleaned_data - - return {} - - if AUTH_USER_MODEL == settings.AUTH_USER_MODEL: - from django.contrib import auth + from django.contrib import auth # pylint: disable=ungrouped-imports + auth.get_user_model = get_user_model -default_app_config = 'blueapps.account.apps.AccountConfig' +default_app_config = "blueapps.account.apps.AccountConfig" # pylint: disable=invalid-name diff --git a/blueapps/account/admin.py b/blueapps/account/admin.py index ac62918412..f83f487c46 100644 --- a/blueapps/account/admin.py +++ b/blueapps/account/admin.py @@ -21,24 +21,24 @@ class UserAdmin(admin.ModelAdmin): fieldsets = ( - (None, {'fields': ('username', 'password')}), - (_('Personal info'), {'fields': ('nickname',)}), - (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', - 'groups', 'user_permissions')}), - (_('Important dates'), {'fields': ('last_login', 'date_joined')}), - ) - add_fieldsets = ( - (None, { - 'classes': ('wide',), - 'fields': ('username',), - }), + (None, {"fields": ("username", "password")}), + (_("Personal info"), {"fields": ("nickname",)}), + (_("Permissions"), {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions",)},), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), ) + add_fieldsets = ((None, {"classes": ("wide",), "fields": ("username",)}),) add_form = UserCreationForm - filter_horizontal = ['groups'] - list_display = ['username', 'nickname', 'is_active', 'is_staff', - 'is_superuser', 'last_login'] - search_fields = ['username'] - list_filter = ['is_superuser', 'is_staff', 'is_active'] + filter_horizontal = ["groups"] + list_display = [ + "username", + "nickname", + "is_active", + "is_staff", + "is_superuser", + "last_login", + ] + list_filter = ("is_superuser", "is_staff") + search_fields = ("username",) admin.site.register(User, UserAdmin) diff --git a/blueapps/account/apps.py b/blueapps/account/apps.py index 0ac1af65c5..733e044c1c 100644 --- a/blueapps/account/apps.py +++ b/blueapps/account/apps.py @@ -17,5 +17,5 @@ class AccountConfig(AppConfig): - name = 'blueapps.account' - verbose_name = _('account') + name = "blueapps.account" + verbose_name = _("account") diff --git a/blueapps/account/backends.py b/blueapps/account/backends.py index 2e86731a54..ee2b712ca0 100644 --- a/blueapps/account/backends.py +++ b/blueapps/account/backends.py @@ -14,14 +14,14 @@ from blueapps.account.conf import ConfFixture from blueapps.account.utils import load_backend -if hasattr(ConfFixture, 'USER_BACKEND'): +if hasattr(ConfFixture, "USER_BACKEND"): UserBackend = load_backend(ConfFixture.USER_BACKEND) -if hasattr(ConfFixture, 'WEIXIN_BACKEND'): +if hasattr(ConfFixture, "WEIXIN_BACKEND"): WeixinBackend = load_backend(ConfFixture.WEIXIN_BACKEND) -if hasattr(ConfFixture, 'RIO_BACKEND'): +if hasattr(ConfFixture, "RIO_BACKEND"): RioBackend = load_backend(ConfFixture.RIO_BACKEND) -if hasattr(ConfFixture, 'BK_JWT_BACKEND'): +if hasattr(ConfFixture, "BK_JWT_BACKEND"): BkJwtBackend = load_backend(ConfFixture.BK_JWT_BACKEND) diff --git a/blueapps/account/components/bk_jwt/backends.py b/blueapps/account/components/bk_jwt/backends.py index fc5e82936e..07e87872aa 100644 --- a/blueapps/account/components/bk_jwt/backends.py +++ b/blueapps/account/components/bk_jwt/backends.py @@ -15,43 +15,41 @@ from django.conf import settings from django.contrib.auth.backends import ModelBackend +from django.utils.translation import ugettext_lazy as _ + from blueapps.account import get_user_model -bkoauth_jwt_client_exists = True +BKOAUTH_JWT_CLIENT_EXISTS = True try: from bkoauth.jwt_client import JWTClient except ImportError: - bkoauth_jwt_client_exists = False + BKOAUTH_JWT_CLIENT_EXISTS = False -logger = logging.getLogger('component') +logger = logging.getLogger("component") # pylint: disable=invalid-name class BkJwtBackend(ModelBackend): - def authenticate(self, request=None): logger.debug(u"进入 BK_JWT 认证 Backend") try: verify_data = self.verify_bk_jwt_request(request) - except Exception as e: - logger.exception(u"[BK_JWT]校验异常: %s" % e) + except Exception as err: # pylint: disable=broad-except + logger.exception(u"[BK_JWT]校验异常: %s" % err) return None - if not verify_data['result'] or not verify_data['data']: - logger.error(u"BK_JWT 验证失败: %s" % ( - verify_data) - ) + if not verify_data["result"] or not verify_data["data"]: + logger.error(u"BK_JWT 验证失败: %s" % verify_data) return None - user_info = verify_data['data']['user'] + user_info = verify_data["data"]["user"] user_model = get_user_model() try: - user, _ = user_model.objects.get_or_create( - username=user_info['bk_username']) - user.nickname = user_info['bk_username'] + user, _ = user_model.objects.get_or_create(username=user_info["bk_username"]) + user.nickname = user_info["bk_username"] user.save() - except Exception as e: - logger.exception(u"自动创建 & 更新 User Model 失败: %s" % e) + except Exception as err: # pylint: disable=broad-except + logger.exception(u"自动创建 & 更新 User Model 失败: %s" % err) return None return user @@ -82,51 +80,42 @@ def verify_bk_jwt_request(request): } } """ - ret = { - 'result': False, - 'message': '', - 'data': {} - } + ret = {"result": False, "message": "", "data": {}} # 兼容bkoauth未支持jwt协议情况 - if not bkoauth_jwt_client_exists: - ret['message'] = u'bkoauth暂不支持JWT协议' + if not BKOAUTH_JWT_CLIENT_EXISTS: + ret["message"] = _(u"bkoauth暂不支持JWT协议") return ret jwt = JWTClient(request) if not jwt.is_valid: - ret['message'] = u"jwt_invalid: %s" % jwt.error_message + ret["message"] = _(u"jwt_invalid: %s") % jwt.error_message return ret # verify: user && app app = jwt.get_app_model() - if not app['verified']: - ret['message'] = app.get('valid_error_message', u'APP鉴权失败') - ret['data']['app'] = app + if not app["verified"]: + ret["message"] = app.get("valid_error_message", _(u"APP鉴权失败")) + ret["data"]["app"] = app return ret - if not app.get('bk_app_code'): - app['bk_app_code'] = app['app_code'] + if not app.get("bk_app_code"): + app["bk_app_code"] = app["app_code"] user = jwt.get_user_model() # ESB默认需要校验用户信息 - esb_white_list = True - if hasattr(settings, 'ESB_WHITE_LIST'): - esb_white_list = settings.ESB_WHITE_LIST + use_esb_white_list = getattr(settings, "USE_ESB_WHITE_LIST", True) - if not esb_white_list and not user['verified']: - ret['message'] = user.get('valid_error_message', u'用户鉴权失败') - ret['data']['user'] = user + if not use_esb_white_list and not user["verified"]: + ret["message"] = user.get("valid_error_message", _(u"用户鉴权失败且不支持ESB白名单")) + ret["data"]["user"] = user return ret - if not user.get('bk_username'): - user['bk_username'] = user['username'] + if not user.get("bk_username"): + user["bk_username"] = user["username"] - if not app['bk_app_code'] or not user['bk_username']: - ret['message'] = u'用户或来源为空' + if not app["bk_app_code"] or not user["bk_username"]: + ret["message"] = _(u"用户或来源为空") return ret - ret['result'] = True - ret['data'] = { - "user": user, - "app": app - } + ret["result"] = True + ret["data"] = {"user": user, "app": app} return ret diff --git a/blueapps/account/components/bk_jwt/middlewares.py b/blueapps/account/components/bk_jwt/middlewares.py index 68784b419d..77526db1a1 100644 --- a/blueapps/account/components/bk_jwt/middlewares.py +++ b/blueapps/account/components/bk_jwt/middlewares.py @@ -20,7 +20,7 @@ from blueapps.account.conf import ConfFixture from blueapps.account.handlers.response import ResponseHandler -logger = logging.getLogger('component') +logger = logging.getLogger("component") class BkJwtLoginRequiredMiddleware(MiddlewareMixin): @@ -31,21 +31,20 @@ def process_view(self, request, view, args, kwargs): 2. JWT签名正确 """ # 框架前置中间件,已将识别的客户端信息填充进 request - if not hasattr(request, 'is_bk_jwt') or not request.is_bk_jwt(): + if not hasattr(request, "is_bk_jwt") or not request.is_bk_jwt(): return None - logger.debug('当前请求是否经过JWT转发') - login_exempt = getattr(view, 'login_exempt', False) + logger.debug("当前请求是否经过JWT转发") + login_exempt = getattr(view, "login_exempt", False) # 每次请求都需要做校验 if not (login_exempt or request.user.is_authenticated): user = auth.authenticate(request=request) - if user: - # 登录成功,确认登陆正常后退出 + if user and user.username != request.user.username: auth.login(request, user) - if request.user.is_authenticated: - return None - + if request.user.is_authenticated: + # 登录成功,确认登陆正常后退出 + return None handler = ResponseHandler(ConfFixture, settings) return handler.build_bk_jwt_401_response(request) return None diff --git a/blueapps/account/components/bk_token/backends.py b/blueapps/account/components/bk_token/backends.py index a033322d80..fb68165c69 100644 --- a/blueapps/account/components/bk_token/backends.py +++ b/blueapps/account/components/bk_token/backends.py @@ -23,9 +23,9 @@ from blueapps.account.utils.http import send from blueapps.utils import client -logger = logging.getLogger('component') +logger = logging.getLogger("component") -ROLE_TYPE_ADMIN = '1' +ROLE_TYPE_ADMIN = "1" class TokenBackend(ModelBackend): @@ -47,34 +47,30 @@ def authenticate(self, request=None, bk_token=None): # 判断是否获取到用户信息,获取不到则返回None if not get_user_info_result: return None - user.set_property(key='qq', value=user_info.get('qq', '')) - user.set_property(key='language', - value=user_info.get('language', '')) - user.set_property(key='time_zone', - value=user_info.get('time_zone', '')) - user.set_property(key='role', value=user_info.get('role', '')) - user.set_property(key='phone', value=user_info.get('phone', '')) - user.set_property(key='email', value=user_info.get('email', '')) - user.set_property(key='wx_userid', - value=user_info.get('wx_userid', '')) - user.set_property(key='chname', value=user_info.get('chname', '')) + user.set_property(key="qq", value=user_info.get("qq", "")) + user.set_property(key="language", value=user_info.get("language", "")) + user.set_property(key="time_zone", value=user_info.get("time_zone", "")) + user.set_property(key="role", value=user_info.get("role", "")) + user.set_property(key="phone", value=user_info.get("phone", "")) + user.set_property(key="email", value=user_info.get("email", "")) + user.set_property(key="wx_userid", value=user_info.get("wx_userid", "")) + user.set_property(key="chname", value=user_info.get("chname", "")) # 用户如果不是管理员,则需要判断是否存在平台权限,如果有则需要加上 if not user.is_superuser and not user.is_staff: - role = user_info.get('role', '') + role = user_info.get("role", "") is_admin = True if str(role) == ROLE_TYPE_ADMIN else False user.is_superuser = is_admin user.is_staff = is_admin user.save() return user + except IntegrityError: logger.exception(traceback.format_exc()) - logger.exception( - u"get_or_create UserModel fail or update_or_create UserProperty" - ) + logger.exception(u"get_or_create UserModel fail or update_or_create UserProperty") return None - except Exception: + except Exception: # pylint: disable=broad-except logger.exception(traceback.format_exc()) logger.exception(u"Auto create & update UserModel fail") return None @@ -104,45 +100,41 @@ def get_user_info(bk_token): } @rtype: bool,dict """ - api_params = { - 'bk_token': bk_token - } + api_params = {"bk_token": bk_token} try: response = client.bk_login.get_user(api_params) - except Exception as e: - logger.exception(u"Abnormal error in get_user_info...:%s" % e) + except Exception as err: # pylint: disable=broad-except + logger.exception(u"Abnormal error in get_user_info...:%s" % err) return False, {} - if response.get('result') is True: + if response.get("result") is True: # 由于v1,v2的get_user存在差异,在这里屏蔽字段的差异,返回字段相同的字典 - origin_user_info = response.get('data', '') + origin_user_info = response.get("data", "") user_info = dict() # v1,v2字段相同的部分 - user_info['wx_userid'] = origin_user_info.get('wx_userid', '') - user_info['language'] = origin_user_info.get('language', '') - user_info['time_zone'] = origin_user_info.get('time_zone', '') - user_info['phone'] = origin_user_info.get('phone', '') - user_info['chname'] = origin_user_info.get('chname', '') - user_info['email'] = origin_user_info.get('email', '') - user_info['qq'] = origin_user_info.get('qq', '') + user_info["wx_userid"] = origin_user_info.get("wx_userid", "") + user_info["language"] = origin_user_info.get("language", "") + user_info["time_zone"] = origin_user_info.get("time_zone", "") + user_info["phone"] = origin_user_info.get("phone", "") + user_info["chname"] = origin_user_info.get("chname", "") + user_info["email"] = origin_user_info.get("email", "") + user_info["qq"] = origin_user_info.get("qq", "") # v2版本特有的字段 - if settings.DEFAULT_BK_API_VER == 'v2': - user_info['username'] = origin_user_info.get('bk_username', '') - user_info['role'] = origin_user_info.get('bk_role', '') + if settings.DEFAULT_BK_API_VER == "v2": + user_info["username"] = origin_user_info.get("bk_username", "") + user_info["role"] = origin_user_info.get("bk_role", "") # v1版本特有的字段 - elif settings.DEFAULT_BK_API_VER == '': - user_info['username'] = origin_user_info.get('username', '') - user_info['role'] = origin_user_info.get('role', '') + elif settings.DEFAULT_BK_API_VER == "": + user_info["username"] = origin_user_info.get("username", "") + user_info["role"] = origin_user_info.get("role", "") return True, user_info else: - error_msg = response.get('message', '') - error_data = response.get('data', '') - logger.error(u"Failed to Get User Info: error=%(err)s, ret=%(ret)s" - % { - u'err': error_msg, - u'ret': error_data, - }) + error_msg = response.get("message", "") + error_data = response.get("data", "") + logger.error( + u"Failed to Get User Info: error=%(err)s, ret=%(ret)s" % {u"err": error_msg, u"ret": error_data} + ) return False, {} @staticmethod @@ -154,24 +146,20 @@ def verify_bk_token(bk_token): @return: False,None True,username @rtype: bool,None/str """ - api_params = { - 'bk_token': bk_token - } + api_params = {"bk_token": bk_token} try: - response = send(ConfFixture.VERIFY_URL, 'GET', api_params, - verify=False) - except Exception: + response = send(ConfFixture.VERIFY_URL, "GET", api_params, verify=False) + except Exception: # pylint: disable=broad-except logger.exception(u"Abnormal error in verify_bk_token...") return False, None - if response.get('result'): - data = response.get('data') - username = data.get('username') + if response.get("result"): + data = response.get("data") + username = data.get("username") return True, username else: - error_msg = response.get('message', '') - error_data = response.get('data', '') - logger.error(u"Fail to verify bk_token, error=%s, ret=%s" % ( - error_msg, error_data)) + error_msg = response.get("message", "") + error_data = response.get("data", "") + logger.error(u"Fail to verify bk_token, error={}, ret={}".format(error_msg, error_data)) return False, None diff --git a/blueapps/account/components/bk_token/middlewares.py b/blueapps/account/components/bk_token/middlewares.py index 905d4dc224..72cc9ab38c 100644 --- a/blueapps/account/components/bk_token/middlewares.py +++ b/blueapps/account/components/bk_token/middlewares.py @@ -15,16 +15,20 @@ from django.conf import settings from django.contrib import auth +from django.core.cache import caches + +from blueapps.account.components.bk_token.forms import AuthenticationForm +from blueapps.account.conf import ConfFixture +from blueapps.account.handlers.response import ResponseHandler + try: from django.utils.deprecation import MiddlewareMixin -except Exception: +except Exception: # pylint: disable=broad-except MiddlewareMixin = object -from blueapps.account.conf import ConfFixture -from blueapps.account.handlers.response import ResponseHandler -from blueapps.account.components.bk_token.forms import AuthenticationForm -logger = logging.getLogger('component') +logger = logging.getLogger("component") +cache = caches["login_db"] class LoginRequiredMiddleware(MiddlewareMixin): @@ -34,43 +38,41 @@ def process_view(self, request, view, args, kwargs): 1. views decorated with 'login_exempt' keyword 2. User has logged in calling auth.login """ - if hasattr(request, 'is_wechat') and request.is_wechat(): + if hasattr(request, "is_wechat") and request.is_wechat(): return None - if hasattr(request, 'is_bk_jwt') and request.is_bk_jwt(): + if hasattr(request, "is_bk_jwt") and request.is_bk_jwt(): return None - if getattr(view, 'login_exempt', False): + if hasattr(request, "is_rio") and request.is_rio(): return None - user = self.authenticate(request) - if user: + if getattr(view, "login_exempt", False): return None + # 先做数据清洗再执行逻辑 + form = AuthenticationForm(request.COOKIES) + if form.is_valid(): + bk_token = form.cleaned_data["bk_token"] + session_key = request.session.session_key + if session_key: + # 确认 cookie 中的 ticket 和 cache 中的是否一致 + cache_session = cache.get(session_key) + is_match = cache_session and bk_token == cache_session.get("bk_token") + if is_match and request.user.is_authenticated: + return None + + user = auth.authenticate(request=request, bk_token=bk_token) + if user is not None and user.username != request.user.username: + auth.login(request, user) + + if user is not None and request.user.is_authenticated: + # 登录成功,重新调用自身函数,即可退出 + cache.set(session_key, {"bk_token": bk_token}, settings.LOGIN_CACHE_EXPIRED) + return self.process_view(request, view, args, kwargs) + handler = ResponseHandler(ConfFixture, settings) return handler.build_401_response(request) def process_response(self, request, response): return response - - @staticmethod - def authenticate(request): - # 先做数据清洗再执行逻辑 - form = AuthenticationForm(request.COOKIES) - if not form.is_valid(): - return None - - bk_token = form.cleaned_data['bk_token'] - # 确认 cookie 中的 bk_token 和 session 中的是否一致 - # 如果登出删除 cookie 后 session 存在 is_match 为False - is_match = (bk_token == request.session.get('bk_token')) - if is_match and request.user.is_authenticated: - return request.user - - user = auth.authenticate(request=request, - bk_token=bk_token) - if user: - # 登录成功,记录 user 信息 - auth.login(request, user) - request.session['bk_token'] = bk_token - return user diff --git a/blueapps/account/components/bk_token/models.py b/blueapps/account/components/bk_token/models.py index cde1ad7ca5..bd9adc1a48 100644 --- a/blueapps/account/components/bk_token/models.py +++ b/blueapps/account/components/bk_token/models.py @@ -11,10 +11,8 @@ specific language governing permissions and limitations under the License. """ -from blueapps.account.models import ( - User as BaseUser, - UserManager as BaseUserManager -) +from blueapps.account.models import User as BaseUser +from blueapps.account.models import UserManager as BaseUserManager class UserProxyManager(BaseUserManager): diff --git a/blueapps/account/components/null/__init__.py b/blueapps/account/components/null/__init__.py index 09f0763213..df67274d2b 100644 --- a/blueapps/account/components/null/__init__.py +++ b/blueapps/account/components/null/__init__.py @@ -19,6 +19,5 @@ class NullMiddleware(MiddlewareMixin): class NullBackend(object): - def authenticate(self, **kwargs): return None diff --git a/blueapps/account/components/weixin/backends.py b/blueapps/account/components/weixin/backends.py index c4686caf24..65dbb8ec14 100644 --- a/blueapps/account/components/weixin/backends.py +++ b/blueapps/account/components/weixin/backends.py @@ -15,15 +15,14 @@ from django.contrib.auth.backends import ModelBackend -from blueapps.account.utils.http import send from blueapps.account import get_user_model from blueapps.account.conf import ConfFixture +from blueapps.account.utils.http import send -logger = logging.getLogger('component') +logger = logging.getLogger("component") class WeixinBackend(ModelBackend): - def authenticate(self, request=None, code=None, is_wechat=True): """ is_wechat 参数是为了使得 WeixinBackend 与其他 Backend 参数个数不同,在框架选择 @@ -34,21 +33,18 @@ def authenticate(self, request=None, code=None, is_wechat=True): return None result, user_info = self.verify_weixin_code(code) - logger.debug(u"微信 CODE 验证结果,result:%s,user_info:%s" % ( - result, user_info) - ) + logger.debug(u"微信 CODE 验证结果,result:{},user_info:{}".format(result, user_info)) if not result: return None user_model = get_user_model() try: - user, _ = user_model.objects.get_or_create( - username=user_info['username']) - user.nickname = user_info['username'] - user.avatar_url = user_info['avatar'] + user, _ = user_model.objects.get_or_create(username=user_info["username"]) + user.nickname = user_info["username"] + user.avatar_url = user_info["avatar"] user.save() - except Exception: + except Exception: # pylint: disable=broad-except logger.exception(u"自动创建 & 更新 User Model 失败") else: return user @@ -74,17 +70,16 @@ def verify_weixin_code(code): } """ api_params = { - 'code': code, + "code": code, } try: - response = send(ConfFixture.WEIXIN_INFO_URL, 'GET', api_params) - ret = response.get('ret') + response = send(ConfFixture.WEIXIN_INFO_URL, "GET", api_params) + ret = response.get("ret") if ret == 0: - return True, response['data'] + return True, response["data"] else: - logger.error(u"通过微信授权码,获取用户信息失败,error=%s,ret=%s" % ( - response['msg'], ret)) + logger.error(u"通过微信授权码,获取用户信息失败,error={},ret={}".format(response["msg"], ret)) return False, None - except Exception: + except Exception: # pylint: disable=broad-except logger.exception(u"通过微信授权码,获取用户信息异常") return False, None diff --git a/blueapps/account/components/weixin/middlewares.py b/blueapps/account/components/weixin/middlewares.py index 7beecbb40b..eba40d497b 100644 --- a/blueapps/account/components/weixin/middlewares.py +++ b/blueapps/account/components/weixin/middlewares.py @@ -19,11 +19,11 @@ from django.contrib import auth from django.utils.deprecation import MiddlewareMixin +from blueapps.account.components.weixin.forms import WeixinAuthenticationForm from blueapps.account.conf import ConfFixture from blueapps.account.handlers.response import ResponseHandler -from blueapps.account.components.weixin.forms import WeixinAuthenticationForm -logger = logging.getLogger('component') +logger = logging.getLogger("component") class WeixinLoginRequiredMiddleware(MiddlewareMixin): @@ -37,34 +37,26 @@ def process_view(self, request, view, args, kwargs): if not request.is_wechat(): return None - logger.debug('当前请求客户端为微信端') - login_exempt = getattr(view, 'login_exempt', False) + logger.debug("当前请求客户端为微信端") + login_exempt = getattr(view, "login_exempt", False) if not (login_exempt or request.user.is_authenticated): form = WeixinAuthenticationForm(request.GET) if form.is_valid(): - code = form.cleaned_data['code'] - state = form.cleaned_data['state'] - logger.debug( - u"微信请求链接,检测到微信验证码,code:%s,state:%s" % ( - code, - state - ) - ) + code = form.cleaned_data["code"] + state = form.cleaned_data["state"] + logger.debug(u"微信请求链接,检测到微信验证码,code:{},state:{}".format(code, state)) if self.valid_state(request, state): - user = auth.authenticate(request=request, code=code, - is_wechat=True) - if user: - # 登录成功,重新调用自身函数,即可退出 + user = auth.authenticate(request=request, code=code, is_wechat=True) + if user and user.username != request.user.username: auth.login(request, user) - return self.process_view(request, view, args, kwargs) + if request.user.is_authenticated: + # 登录成功,确认登陆正常后退出 + return None else: - logger.debug( - u"微信请求链接,未检测到微信验证码,url:%s,params:%s" % ( - request.path_info, request.GET) - ) + logger.debug(u"微信请求链接,未检测到微信验证码,url:{},params:{}".format(request.path_info, request.GET)) self.set_state(request) handler = ResponseHandler(ConfFixture, settings) @@ -80,40 +72,29 @@ def set_state(self, request, length=32): 附带上的参数,认证服务器的回应必须一模一样包含这个参数,此处将 state 设置在 session """ - allowed_chars = ('abcdefghijkmnpqrstuvwxyz' - 'ABCDEFGHIJKLMNPQRSTUVWXYZ' - '0123456789') - state = ''.join(random.choice(allowed_chars) - for _ in range(length)) - request.session['WEIXIN_OAUTH_STATE'] = state - request.session['WEIXIN_OAUTH_STATE_TIMESTAMP'] = time.time() + allowed_chars = "abcdefghijkmnpqrstuvwxyz" "ABCDEFGHIJKLMNPQRSTUVWXYZ" "0123456789" + state = "".join(random.choice(allowed_chars) for _ in range(length)) + request.session["WEIXIN_OAUTH_STATE"] = state + request.session["WEIXIN_OAUTH_STATE_TIMESTAMP"] = time.time() return True def valid_state(self, request, state, expires_in=60): """ 验证微信认证服务器返回的 code & state 是否合法 """ - raw_state = request.session.get('WEIXIN_OAUTH_STATE') - raw_timestamp = request.session.get('WEIXIN_OAUTH_STATE_TIMESTAMP') + raw_state = request.session.get("WEIXIN_OAUTH_STATE") + raw_timestamp = request.session.get("WEIXIN_OAUTH_STATE_TIMESTAMP") if not raw_state or raw_state != state: logger.warning( - (u"验证 WEIXIN 服务器返回信息,state 不一致," - u"WEIXIN_OAUTH_STATE=%s,state=%s") % ( - raw_state, state - ) + u"验证 WEIXIN 服务器返回信息,state 不一致," u"WEIXIN_OAUTH_STATE=%s,state=%s" % raw_state, state, ) return False if not raw_timestamp or time.time() - raw_timestamp > expires_in: - logger.warning( - (u"验证 WEIXIN 服务器返回信息,state 过期," - u"WEIXIN_OAUTH_STATE_TIMESTAMP=%s") % ( - raw_timestamp - ) - ) + logger.warning(u"验证 WEIXIN 服务器返回信息,state 过期," u"WEIXIN_OAUTH_STATE_TIMESTAMP=%s" % raw_timestamp) return False - request.session['WEIXIN_OAUTH_STATE'] = None - request.session['WEIXIN_OAUTH_STATE_TIMESTAMP'] = None + request.session["WEIXIN_OAUTH_STATE"] = None + request.session["WEIXIN_OAUTH_STATE_TIMESTAMP"] = None return True diff --git a/blueapps/account/conf.py b/blueapps/account/conf.py index 0fdafa5cca..917b0edd26 100644 --- a/blueapps/account/conf.py +++ b/blueapps/account/conf.py @@ -11,15 +11,15 @@ specific language governing permissions and limitations under the License. """ -from django.utils.module_loading import import_string -from django.core.exceptions import ImproperlyConfigured from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.module_loading import import_string +from django.utils.translation import gettext_lazy as _ from blueapps.account.sites.default import ConfFixture as default_fixture class _ConfFixture(object): - def __init__(self, fixture_module): # store the module self._fixture = import_string(fixture_module) @@ -34,19 +34,18 @@ def __getattr__(self, name): setting = getattr(default_fixture, name) if setting is None: raise ImproperlyConfigured( - 'Requested %s, but ConfFixture are not configured. ' - 'You must set options in ConfFixture in right site.conf.py' - % (name)) + "Requested %s, but ConfFixture are not configured. " + "You must set options in ConfFixture in right site.conf.py" % name + ) return setting - raise KeyError('%s not exist' % name) + raise KeyError("%s not exist" % name) -mod = 'blueapps.account.sites.{VER}.conf.ConfFixture'.format( - VER=settings.RUN_VER) -ConfFixture = _ConfFixture(mod) +MOD = "blueapps.account.sites.{VER}.conf.ConfFixture".format(VER=settings.RUN_VER) +ConfFixture = _ConfFixture(MOD) -AUTH_USER_MODEL = 'account.User' +AUTH_USER_MODEL = "account.User" ###################### # 二次验证配置默认参数 # @@ -54,8 +53,8 @@ def __getattr__(self, name): # 短信验证有效时间 SECOND_VERIFY_CONF = { - 'VALID_MINUTES': 5, - 'RETRY_MINUTES': 3, - 'SMS_FORMAT': u'您正在蓝鲸应用上执行敏感操作,验证码:{}', - 'CODE_NAME': 'bk_verify_code' + "VALID_MINUTES": 5, + "RETRY_MINUTES": 3, + "SMS_FORMAT": _(u"您正在蓝鲸应用上执行敏感操作,验证码:{}"), + "CODE_NAME": "bk_verify_code", } diff --git a/blueapps/account/decorators.py b/blueapps/account/decorators.py index bbe4062963..3fd02e6d6c 100644 --- a/blueapps/account/decorators.py +++ b/blueapps/account/decorators.py @@ -16,7 +16,9 @@ def login_exempt(view_func): """"Mark a view function as being exempt from login view protection""" + def wrapped_view(*args, **kwargs): return view_func(*args, **kwargs) + wrapped_view.login_exempt = True return wraps(view_func)(wrapped_view) diff --git a/blueapps/account/forms.py b/blueapps/account/forms.py index e38bcf6d0d..28255d84fb 100644 --- a/blueapps/account/forms.py +++ b/blueapps/account/forms.py @@ -17,7 +17,6 @@ class UserCreationForm(forms.ModelForm): - class Meta: model = User - fields = ('username',) + fields = ("username",) diff --git a/blueapps/account/handlers/response.py b/blueapps/account/handlers/response.py index ea95590a23..81597c33cd 100644 --- a/blueapps/account/handlers/response.py +++ b/blueapps/account/handlers/response.py @@ -13,15 +13,16 @@ from django.conf import settings from django.http import HttpResponseRedirect, JsonResponse +from django.utils.translation import ugettext_lazy as _ + +from blueapps.account.utils.http import build_redirect_url +from blueapps.core.exceptions import BkJwtVerifyError, RioVerifyError try: from django.urls import reverse -except Exception: +except Exception: # pylint: disable=broad-except from django.core.urlresolvers import reverse -from blueapps.account.utils.http import build_redirect_url -from blueapps.core.exceptions import RioVerifyError, BkJwtVerifyError - class ResponseHandler(object): def __init__(self, _confFixture, _settings): @@ -63,7 +64,7 @@ def _build_ajax_401_response(self, request): _next = self._conf.CROSS_PREFIX + _next _login_url = build_redirect_url( - _next, self._conf.LOGIN_PLAIN_URL, self._conf.C_URL, extra_args=self._build_extra_args() + _next, self._conf.LOGIN_PLAIN_URL, self._conf.C_URL, extra_args=self._build_extra_args(), ) context = { @@ -98,7 +99,7 @@ def _build_page_401_response_to_platform(self, request): _next = self._conf.CROSS_PREFIX + _next _login_url = build_redirect_url( - _next, self._conf.LOGIN_URL, self._conf.C_URL, extra_args=self._build_extra_args() + _next, self._conf.LOGIN_URL, self._conf.C_URL, extra_args=self._build_extra_args(), ) return HttpResponseRedirect(_login_url) @@ -127,12 +128,20 @@ def build_weixin_401_response(self, request): return HttpResponseRedirect(_redirect) def build_rio_401_response(self, request): - context = {"result": False, "code": RioVerifyError.ERROR_CODE, "message": u"您的登陆请求无法经智能网关正常检测,请与管理人员联系"} + context = { + "result": False, + "code": RioVerifyError.ERROR_CODE, + "message": _(u"您的登陆请求无法经智能网关正常检测,请与管理人员联系"), + } return JsonResponse(context, status=401) def build_bk_jwt_401_response(self, request): """ BK_JWT鉴权异常 """ - context = {"result": False, "code": BkJwtVerifyError.ERROR_CODE, "message": u"您的登陆请求无法经BK JWT检测,请与管理人员联系"} + context = { + "result": False, + "code": BkJwtVerifyError.ERROR_CODE, + "message": _(u"您的登陆请求无法经BK JWT检测,请与管理人员联系"), + } return JsonResponse(context, status=401) diff --git a/blueapps/account/middlewares.py b/blueapps/account/middlewares.py index ea1fb010b9..f3ae537c4a 100644 --- a/blueapps/account/middlewares.py +++ b/blueapps/account/middlewares.py @@ -17,23 +17,18 @@ def load_middleware(middleware): - path = 'blueapps.account.components.{middleware}'.format( - middleware=middleware) + path = "blueapps.account.components.{middleware}".format(middleware=middleware) return import_string(path) -if hasattr(ConfFixture, 'LOGIN_REQUIRED_MIDDLEWARE'): - LoginRequiredMiddleware = load_middleware( - ConfFixture.LOGIN_REQUIRED_MIDDLEWARE) +if hasattr(ConfFixture, "LOGIN_REQUIRED_MIDDLEWARE"): + LoginRequiredMiddleware = load_middleware(ConfFixture.LOGIN_REQUIRED_MIDDLEWARE) -if hasattr(ConfFixture, 'WEIXIN_MIDDLEWARE'): - WeixinLoginRequiredMiddleware = load_middleware( - ConfFixture.WEIXIN_MIDDLEWARE) +if hasattr(ConfFixture, "WEIXIN_MIDDLEWARE"): + WeixinLoginRequiredMiddleware = load_middleware(ConfFixture.WEIXIN_MIDDLEWARE) -if hasattr(ConfFixture, 'RIO_MIDDLEWARE'): - RioLoginRequiredMiddleware = load_middleware( - ConfFixture.RIO_MIDDLEWARE) +if hasattr(ConfFixture, "RIO_MIDDLEWARE"): + RioLoginRequiredMiddleware = load_middleware(ConfFixture.RIO_MIDDLEWARE) -if hasattr(ConfFixture, 'BK_JWT_MIDDLEWARE'): - BkJwtLoginRequiredMiddleware = load_middleware( - ConfFixture.BK_JWT_MIDDLEWARE) +if hasattr(ConfFixture, "BK_JWT_MIDDLEWARE"): + BkJwtLoginRequiredMiddleware = load_middleware(ConfFixture.BK_JWT_MIDDLEWARE) diff --git a/blueapps/account/migrations/0001_initial.py b/blueapps/account/migrations/0001_initial.py index 688dd25531..cabff94a62 100644 --- a/blueapps/account/migrations/0001_initial.py +++ b/blueapps/account/migrations/0001_initial.py @@ -11,12 +11,13 @@ specific language governing permissions and limitations under the License. """ +# Generated by Django 1.11.2 on 2017-08-09 14:51 from __future__ import unicode_literals -import django.utils.timezone import django.core.validators -from django.db import migrations, models +import django.utils.timezone from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -24,56 +25,132 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0008_alter_user_username_max_length'), + ("auth", "0008_alter_user_username_max_length"), ] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that openid already exists.'}, help_text='Required. 64 characters or fewer. Letters, digits and underlined only.', max_length=64, unique=True, validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9_]+$', 'Enter a valid openid. This value may contain only letters, numbers and underlined characters.', 'invalid')], verbose_name='username')), - ('nickname', models.CharField(blank=True, help_text='Required. 64 characters or fewer.', max_length=64, verbose_name='nick name')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID",),), + ("password", models.CharField(max_length=128, verbose_name="password")), + ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login"),), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={"unique": "A user with that openid already exists."}, + help_text="Required. 64 characters or fewer. Letters, digits and underlined only.", + max_length=64, + unique=True, + validators=[ + django.core.validators.RegexValidator( + "^[a-zA-Z0-9_]+$", + "Enter a valid openid. This value may contain only letters, " + "numbers and underlined characters.", + "invalid", + ) + ], + verbose_name="username", + ), + ), + ( + "nickname", + models.CharField( + blank=True, + help_text="Required. 64 characters or fewer.", + max_length=64, + verbose_name="nick name", + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. " + "Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. " + "A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - }, + options={"verbose_name": "user", "verbose_name_plural": "users",}, ), migrations.CreateModel( - name='UserProperty', + name="UserProperty", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and underlined only.', max_length=64, validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9_]+$', 'Enter a valid key. This value may contain only letters, numbers and underlined characters.', 'invalid')])), - ('value', models.TextField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties', to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID",),), + ( + "key", + models.CharField( + help_text="Required. 64 characters or fewer. Letters, digits and underlined only.", + max_length=64, + validators=[ + django.core.validators.RegexValidator( + "^[a-zA-Z0-9_]+$", + "Enter a valid key. This value may contain only letters, " + "numbers and underlined characters.", + "invalid", + ) + ], + ), + ), + ("value", models.TextField()), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="properties", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'db_table': 'account_user_property', - 'verbose_name': 'user property', - 'verbose_name_plural': 'user properties', + "db_table": "account_user_property", + "verbose_name": "user property", + "verbose_name_plural": "user properties", }, ), migrations.CreateModel( - name='UserProxy', - fields=[ - ], - options={ - 'proxy': True, - 'indexes': [], - }, - bases=('account.user',), - ), - migrations.AlterUniqueTogether( - name='userproperty', - unique_together=set([('user', 'key')]), + name="UserProxy", fields=[], options={"proxy": True, "indexes": [],}, bases=("account.user",), ), + migrations.AlterUniqueTogether(name="userproperty", unique_together={("user", "key")},), ] diff --git a/blueapps/account/migrations/0002_init_superuser.py b/blueapps/account/migrations/0002_init_superuser.py index 48e7ec9d35..fa25741838 100644 --- a/blueapps/account/migrations/0002_init_superuser.py +++ b/blueapps/account/migrations/0002_init_superuser.py @@ -11,8 +11,8 @@ specific language governing permissions and limitations under the License. """ -from django.db import migrations from django.conf import settings +from django.db import migrations def load_data(apps, schema_editor): @@ -22,15 +22,10 @@ def load_data(apps, schema_editor): User = apps.get_model("account", "User") for name in settings.INIT_SUPERUSER: User.objects.update_or_create( - username=name, - defaults={'is_staff': True, 'is_active': True, 'is_superuser': True} + username=name, defaults={"is_staff": True, "is_active": True, "is_superuser": True}, ) class Migration(migrations.Migration): - dependencies = [ - ('account', '0001_initial') - ] - operations = [ - migrations.RunPython(load_data) - ] + dependencies = [("account", "0001_initial")] + operations = [migrations.RunPython(load_data)] diff --git a/blueapps/account/migrations/0003_verifyinfo.py b/blueapps/account/migrations/0003_verifyinfo.py index 2819a86614..a73a66fa77 100644 --- a/blueapps/account/migrations/0003_verifyinfo.py +++ b/blueapps/account/migrations/0003_verifyinfo.py @@ -11,27 +11,28 @@ specific language governing permissions and limitations under the License. """ +# Generated by Django 1.11.2 on 2018-12-29 14:30 from __future__ import unicode_literals +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('account', '0002_init_superuser'), + ("account", "0002_init_superuser"), ] operations = [ migrations.CreateModel( - name='VerifyInfo', + name="VerifyInfo", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.CharField(max_length=6)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID",),), + ("code", models.CharField(max_length=6)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL,),), ], ), ] diff --git a/blueapps/account/migrations/0004_create_cache_table.py b/blueapps/account/migrations/0004_create_cache_table.py new file mode 100644 index 0000000000..4efa099b6d --- /dev/null +++ b/blueapps/account/migrations/0004_create_cache_table.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from django.core.management import call_command +from django.db import migrations + + +def create_cache_table(apps, schema_editor): + """ + 创建 cache table + """ + call_command("createcachetable", "account_cache") + + +class Migration(migrations.Migration): + + dependencies = [ + ("account", "0003_verifyinfo"), + ] + + operations = [migrations.RunPython(create_cache_table)] diff --git a/blueapps/account/models.py b/blueapps/account/models.py index 2b8f19a862..c9436ad8ce 100644 --- a/blueapps/account/models.py +++ b/blueapps/account/models.py @@ -12,15 +12,18 @@ """ from __future__ import unicode_literals + import datetime -import random import logging +import random import traceback +from django.conf import settings from django.contrib.auth.models import ( - AbstractBaseUser, BaseUserManager, PermissionsMixin, + AbstractBaseUser, + BaseUserManager, + PermissionsMixin, ) -from django.conf import settings from django.core import validators from django.db import models from django.utils import timezone @@ -33,92 +36,86 @@ # 结合获取用户配置的二步验证配置结果 SV_CONF = conf.SECOND_VERIFY_CONF -user_sv_conf = getattr(settings, 'SECOND_VERIFY_CONF', {}) +user_sv_conf = getattr(settings, "SECOND_VERIFY_CONF", {}) SV_CONF.update(user_sv_conf) -logger = logging.getLogger('app') +logger = logging.getLogger("app") class UserManager(BaseUserManager): - - def _create_user(self, username, is_staff=False, is_superuser=False, - password=None, **extra_fields): + def _create_user(self, username, is_staff=False, is_superuser=False, password=None, **extra_fields): now = timezone.now() if not username: - raise ValueError('The given username must be set') - user = self.model(username=username, is_active=True, - is_staff=is_staff, is_superuser=is_superuser, - date_joined=now, **extra_fields) + raise ValueError(_("The given username must be set")) + user = self.model( + username=username, + is_active=True, + is_staff=is_staff, + is_superuser=is_superuser, + date_joined=now, + **extra_fields + ) if password: user.set_password(password) user.save(using=self._db) return user def create_user(self, username, password=None, **extra_fields): - return self._create_user(username, False, False, password, - **extra_fields) + return self._create_user(username, False, False, password, **extra_fields) def create_superuser(self, username, password=None, **extra_fields): - return self._create_user(username, True, True, password, - **extra_fields) + return self._create_user(username, True, True, password, **extra_fields) class User(AbstractBaseUser, PermissionsMixin): username = models.CharField( - _('username'), + _("username"), max_length=64, unique=True, - help_text=_('Required. 64 characters or fewer. Letters, ' - 'digits and underlined only.'), + help_text=_("Required. 64 characters or fewer. Letters, " "digits and underlined only."), validators=[ - validators.RegexValidator(r'^[a-zA-Z0-9_]+$', - _('Enter a valid openid. ' - 'This value may contain only letters, ' - 'numbers and underlined characters.'), - 'invalid'), + validators.RegexValidator( + r"^[a-zA-Z0-9_]+$", + _( + "Enter a valid openid. " + "This value may contain only letters, " + "numbers and underlined characters." + ), + "invalid", + ), ], - error_messages={ - 'unique': _("A user with that openid already exists."), - }, + error_messages={"unique": _("A user with that openid already exists.")}, ) nickname = models.CharField( - _('nick name'), - max_length=64, - blank=True, - help_text=_('Required. 64 characters or fewer.'), + _("nick name"), max_length=64, blank=True, help_text=_("Required. 64 characters or fewer."), ) is_staff = models.BooleanField( - _('staff status'), - default=False, - help_text=_('Designates whether the user can log into this ' - 'admin site.'), + _("staff status"), default=False, help_text=_("Designates whether the user can log into this " "admin site."), ) is_active = models.BooleanField( - _('active'), + _("active"), default=True, - help_text=_('Designates whether this user should be treated as ' - 'active. Unselect this instead of deleting accounts.'), - ) - date_joined = models.DateTimeField( - _('date joined'), - default=timezone.now, + help_text=_( + "Designates whether this user should be treated as " "active. Unselect this instead of deleting accounts." + ), ) + date_joined = models.DateTimeField(_("date joined"), default=timezone.now,) objects = UserManager() - USERNAME_FIELD = 'username' - REQUIRED_FIELDS = ['nickname'] + USERNAME_FIELD = "username" + REQUIRED_FIELDS = ["nickname"] class Meta: - verbose_name = _('user') - verbose_name_plural = _('users') + verbose_name = _("user") + verbose_name_plural = _("users") # Pass platform default user table # db_table = 'auth_user' def get_full_name(self): - full_name = '%s(%s)' % (self.username, self.nickname) + full_name = "{}({})".format(self.username, self.nickname) return full_name.strip() def get_short_name(self): @@ -137,32 +134,26 @@ def set_property(self, key, value): @property def avatar_url(self): - return self.get_property('avatar_url') + return self.get_property("avatar_url") @avatar_url.setter def avatar_url(self, a_url): - self.set_property('avatar_url', a_url) + self.set_property("avatar_url", a_url) def send_sms(self, code): try: - result = sms.send_sms([self.username], SV_CONF['SMS_FORMAT'].format(code)) - except Exception: + result = sms.send_sms([self.username], SV_CONF["SMS_FORMAT"].format(code)) + except Exception: # pylint: disable=broad-except logger.error( - u'cmsi.send_sms_for_external_user failed. ' - 'username->[%s], code->[%s] for->[%s]' % (self.username, code, traceback.format_exc()) + "cmsi.send_sms_for_external_user failed. " + "username->[%s], code->[%s] for->[%s]" % (self.username, code, traceback.format_exc()) ) - return { - 'result': False, - 'message': u'ESB发送短信接口错误,可能由权限问题导致' - } - return { - 'result': result['result'], - 'message': result['message'] - } + return {"result": False, "message": _("ESB发送短信接口错误,可能由权限问题导致")} + return {"result": result["result"], "message": result["message"]} def send_code(self): - now = datetime.datetime.now() + now = timezone.now() v_info = VerifyInfo.objects.filter(user=self) v_info_cnt = v_info.count() if v_info_cnt == 0: @@ -171,46 +162,37 @@ def send_code(self): code = random.randint(111111, 999999) VerifyInfo.objects.create(user=self, code=code) ret = self.send_sms(code) - if ret['result']: - ret['message'] = u'初始化验证码,发送成功' + if ret["result"]: + ret["message"] = _("初始化验证码,发送成功") elif v_info_cnt == 1: cur = v_info[0] - if cur.updated_at >= now - datetime.timedelta( - minutes=SV_CONF['VALID_MINUTES'] - ): + if cur.updated_at >= now - datetime.timedelta(minutes=SV_CONF["VALID_MINUTES"]): # 早前生成过验证码,且未过期 - if cur.updated_at < now - datetime.timedelta( - minutes=SV_CONF['RETRY_MINUTES'] - ): + if cur.updated_at < now - datetime.timedelta(minutes=SV_CONF["RETRY_MINUTES"]): # 重发已生成的 ret = self.send_sms(cur.code) - if ret['result']: - ret['message'] = u'已生成的验证码,重发成功' + if ret["result"]: + ret["message"] = _("已生成的验证码,重发成功") else: # 等待时间不足,不重发 - ret = {'result': False, 'message': u'暂不能重发验证码,请稍等'} + ret = {"result": False, "message": _("暂不能重发验证码,请稍等")} else: # 已过期,重新生成并重发 new_code = random.randint(111111, 999999) cur.code = new_code cur.save() ret = self.send_sms(new_code) - if ret['result']: - ret['message'] = u'重新生成验证码,发送成功' + if ret["result"]: + ret["message"] = _("重新生成验证码,发送成功") else: - logger.error( - u'found more than one code of the user->[%s]' % self.id - ) - ret = {'result': False, 'message': u'数据库中的验证码异常'} + logger.error("found more than one code of the user->[%s]" % self.id) + ret = {"result": False, "message": _("数据库中的验证码异常")} return ret def verify_code(self, code): check = VerifyInfo.objects.filter( - user=self, code=code, - updated_at__gt=datetime.datetime.now() - datetime.timedelta( - minutes=SV_CONF['VALID_MINUTES'] - ) + user=self, code=code, updated_at__gt=timezone.now() - datetime.timedelta(minutes=SV_CONF["VALID_MINUTES"]), ).count() if check == 1: # 一个验证码只能用一次 用完删除 @@ -223,30 +205,26 @@ class UserProperty(models.Model): """ Add user extra property """ - user = models.ForeignKey( - User, - on_delete=models.CASCADE, - related_name='properties', - ) + + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="properties",) key = models.CharField( max_length=64, - help_text=_('Required. 64 characters or fewer. Letters, ' - 'digits and underlined only.'), + help_text=_("Required. 64 characters or fewer. Letters, " "digits and underlined only."), validators=[ - validators.RegexValidator(r'^[a-zA-Z0-9_]+$', - _('Enter a valid key. ' - 'This value may contain only letters, ' - 'numbers and underlined characters.'), - 'invalid'), + validators.RegexValidator( + r"^[a-zA-Z0-9_]+$", + _("Enter a valid key. " "This value may contain only letters, " "numbers and underlined characters."), + "invalid", + ), ], ) value = models.TextField() class Meta: - verbose_name = _('user property') - verbose_name_plural = _('user properties') - db_table = 'account_user_property' - unique_together = (('user', 'key'),) + verbose_name = _("user property") + verbose_name_plural = _("user properties") + db_table = "account_user_property" + unique_together = (("user", "key"),) class VerifyInfo(models.Model): diff --git a/blueapps/account/sites/default.py b/blueapps/account/sites/default.py index 507ea9bea8..3a4e70b25c 100644 --- a/blueapps/account/sites/default.py +++ b/blueapps/account/sites/default.py @@ -16,6 +16,7 @@ class ConfFixture(object): """ 登录模块项目变量汇总 """ + ################# # 浏览器参数说明 # ################# @@ -47,18 +48,18 @@ class ConfFixture(object): # 跳转至登录平台是否加跨域前缀标识 # http://xxx.com/login/?c_url={CROSS_PREFIX}http%3A//xxx.com%3A8000/ ADD_CROSS_PREFIX = True - CROSS_PREFIX = '' + CROSS_PREFIX = "" # 跳转至登录平台是否加上APP_CODE # http://xxx.com/login/?c_url=http%3A//xxx.com%3A8000/&app_code=xxx ADD_APP_CODE = True # http://xxx.com/login/?c_url=http%3A//xxx.com%3A8000/&{APP_KEY}=xxx - APP_KEY = 'app_code' - SETTINGS_APP_KEY = 'APP_CODE' + APP_KEY = "app_code" + SETTINGS_APP_KEY = "APP_CODE" # 跳转至登录平台,回调参数名称 # http://xxx.com/login/?{C_URL}=http%3A//xxx.com%3A8000/ - C_URL = 'c_url' + C_URL = "c_url" # 内嵌式的登录平台的尺寸大小,决定前端适配的弹框大小 IFRAME_HEIGHT = 490 diff --git a/blueapps/account/static/account/login.js b/blueapps/account/static/account/login.js index e36b0fa655..96d05e1161 100644 --- a/blueapps/account/static/account/login.js +++ b/blueapps/account/static/account/login.js @@ -1,20 +1,19 @@ /** - * Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community - * Edition) available. - * Copyright (C) 2017-2020 THL A29 Limited, a Tencent company. All rights reserved. - * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://opensource.org/licenses/MIT - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - +* Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +* Edition) available. +* Copyright (C) 2017-2020 THL A29 Limited, a Tencent company. All rights reserved. +* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://opensource.org/licenses/MIT +* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +* specific language governing permissions and limitations under the License. +*/ /** * 登录相关 JS,其中 remote_static_url & static_url 来源于全局变量 */ -document.write(" `); /** * 对 AJAX 请求做一些统一公共处理,目前主要是对登录页面做处理 @@ -22,7 +21,7 @@ document.write(" @@ -10,7 +11,7 @@