From 83ceefd7eea02751ebf2474bd8d76a83e5a89b9f Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Tue, 28 Dec 2021 11:44:20 +0800 Subject: [PATCH 01/19] =?UTF-8?q?fix:=20=E4=BA=8C=E7=BA=A7=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98=20#158?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/src/views/organization/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/src/views/organization/index.vue b/src/pages/src/views/organization/index.vue index 38fbd3c9d..4bad1d570 100644 --- a/src/pages/src/views/organization/index.vue +++ b/src/pages/src/views/organization/index.vue @@ -1278,9 +1278,9 @@ export default { const next = event.target.nextElementSibling; next.style.left = `${calculateDistance.getOffsetLeft + 20}px`; next.style.top = `${calculateDistance.getOffsetTop + 30}px`; - if (differ <= 280) { + if (differ <= 178) { next.style.top = 'auto'; - next.style.bottom = `${differ}px`; + next.style.bottom = `${differ + 7}px`; } }); }, From f4113772ca995a62fe23547a0883b9ece673c2da Mon Sep 17 00:00:00 2001 From: IMBlues Date: Wed, 3 Nov 2021 12:05:31 +0800 Subject: [PATCH 02/19] chore: add helm chart for login module --- deploy/helm/saas/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/saas/values.yaml b/deploy/helm/saas/values.yaml index b65fe30b8..54eb12b73 100644 --- a/deploy/helm/saas/values.yaml +++ b/deploy/helm/saas/values.yaml @@ -79,7 +79,7 @@ env: BK_APP_CODE: "bk_usermgr" DJANGO_SETTINGS_MODULE: "bkuser_shell.config.overlays.prod" BKAPP_BK_USER_CORE_API_HOST: "http://bkuserapi-web" - BK_LOGIN_API_URL: "http://bk-login-web" + BK_LOGIN_API_URL: "http://bklogin-web" # 容器化版本默认采用子域名形式暴露服务 SITE_URL: "/" From b4505e2909d411748b1ea6fb5bb0a573b45c7165 Mon Sep 17 00:00:00 2001 From: IMBlues Date: Mon, 8 Nov 2021 16:05:35 +0800 Subject: [PATCH 03/19] fix: adjust minor config according to review --- deploy/helm/saas/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/saas/values.yaml b/deploy/helm/saas/values.yaml index 54eb12b73..b65fe30b8 100644 --- a/deploy/helm/saas/values.yaml +++ b/deploy/helm/saas/values.yaml @@ -79,7 +79,7 @@ env: BK_APP_CODE: "bk_usermgr" DJANGO_SETTINGS_MODULE: "bkuser_shell.config.overlays.prod" BKAPP_BK_USER_CORE_API_HOST: "http://bkuserapi-web" - BK_LOGIN_API_URL: "http://bklogin-web" + BK_LOGIN_API_URL: "http://bk-login-web" # 容器化版本默认采用子域名形式暴露服务 SITE_URL: "/" From e4061abb46dbef7970e977c64a43fe551593660b Mon Sep 17 00:00:00 2001 From: IMBlues Date: Tue, 9 Nov 2021 21:12:29 +0800 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E8=BF=87=E6=9C=9F=E8=BF=94=E5=9B=9Etoken=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E5=AF=86=E7=A0=81=E9=93=BE=E6=8E=A5=20&=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=88=9D=E5=A7=8B=E5=AF=86=E7=A0=81=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bkuser_core/profiles/views.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/api/bkuser_core/profiles/views.py b/src/api/bkuser_core/profiles/views.py index b68eb7064..542f75c16 100644 --- a/src/api/bkuser_core/profiles/views.py +++ b/src/api/bkuser_core/profiles/views.py @@ -679,6 +679,20 @@ def _generate_reset_passwd_url_with_token(profile: Profile) -> dict: return data + @staticmethod + def _generate_reset_passwd_url_with_token(profile: Profile) -> dict: + data = {} + try: + token_holder = ProfileTokenHolder.objects.create( + profile=profile, token_expire_seconds=settings.PAGE_TOKEN_EXPIRE_SECONDS + ) + except Exception: # pylint: disable=broad-except + logger.exception("failed to create token for password reset") + else: + data.update({"reset_password_url": make_passwd_reset_url_by_token(token_holder.token)}) + + return data + @method_decorator(clear_cache_if_succeed) @swagger_auto_schema(request_body=local_serializers.LoginUpsertSerializer) def upsert(self, request): From fe424fc214267407b6b6dbdf7b0c4c46c32025d1 Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Mon, 15 Nov 2021 10:18:23 +0800 Subject: [PATCH 05/19] =?UTF-8?q?fix:=20=E7=A4=BE=E5=8C=BA=E7=89=88?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=A1=B5=E9=9D=A2=E4=BC=98=E5=8C=96=20#144?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bklogin/templates/account/login_ce.html | 1 + src/login/static/css_ce/login.min.css | 6 +++--- src/login/static/img/logo_ce/logo.png | Bin 0 -> 18063 bytes src/login/static/img/logo_ce/v7.png | Bin 0 -> 1006 bytes 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 src/login/static/img/logo_ce/logo.png create mode 100644 src/login/static/img/logo_ce/v7.png diff --git a/src/login/bklogin/templates/account/login_ce.html b/src/login/bklogin/templates/account/login_ce.html index 10f916015..32e8fc283 100755 --- a/src/login/bklogin/templates/account/login_ce.html +++ b/src/login/bklogin/templates/account/login_ce.html @@ -46,6 +46,7 @@
+

密码

diff --git a/src/login/static/css_ce/login.min.css b/src/login/static/css_ce/login.min.css index 3fff70724..9160f71ad 100755 --- a/src/login/static/css_ce/login.min.css +++ b/src/login/static/css_ce/login.min.css @@ -1,3 +1,3 @@ -*{box-sizing:border-box}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,i,pre,form,fieldset,input,blockquote,th,td,p,span,button,textarea,b{margin:0;padding:0}html,body{font-size:14px;font-family:"Microsoft YaHei";height:100%;position:relative}a{text-decoration:none;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;transition:all .5s}button{text-decoration:none;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;transition:all .5s}a:hover{text-decoration:none}ul,ol,li{list-style:none}h1,h2,h3,h4,h5,h6{font-weight:normal}input::-webkit-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input:-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input::-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input:-ms-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea::-webkit-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea:-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea::-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea:-ms-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}.pb110{padding-bottom:110px}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.clearfix{*zoom:1}.hide{display:none!important;visibility:hidden}input[type="number"]{-moz-appearance:textfield}input,select{background:0}input[disabled]{background:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.page-content{height:100%;width:100%;position:relative;margin:0 auto;background-color:#ebf2fa}#particles-js{height:100%}.page-content .right-top{width:52%;height:0;position:absolute;right:0;top:0;padding-bottom:10%;background-color:#ebf2fa}.page-content .right-top img{width:100%}.page-content .right-bottom{width:31%;height:0;position:absolute;right:1%;bottom:0;padding-bottom:15%}.page-content .right-bottom img{width:100%}.page-content .left-bottom{width:35%;height:0;position:absolute;left:0;bottom:0;padding-bottom:12%}.page-content .left-bottom img{width:100%}.page-content .login-from{width:450px;position:absolute;top:17%;left:50%;margin-left:-200px;z-index:100;overflow:visible;background:#fff;border-radius:8px;box-shadow:0 2px 6px 0 rgba(0,0,0,0.10)}.page-content .login-from .logo-title{opacity:1;padding:55px 0 50px 68px;display:flex;align-items:center;justify-content:flex-start}.page-content .login-from .logo-title .logo-img{height:34px}.page-content .login-from .from-detail{position:relative;padding-bottom:40px}.page-content .login-from .from-detail .is-danger-tip{position:absolute;color:#ea3636;top:11px;left:38px;font-size:12px}.page-content .login-from .from-detail .is-danger-tip .icon-exclamation-circle-shape{margin-right:10px}.page-content .login-from .form-login{width:100%;padding:0 38px}.page-content .login-from .form-login .change-password{margin-top:2px}.page-content .login-from .form-login .change-password span{color:#ea3636}.page-content .login-from .form-login .change-password a{color:#1768ef}.page-content .login-from .form-login.is-danger .group-control p{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input{border-color:#ff5656;color:#63656e}.page-content .login-from .form-login.is-danger .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control i{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input{border-color:#dde4eb;color:#63656e}.page-content .login-from .form-login.certificate-expired .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.certificate-expired .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn{background:#313b4c;cursor:not-allowed}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn:hover{background:#344157} -.page-content .login-from .form-login .group-control{width:100%;height:40px;border-radius:2px;position:relative}.page-content .login-from .form-login .user{margin-bottom:32px}.page-content .login-from .form-login .group-control i{position:absolute;font-size:16px;top:12px;right:13px;color:#979ba5}.page-content .login-from .form-login .group-control i:hover{cursor:pointer}.page-content .login-from .form-login .group-control input{width:100%;height:100%;outline:0;border:1px solid #c4c6cc;padding:0 40px 0 12px;color:#63656e;border-radius:2px}.page-content .login-from .form-login .action{margin-top:12px}.page-content .login-from .form-login .group-control input:focus{border-color:#3c96ff}.page-content .login-from .form-login .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login .btn-content{font-size:0;padding-top:32px}.page-content .login-from .form-login .btn-content .login-btn{width:100%;height:42px;display:inline-block;background-color:#3a84ff;border-radius:2px;outline:0;border:0;font-size:14px;line-height:18px;letter-spacing:0;color:#fff;cursor:pointer;float:left}.page-content .login-from .form-login .btn-content .login-btn:hover{background:#3a84ff}.page-content .login-from .form-login .protocol-btn,.page-content .login-from .form-login .password-btn{font-size:14px;letter-spacing:0;color:#63656e;display:inline-block!important;cursor:pointer;float:right}.page-content .login-from .form-login .protocol-btn:hover,.page-content .login-from .form-login .password-btn:hover{color:#1768ef}.language-switcher{display:flex;border-radius:2px;height:24px;line-height:24px;justify-content:end;text-align:right;margin-top:47px}.language-switcher .language-item{width:70px;text-align:center;background:#f5f7fa;transform:skew(-15deg,0deg);display:inline-block;height:24px;cursor:pointer}.language-switcher .language-item:nth-child(1){border-radius:2px 0 0 2px}.language-switcher .language-item:nth-child(2){border-radius:0 2px 2px 0}.language-switcher .language-item .text-active{display:block;width:70px;height:24px;line-height:24px;font-size:12px;transform:skew(15deg,0deg)}.language-switcher .active{background:#e1ecff}.language-switcher .active .text-active{color:#3a84ff}.footer{width:100%;line-height:20px;padding:2% 0;position:absolute;bottom:0;color:#bfcbd7;font-size:12px;text-align:center;background:url(../img/logo_ce/footer.png) no-repeat center;background-size:100% 100%}.footer .logo-qt{margin-bottom:10px}.footer .logo-qt .img-logo{position:relative;top:4px;margin-right:8px}.footer a{color:#bfcbd7;margin:0 5px}.footer a:hover{color:#fff}.footer .follow-us{position:relative}.footer .follow-us:hover .qr-box{display:inline-block;padding-bottom:20px}.footer .qr-box{display:none;left:-25px;top:-110px}.footer .qr{padding:4px;border:1px solid #e4e4e4;background:#fff;display:inline-block}.footer .qr-caret{width:0;height:0;border:7px solid transparent;border-top:7px solid #fff;top:101px;left:42px;position:absolute}.footer .follow-us:hover .qr-box{display:inline-block}.footer a:hover{color:#fff}.footer .qr-box{position:absolute;top:0;margin-top:-118px}.protocol-pop{position:fixed;top:0;left:0;width:100%;height:100%;display:none;z-index:101}.protocol-pop .protocol-detail{width:1200px;height:700px;background-color:#fff;border-radius:2px;top:10%;left:50%;margin-left:-600px;position:absolute;padding:59px 23px 40px 37px}.protocol-pop .protocol-detail .del-text{position:absolute;top:0;right:0;width:27px;height:27px;line-height:26px;border-radius:50%;text-align:center;margin:4px 4px 0 0;background-repeat:no-repeat;background-size:11px 11px;background-position:50% 50%;cursor:pointer;display:inline-block}.protocol-pop .protocol-detail .del-text:hover{background-color:#f3f3f3}.protocol-pop .protocol-detail .del-text>i{font-size:10px;color:#50525f;font-weight:bold}.protocol-pop .protocol-detail .detail-content{height:536px;overflow-y:auto}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar{width:6px;height:5px}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar-thumb{border-radius:20px;background:#a5a5a5;box-shadow:inset 0 0 6px rgba(204,204,204,0.3)}.protocol-pop .protocol-detail .detail-content>.title{text-align:center;font-size:32px;font-weight:normal;font-stretch:normal;line-height:36px;letter-spacing:1px;color:#4f515e;position:relative;margin-bottom:67px}.protocol-pop .protocol-detail .detail-content>.title:after{content:"";position:absolute;width:30px;height:2px;background:#5c7ac6;top:46px;left:50%;margin-left:-15px}.protocol-pop .protocol-detail .detail-content .detail-list{padding-right:23px}.protocol-pop .protocol-detail .detail-content .detail-list>.title{font-weight:bold} -.protocol-pop .protocol-detail .detail-content .detail-list P{text-align:left;font-size:12px;line-height:32px;letter-spacing:0;color:#7b7d8a}.protocol-pop .protocol-detail .consent-content{text-align:center;margin-top:25px}.protocol-pop .protocol-detail .consent-content .consent-btn{width:160px;height:42px;display:inline-block;background-color:#5c7ac6;border-radius:2px;border:0;font-size:16px;font-weight:normal;font-stretch:normal;line-height:18px;letter-spacing:0;color:#fff}.protocol-pop .protocol-detail .consent-content .consent-btn:hover{background:#526eb5}.error-message-content{position:fixed;top:0;width:100%;height:40px;line-height:40px;text-align:center;display:none}.error-message-content i{cursor:pointer}.error-message-content.is-chrome{background:#f8f6db}.error-message-content.is-certificate{background:#fbd9d9;color:#ff5656}.error-message-content span{color:#ff5656;display:inline-block;margin-right:20px}.error-message-content i{color:#ff5656;display:inline-block} \ No newline at end of file +*{box-sizing:border-box}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,i,pre,form,fieldset,input,blockquote,th,td,p,span,button,textarea,b{margin:0;padding:0}html,body{font-size:14px;font-family:"Microsoft YaHei";height:100%;position:relative}a{text-decoration:none;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;transition:all .5s}button{text-decoration:none;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;transition:all .5s}a:hover{text-decoration:none}ul,ol,li{list-style:none}h1,h2,h3,h4,h5,h6{font-weight:normal}input::-webkit-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input:-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input::-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}input:-ms-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea::-webkit-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea:-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea::-moz-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}textarea:-ms-input-placeholder{font-family:"Microsoft YaHei";color:#c4c6cc}.pb110{padding-bottom:110px}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.clearfix{*zoom:1}.hide{display:none!important;visibility:hidden}input[type="number"]{-moz-appearance:textfield}input,select{background:0}input[disabled]{background:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.page-content{height:100%;width:100%;position:relative;margin:0 auto;background-color:#ebf2fa}#particles-js{height:100%}.page-content .right-top{width:52%;height:0;position:absolute;right:0;top:0;padding-bottom:10%;background-color:#ebf2fa}.page-content .right-top img{width:100%}.page-content .right-bottom{width:31%;height:0;position:absolute;right:1%;bottom:0;padding-bottom:15%}.page-content .right-bottom img{width:100%}.page-content .left-bottom{width:35%;height:0;position:absolute;left:0;bottom:0;padding-bottom:12%}.page-content .left-bottom img{width:100%}.page-content .login-from{width:450px;position:absolute;top:17%;left:50%;margin-left:-200px;z-index:100;overflow:visible;background:#fff;border-radius:8px;box-shadow:0 2px 6px 0 rgba(0,0,0,0.10)}.page-content .login-from .logo-title{opacity:1;padding:55px 0 50px 68px;display:flex;align-items:center;justify-content:flex-start}.page-content .login-from .logo-title .logo-img{height:34px}.page-content .login-from .from-detail{position:relative;padding-bottom:40px}.page-content .login-from .from-detail .is-danger-tip{position:absolute;color:#ea3636;top:11px;left:38px;font-size:12px}.page-content .login-from .from-detail .is-danger-tip .icon-exclamation-circle-shape{margin-right:10px}.page-content .login-from .form-login{width:100%;padding:0 38px}.page-content .login-from .form-login .change-password{margin-top:2px}.page-content .login-from .form-login .change-password span{color:#ea3636}.page-content .login-from .form-login .change-password a{color:#1768ef}.page-content .login-from .form-login.is-danger .group-control p{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input{border-color:#ff5656;color:#63656e}.page-content .login-from .form-login.is-danger .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.is-danger .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control i{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input{border-color:#dde4eb;color:#63656e}.page-content .login-from .form-login.certificate-expired .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.certificate-expired .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn{background:#313b4c;cursor:not-allowed}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn:hover{background:#344157} +.page-content .login-from .form-login .group-control{width:100%;height:40px;border-radius:2px;position:relative}.page-content .login-from .form-login .user{margin-bottom:32px}.page-content .login-from .form-login .group-control i{position:absolute;font-size:16px;top:12px;right:13px;color:#979ba5}.page-content .login-from .form-login .group-control i:hover{cursor:pointer}.page-content .login-from .form-login .group-control input{width:100%;height:100%;outline:0;border:1px solid #c4c6cc;padding:0 40px 0 12px;color:#63656e;border-radius:2px}.page-content .login-from .form-login .action{margin-top:12px}.page-content .login-from .form-login .group-control input:focus{border-color:#3c96ff}.page-content .login-from .form-login .group-control input::-webkit-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input:-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input::-moz-placeholder{color:#c4c6cc}.page-content .login-from .form-login .group-control input:-ms-input-placeholder{color:#c4c6cc}.page-content .login-from .form-login .btn-content{font-size:0;padding-top:32px}.page-content .login-from .form-login .btn-content .login-btn{width:100%;height:42px;display:inline-block;background-color:#3a84ff;border-radius:2px;outline:0;border:0;font-size:14px;line-height:18px;letter-spacing:0;color:#fff;cursor:pointer;float:left}.page-content .login-from .form-login .btn-content .login-btn:hover{background:#3a84ff}.page-content .login-from .form-login .protocol-btn,.page-content .login-from .form-login .password-btn{font-size:14px;letter-spacing:0;color:#63656e;display:inline-block!important;cursor:pointer;float:right}.page-content .login-from .form-login .protocol-btn:hover,.page-content .login-from .form-login .password-btn:hover{color:#1768ef}.language-switcher{display:flex;border-radius:2px;height:24px;line-height:24px;justify-content:end;text-align:right;margin-top:47px}.language-switcher .language-item{width:70px;text-align:center;background:#f5f7fa;transform:skew(-15deg,0deg);display:inline-block;height:24px;cursor:pointer}.language-switcher .language-item:nth-child(1){border-radius:2px 0 0 2px}.language-switcher .language-item:nth-child(2){border-radius:0 2px 2px 0}.language-switcher .language-item .text-active{display:block;width:70px;height:24px;line-height:24px;font-size:12px;transform:skew(15deg,0deg)}.language-switcher .active{background:#e1ecff}.language-switcher .active .text-active{color:#3a84ff}.footer{width:100%;line-height:20px;padding:2% 0;position:absolute;bottom:0;color:#bfcbd7;font-size:12px;text-align:center;background:url(../img/logo_ce/footer.png) no-repeat center;background-size:100% 100%}.footer .logo-qt{margin-bottom:10px}.footer .logo-qt .img-logo{position:relative;top:4px;margin-right:8px}.footer a{color:#bfcbd7;margin:0 5px}.footer a:hover{color:#fff}.footer .follow-us{position:relative}.footer .follow-us:hover .qr-box{display:inline-block;padding-bottom:20px}.footer .qr-box{display:none;left:-25px;top:-110px}.footer .qr{padding:4px;border:1px solid #e4e4e4;background:#fff;display:inline-block}.footer .qr-caret{width:0;height:0;border:7px solid transparent;border-top:7px solid #fff;top:101px;left:42px;position:absolute}.footer .follow-us:hover .qr-box{display:inline-block}.footer a:hover{color:#fff}.footer .qr-box{position:absolute;top:0;margin-top:-118px}.protocol-pop{position:fixed;top:0;left:0;width:100%;height:100%;display:none;z-index:101}.protocol-pop .protocol-detail{width:1200px;height:700px;background-color:#fff;border-radius:2px;top:10%;left:50%;margin-left:-600px;position:absolute;padding:59px 23px 40px 37px}.protocol-pop .protocol-detail .del-text{position:absolute;top:0;right:0;width:27px;height:27px;line-height:26px;border-radius:50%;text-align:center;margin:4px 4px 0 0;background-repeat:no-repeat;background-size:11px 11px;background-position:50% 50%;cursor:pointer;display:inline-block}.protocol-pop .protocol-detail .del-text:hover{background-color:#f3f3f3}.protocol-pop .protocol-detail .del-text>i{font-size:10px;color:#50525f;font-weight:bold}.protocol-pop .protocol-detail .detail-content{height:536px;overflow-y:auto}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar{width:6px;height:5px}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar-thumb{border-radius:20px;background:#a5a5a5;box-shadow:inset 0 0 6px rgba(204,204,204,0.3)}.protocol-pop .protocol-detail .detail-content>.title{text-align:center;font-size:32px;font-weight:normal;font-stretch:normal;line-height:36px;letter-spacing:1px;color:#4f515e;position:relative;margin-bottom:67px}.protocol-pop .protocol-detail .detail-content>.title:after{content:"";position:absolute;width:30px;height:2px;background:#5c7ac6;top:46px;left:50%;margin-left:-15px}.protocol-pop .protocol-detail .detail-content .detail-list{padding-right:23px}.protocol-pop .protocol-detail .detail-content .detail-list>.title{font-weight:bold} +.protocol-pop .protocol-detail .detail-content .detail-list P{text-align:left;font-size:12px;line-height:32px;letter-spacing:0;color:#7b7d8a}.protocol-pop .protocol-detail .consent-content{text-align:center;margin-top:25px}.protocol-pop .protocol-detail .consent-content .consent-btn{width:160px;height:42px;display:inline-block;background-color:#5c7ac6;border-radius:2px;border:0;font-size:16px;font-weight:normal;font-stretch:normal;line-height:18px;letter-spacing:0;color:#fff}.protocol-pop .protocol-detail .consent-content .consent-btn:hover{background:#526eb5}.error-message-content{position:fixed;top:0;width:100%;height:40px;line-height:40px;text-align:center;display:none}.error-message-content i{cursor:pointer}.error-message-content.is-chrome{background:#f8f6db}.error-message-content.is-certificate{background:#fbd9d9;color:#ff5656}.error-message-content span{color:#ff5656;display:inline-block;margin-right:20px}.error-message-content i{color:#ff5656;display:inline-block} diff --git a/src/login/static/img/logo_ce/logo.png b/src/login/static/img/logo_ce/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5dde4f027b8233e91b4403580f623813a04e6b4e GIT binary patch literal 18063 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 literal 0 HcmV?d00001 From 3b1da03994e8ac6a2a15f1acaa592c8cfa129c00 Mon Sep 17 00:00:00 2001 From: v_yutyi Date: Fri, 19 Nov 2021 19:57:39 +0800 Subject: [PATCH 06/19] =?UTF-8?q?fix:=20=E7=A4=BE=E5=8C=BA=E7=89=88?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20#144?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bklogin/templates/account/login_ce.html | 1 - src/login/static/css_ce/login.css | 2 +- src/login/static/img/logo_ce/logo_ce.png | Bin 0 -> 5726 bytes 3 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 src/login/static/img/logo_ce/logo_ce.png diff --git a/src/login/bklogin/templates/account/login_ce.html b/src/login/bklogin/templates/account/login_ce.html index 32e8fc283..10f916015 100755 --- a/src/login/bklogin/templates/account/login_ce.html +++ b/src/login/bklogin/templates/account/login_ce.html @@ -46,7 +46,6 @@
-

密码

diff --git a/src/login/static/css_ce/login.css b/src/login/static/css_ce/login.css index 373fa95c3..fdd8b4642 100755 --- a/src/login/static/css_ce/login.css +++ b/src/login/static/css_ce/login.css @@ -359,7 +359,7 @@ input[type="number"]::-webkit-outer-spin-button { position: relative; } .page-content .login-from .form-login .user { - margin-bottom: 32px; + margin-bottom: 24px; } .page-content .login-from .form-login .group-control i { position: absolute; diff --git a/src/login/static/img/logo_ce/logo_ce.png b/src/login/static/img/logo_ce/logo_ce.png new file mode 100644 index 0000000000000000000000000000000000000000..5729e217769247448e3dff13c0d147efee5b1cc7 GIT binary patch literal 5726 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 QX8-^I07*qoM6N<$f}Exe$^ZZW literal 0 HcmV?d00001 From 78abe7c07af484149dc96f25d9f7bedf6a56e9a3 Mon Sep 17 00:00:00 2001 From: IMBlues Date: Tue, 19 Oct 2021 17:18:38 +0800 Subject: [PATCH 07/19] =?UTF-8?q?integration:=20=E6=95=B4=E5=90=88?= =?UTF-8?q?=E8=93=9D=E9=B2=B8=E7=99=BB=E5=BD=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/login/.pre-commit-config.yaml | 23 ++ src/login/api/__init__.py | 11 + src/login/api/constants.py | 42 +++ src/login/api/utils.py | 107 ++++++ src/login/api/views.py | 139 ++++++++ src/login/backends/__init__.py | 11 + src/login/backends/bk.py | 82 +++++ src/login/bin/post-compile | 5 + src/login/bin/pre-run | 6 + src/login/bk_i18n/__init__.py | 15 + src/login/bk_i18n/apps.py | 22 ++ src/login/bk_i18n/constants.py | 30 ++ src/login/bk_i18n/middlewares.py | 60 ++++ src/login/bk_i18n/migrations/__init__.py | 12 + src/login/bk_i18n/signal_receivers.py | 89 +++++ src/login/bkaccount/__init__.py | 11 + src/login/bkaccount/admin.py | 31 ++ src/login/bkaccount/manager.py | 37 ++ .../bkaccount/migrations/0001_initial.py | 57 +++ .../migrations/0002_initial_user_data.py | 31 ++ .../0003_bktoken_inactive_expire_time.py | 30 ++ .../migrations/0004_auto_20170621_0929.py | 27 ++ .../bkaccount/migrations/0005_initial_role.py | 33 ++ .../migrations/0006_initial_bkuser_role.py | 33 ++ .../bkaccount/migrations/0007_userinfo.py | 26 ++ .../migrations/0008_auto_20171116_2026.py | 26 ++ .../migrations/0009_add_role_data.py | 32 ++ .../migrations/0010_auto_20190704_1106.py | 34 ++ src/login/bkaccount/migrations/__init__.py | 12 + src/login/bkaccount/models.py | 61 ++++ src/login/bkauth/__init__.py | 11 + src/login/bkauth/actions.py | 120 +++++++ src/login/bkauth/constants.py | 14 + src/login/bkauth/decorators.py | 29 ++ src/login/bkauth/forms.py | 40 +++ src/login/bkauth/manager.py | 33 ++ src/login/bkauth/middlewares.py | 110 ++++++ src/login/bkauth/migrations/0001_initial.py | 26 ++ src/login/bkauth/migrations/__init__.py | 0 src/login/bkauth/models.py | 123 +++++++ src/login/bkauth/utils.py | 201 +++++++++++ src/login/bkauth/views.py | 181 ++++++++++ src/login/common/__init__.py | 11 + src/login/common/constants.py | 35 ++ src/login/common/context_processors.py | 41 +++ src/login/common/encryption.py | 90 +++++ src/login/common/exceptions.py | 37 ++ src/login/common/http.py | 76 ++++ src/login/common/license.py | 125 +++++++ src/login/common/log.py | 33 ++ src/login/common/mixins/__init__.py | 11 + src/login/common/mixins/exempt.py | 50 +++ src/login/common/usermgr.py | 137 ++++++++ src/login/common/utils/__init__.py | 11 + src/login/common/utils/basic.py | 27 ++ src/login/common/utils/time.py | 53 +++ src/login/components/__init__.py | 11 + src/login/components/http.py | 106 ++++++ src/login/components/usermgr_api.py | 119 +++++++ src/login/conf/__init__.py | 11 + src/login/conf/default.py | 328 ++++++++++++++++++ src/login/conf/settings_env.py | 71 ++++ src/login/ee_login/__init__.py | 11 + src/login/ee_login/settings_login.py | 26 ++ src/login/ee_login/settings_login_mock.py | 26 ++ .../ee_login/settings_login_oauth_google.py | 26 ++ src/login/ee_official_login/__init__.py | 11 + src/login/ee_official_login/mock/__init__.py | 11 + src/login/ee_official_login/mock/backends.py | 48 +++ src/login/ee_official_login/mock/views.py | 66 ++++ src/login/ee_official_login/oauth/__init__.py | 11 + .../oauth/google/__init__.py | 11 + .../oauth/google/backends.py | 68 ++++ .../oauth/google/settings.py | 26 ++ .../ee_official_login/oauth/google/utils.py | 105 ++++++ .../ee_official_login/oauth/google/views.py | 76 ++++ src/login/healthz/__init__.py | 11 + src/login/healthz/urls.py | 17 + src/login/healthz/views.py | 130 +++++++ src/login/metadata/__init__.py | 11 + src/login/metadata/urls.py | 17 + src/login/metadata/views.py | 22 ++ src/login/requirements.txt | 21 ++ src/login/requirements_dev.txt | 14 + src/login/settings.py | 38 ++ src/login/start.sh | 10 + src/login/static/css/login.css | 4 - src/login/static/css/login.min.css | 2 +- src/login/templates/401.html | 25 ++ src/login/templates/403.html | 24 ++ src/login/templates/404.html | 23 ++ src/login/templates/500.html | 24 ++ src/login/templates/50x.html | 23 ++ src/login/templates/account/agreement.part | 127 +++++++ src/login/templates/account/base.html | 175 ++++++++++ src/login/templates/account/login.html | 115 ++++++ src/login/templates/account/login_ce.html | 103 ++++++ .../templates/account/login_ce_i18n.html | 77 ++++ src/login/templates/account/no_right.html | 8 + src/login/templates/account/user_table.part | 116 +++++++ src/login/templates/account/users.html | 102 ++++++ src/login/templates/admin/base_site.html | 15 + src/login/templates/admin/login.html | 24 ++ src/login/templates/csrf_failure.html | 29 ++ src/login/templates/metadata/website.json | 49 +++ src/login/urls.py | 121 +++++++ 106 files changed, 5400 insertions(+), 5 deletions(-) create mode 100644 src/login/.pre-commit-config.yaml create mode 100755 src/login/api/__init__.py create mode 100755 src/login/api/constants.py create mode 100755 src/login/api/utils.py create mode 100755 src/login/api/views.py create mode 100755 src/login/backends/__init__.py create mode 100755 src/login/backends/bk.py create mode 100755 src/login/bin/post-compile create mode 100755 src/login/bin/pre-run create mode 100755 src/login/bk_i18n/__init__.py create mode 100755 src/login/bk_i18n/apps.py create mode 100755 src/login/bk_i18n/constants.py create mode 100755 src/login/bk_i18n/middlewares.py create mode 100755 src/login/bk_i18n/migrations/__init__.py create mode 100755 src/login/bk_i18n/signal_receivers.py create mode 100755 src/login/bkaccount/__init__.py create mode 100755 src/login/bkaccount/admin.py create mode 100755 src/login/bkaccount/manager.py create mode 100755 src/login/bkaccount/migrations/0001_initial.py create mode 100755 src/login/bkaccount/migrations/0002_initial_user_data.py create mode 100755 src/login/bkaccount/migrations/0003_bktoken_inactive_expire_time.py create mode 100755 src/login/bkaccount/migrations/0004_auto_20170621_0929.py create mode 100755 src/login/bkaccount/migrations/0005_initial_role.py create mode 100755 src/login/bkaccount/migrations/0006_initial_bkuser_role.py create mode 100755 src/login/bkaccount/migrations/0007_userinfo.py create mode 100755 src/login/bkaccount/migrations/0008_auto_20171116_2026.py create mode 100755 src/login/bkaccount/migrations/0009_add_role_data.py create mode 100755 src/login/bkaccount/migrations/0010_auto_20190704_1106.py create mode 100755 src/login/bkaccount/migrations/__init__.py create mode 100755 src/login/bkaccount/models.py create mode 100755 src/login/bkauth/__init__.py create mode 100755 src/login/bkauth/actions.py create mode 100755 src/login/bkauth/constants.py create mode 100755 src/login/bkauth/decorators.py create mode 100755 src/login/bkauth/forms.py create mode 100755 src/login/bkauth/manager.py create mode 100755 src/login/bkauth/middlewares.py create mode 100644 src/login/bkauth/migrations/0001_initial.py create mode 100644 src/login/bkauth/migrations/__init__.py create mode 100755 src/login/bkauth/models.py create mode 100755 src/login/bkauth/utils.py create mode 100755 src/login/bkauth/views.py create mode 100755 src/login/common/__init__.py create mode 100755 src/login/common/constants.py create mode 100755 src/login/common/context_processors.py create mode 100755 src/login/common/encryption.py create mode 100755 src/login/common/exceptions.py create mode 100755 src/login/common/http.py create mode 100755 src/login/common/license.py create mode 100755 src/login/common/log.py create mode 100755 src/login/common/mixins/__init__.py create mode 100755 src/login/common/mixins/exempt.py create mode 100755 src/login/common/usermgr.py create mode 100755 src/login/common/utils/__init__.py create mode 100755 src/login/common/utils/basic.py create mode 100755 src/login/common/utils/time.py create mode 100755 src/login/components/__init__.py create mode 100755 src/login/components/http.py create mode 100755 src/login/components/usermgr_api.py create mode 100755 src/login/conf/__init__.py create mode 100755 src/login/conf/default.py create mode 100755 src/login/conf/settings_env.py create mode 100755 src/login/ee_login/__init__.py create mode 100755 src/login/ee_login/settings_login.py create mode 100755 src/login/ee_login/settings_login_mock.py create mode 100755 src/login/ee_login/settings_login_oauth_google.py create mode 100755 src/login/ee_official_login/__init__.py create mode 100755 src/login/ee_official_login/mock/__init__.py create mode 100755 src/login/ee_official_login/mock/backends.py create mode 100755 src/login/ee_official_login/mock/views.py create mode 100755 src/login/ee_official_login/oauth/__init__.py create mode 100755 src/login/ee_official_login/oauth/google/__init__.py create mode 100755 src/login/ee_official_login/oauth/google/backends.py create mode 100755 src/login/ee_official_login/oauth/google/settings.py create mode 100755 src/login/ee_official_login/oauth/google/utils.py create mode 100755 src/login/ee_official_login/oauth/google/views.py create mode 100755 src/login/healthz/__init__.py create mode 100755 src/login/healthz/urls.py create mode 100755 src/login/healthz/views.py create mode 100755 src/login/metadata/__init__.py create mode 100755 src/login/metadata/urls.py create mode 100755 src/login/metadata/views.py create mode 100755 src/login/requirements.txt create mode 100644 src/login/requirements_dev.txt create mode 100755 src/login/settings.py create mode 100755 src/login/start.sh create mode 100755 src/login/templates/401.html create mode 100755 src/login/templates/403.html create mode 100755 src/login/templates/404.html create mode 100755 src/login/templates/500.html create mode 100755 src/login/templates/50x.html create mode 100755 src/login/templates/account/agreement.part create mode 100755 src/login/templates/account/base.html create mode 100755 src/login/templates/account/login.html create mode 100755 src/login/templates/account/login_ce.html create mode 100755 src/login/templates/account/login_ce_i18n.html create mode 100755 src/login/templates/account/no_right.html create mode 100755 src/login/templates/account/user_table.part create mode 100755 src/login/templates/account/users.html create mode 100755 src/login/templates/admin/base_site.html create mode 100755 src/login/templates/admin/login.html create mode 100755 src/login/templates/csrf_failure.html create mode 100755 src/login/templates/metadata/website.json create mode 100755 src/login/urls.py diff --git a/src/login/.pre-commit-config.yaml b/src/login/.pre-commit-config.yaml new file mode 100644 index 000000000..fc758323d --- /dev/null +++ b/src/login/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: + - repo: local + hooks: + - id: isort + name: isort + language: python + pass_filenames: false + entry: isort --settings-path=pyproject.toml . + - id: black + name: black + language: python + pass_filenames: false + entry: black --config=pyproject.toml . + - id: flake8 + name: flak8 + language: python + pass_filenames: false + entry: pflake8 --config=pyproject.toml + - id: mypy + name: mypy + language: python + pass_filenames: false + entry: mypy . --config-file=pyproject.toml diff --git a/src/login/api/__init__.py b/src/login/api/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/api/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/api/constants.py b/src/login/api/constants.py new file mode 100755 index 000000000..d50367634 --- /dev/null +++ b/src/login/api/constants.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from common.constants import enum + +ApiErrorCodeEnum = enum( + SUCCESS="00", + PARAM_NOT_VALID="1200", + USER_NOT_EXISTS="1201", + # 做兼容 + USER_NOT_EXISTS2="1300", + USER_INFO_UPDATE_FAIL="1202", +) + +ApiErrorCodeEnumV2 = enum( + SUCCESS=0, + PARAM_NOT_VALID=1302100, + USER_NOT_EXISTS=1302101, + USER_INFO_UPDATE_FAIL=1302102, + USER_NOT_EXISTS2=1302103, +) + +ApiErrorCodeEnumV3 = enum( + SUCCESS=0, + PARAM_NOT_VALID=1302100, + USER_NOT_EXISTS=1302101, + USER_INFO_UPDATE_FAIL=1302102, + USER_NOT_EXISTS2=1302103, + RESOUCE_OWNER_MISMATCH=1302200, +) diff --git a/src/login/api/utils.py b/src/login/api/utils.py new file mode 100755 index 000000000..ff6e7bb71 --- /dev/null +++ b/src/login/api/utils.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from django.conf import settings +from django.http import JsonResponse + +from api.constants import ApiErrorCodeEnum, ApiErrorCodeEnumV2, ApiErrorCodeEnumV3 + + +def is_request_from_esb(request): + """ + 请求是否来自ESB + """ + x_app_token = request.META.get("HTTP_X_APP_TOKEN") + x_app_code = request.META.get("HTTP_X_APP_CODE") + if x_app_code == "esb" and x_app_token == settings.ESB_TOKEN: + return True + return False + + +######## +# v1 # +######## + + +class APIV1BaseJsonResponse(JsonResponse): + def __init__(self, result, code, message, data=None): + data = data if data is not None else {} + json_data = {"result": result, "code": code, "message": message, "data": data} + super(APIV1BaseJsonResponse, self).__init__(json_data) + + +class APIV1FailJsonResponse(APIV1BaseJsonResponse): + def __init__(self, message, **kwargs): + code = kwargs.get("code") or ApiErrorCodeEnum.PARAM_NOT_VALID + data = kwargs.get("data") + super(APIV1FailJsonResponse, self).__init__(False, code, message, data=data) + + +class APIV1OKJsonResponse(APIV1BaseJsonResponse): + def __init__(self, message, **kwargs): + data = kwargs.get("data") + super(APIV1OKJsonResponse, self).__init__(True, ApiErrorCodeEnum.SUCCESS, message, data=data) + + +######## +# v2 # +######## + + +class APIV2BaseJsonResponse(JsonResponse): + def __init__(self, result, code, message, data=None): + data = data if data is not None else {} + json_data = {"result": result, "bk_error_code": code, "bk_error_msg": message, "data": data} + super(APIV2BaseJsonResponse, self).__init__(json_data) + + +class APIV2FailJsonResponse(APIV2BaseJsonResponse): + def __init__(self, message, **kwargs): + code = kwargs.get("code") or ApiErrorCodeEnumV2.PARAM_NOT_VALID + data = kwargs.get("data") + super(APIV2FailJsonResponse, self).__init__(False, code, message, data=data) + + +class APIV2OKJsonResponse(APIV2BaseJsonResponse): + def __init__(self, message, **kwargs): + data = kwargs.get("data") + super(APIV2OKJsonResponse, self).__init__(True, ApiErrorCodeEnumV2.SUCCESS, message, data=data) + + +######## +# v3 # +######## +# result/code/message/data +# code is int + + +class APIV3BaseJsonResponse(JsonResponse): + def __init__(self, result, code, message, data=None): + data = data if data is not None else {} + json_data = {"result": result, "code": code, "message": message, "data": data} + super(APIV3BaseJsonResponse, self).__init__(json_data) + + +class APIV3FailJsonResponse(APIV3BaseJsonResponse): + def __init__(self, message, **kwargs): + code = kwargs.get("code") or ApiErrorCodeEnumV3.PARAM_NOT_VALID + data = kwargs.get("data") + super(APIV3FailJsonResponse, self).__init__(False, code, message, data=data) + + +class APIV3OKJsonResponse(APIV3BaseJsonResponse): + def __init__(self, message, **kwargs): + data = kwargs.get("data") + super(APIV3OKJsonResponse, self).__init__(True, ApiErrorCodeEnumV3.SUCCESS, message, data=data) diff --git a/src/login/api/views.py b/src/login/api/views.py new file mode 100755 index 000000000..3d52e9e5b --- /dev/null +++ b/src/login/api/views.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from bkauth.utils import validate_bk_token +from common import usermgr +from common.mixins.exempt import LoginExemptMixin +from django.utils.translation import ugettext as _ +from django.views.generic import View + +from api.constants import ApiErrorCodeEnum, ApiErrorCodeEnumV2, ApiErrorCodeEnumV3 +from api.utils import ( + APIV1FailJsonResponse, + APIV1OKJsonResponse, + APIV2FailJsonResponse, + APIV2OKJsonResponse, + APIV3FailJsonResponse, + APIV3OKJsonResponse, + is_request_from_esb, +) + +######## +# v1 # +######## + + +class CheckLoginView(LoginExemptMixin, View): + def get(self, request): + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + return APIV1FailJsonResponse(message, code=ApiErrorCodeEnum.PARAM_NOT_VALID) + return APIV1OKJsonResponse(_("用户验证成功"), data={"username": username}) + + +class UserView(LoginExemptMixin, View): + def get(self, request): + """ + 获取用户信息API + """ + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + # 如果是ESB的请求,可以直接从参数中获取用户id + is_from_esb = is_request_from_esb(request) + username = request.GET.get("username") + if not is_from_esb or not username: + return APIV1FailJsonResponse(message, code=ApiErrorCodeEnum.PARAM_NOT_VALID) + + # 获取用户数据 + ok, message, data = usermgr.get_user(username) + if not ok: + return APIV1FailJsonResponse(message, code=ApiErrorCodeEnum.USER_NOT_EXISTS2) + + return APIV1OKJsonResponse(_("用户信息获取成功"), data=data) + + +######## +# v2 # +######## + + +class CheckLoginViewV2(LoginExemptMixin, View): + def get(self, request): + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + return APIV2FailJsonResponse(message, code=ApiErrorCodeEnumV2.PARAM_NOT_VALID) + return APIV2OKJsonResponse(_("用户验证成功"), data={"bk_username": username}) + + +class UserViewV2(LoginExemptMixin, View): + def get(self, request): + """ + 获取用户信息API + """ + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + # 如果是ESB的请求,可以直接从参数中获取用户id + is_from_esb = is_request_from_esb(request) + username = request.GET.get("bk_username") + if not is_from_esb or not username: + return APIV2FailJsonResponse(message, code=ApiErrorCodeEnumV2.PARAM_NOT_VALID) + + # 获取用户数据 + ok, message, data = usermgr.get_user(username, "v2") + if not ok: + return APIV2FailJsonResponse(message, code=ApiErrorCodeEnumV2.USER_NOT_EXISTS2) + + return APIV2OKJsonResponse(_("用户信息获取成功"), data=data) + + +######## +# v3 # +######## + + +class CheckLoginViewV3(LoginExemptMixin, View): + def get(self, request): + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + return APIV3FailJsonResponse(message, code=ApiErrorCodeEnumV3.PARAM_NOT_VALID) + return APIV3OKJsonResponse(_("用户验证成功"), data={"bk_username": username}) + + +class UserViewV3(LoginExemptMixin, View): + def get(self, request): + """ + 获取用户信息API + v3, 直接返回usermgr返回的内容不做字段转换 + """ + # 验证Token参数 + is_valid, username, message = validate_bk_token(request.GET) + if not is_valid: + # 如果是ESB的请求,可以直接从参数中获取用户id + is_from_esb = is_request_from_esb(request) + username = request.GET.get("bk_username") + if not is_from_esb or not username: + return APIV3FailJsonResponse(message, code=ApiErrorCodeEnumV3.PARAM_NOT_VALID) + + # 获取用户数据 + ok, message, data = usermgr.get_user(username, "v3") + if not ok: + return APIV3FailJsonResponse(message, code=ApiErrorCodeEnumV3.USER_NOT_EXISTS2) + + return APIV3OKJsonResponse(_("用户信息获取成功"), data=data) diff --git a/src/login/backends/__init__.py b/src/login/backends/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/backends/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/backends/bk.py b/src/login/backends/bk.py new file mode 100755 index 000000000..d87ab7882 --- /dev/null +++ b/src/login/backends/bk.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from common.exceptions import AuthenticationError +from common.usermgr import get_categories_str +from components import usermgr_api +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from django.core.exceptions import ObjectDoesNotExist + +# from bk_i18n.constants import DJANGO_LANG_TO_BK_LANG + + +def _split_username(username): + """ + admin => ("admin", "") + admin@123.com => ("admin", "123.com") + admin@123.com@456.com => ("admin@123.com", "145.com") + """ + if "@" not in username: + return username, "" + parts = username.split("@") + length = len(parts) + if length == 2: + return parts[0], parts[1] + return "@".join(parts[: length - 1]), parts[length - 1] + + +class BkUserBackend(ModelBackend): + """ + 蓝鲸用户管理提供的认证 + """ + + def authenticate(self, username=None, password=None, language="", **kwargs): + # NOTE: username here maybe: username/phone/email + if not username or not password: + return None + + domain_list = get_categories_str().split(";") + + s_username, s_domain = _split_username(username) + if s_domain in domain_list: + username, domain = s_username, s_domain + else: + domain = "" + + # 调用用户管理接口进行验证 + ok, code, message, userinfo = usermgr_api.authenticate(username, password, language=language, domain=domain) + + # 认证不通过 + if not ok: + # 用户第一次登录,且需要修改初始密码 + redirect_to = userinfo.get("url") if code == 3210017 else None + raise AuthenticationError(message=message, redirect_to=redirect_to) + + # here we got the userinfo, but the language is not update yet(async in signal) + # so we need to use the current language + # if DJANGO_LANG_TO_BK_LANG.get(language): + # userinfo["language"] = DJANGO_LANG_TO_BK_LANG.get(language) + + # set the username to real username + username = userinfo.get("username", username) + UserModel = get_user_model() + try: + user = UserModel.objects.get(username=username) + except ObjectDoesNotExist: + user = UserModel.objects.create_user(username=username) + + user.fill_with_userinfo(userinfo) + return user diff --git a/src/login/bin/post-compile b/src/login/bin/post-compile new file mode 100755 index 000000000..853972e66 --- /dev/null +++ b/src/login/bin/post-compile @@ -0,0 +1,5 @@ +#!/bin/bash + +if [ "${BUILD_ENGINE}" == "landun" ]; then + find bin/ -type f ! -name pre-run -delete || true +fi \ No newline at end of file diff --git a/src/login/bin/pre-run b/src/login/bin/pre-run new file mode 100755 index 000000000..fb439689b --- /dev/null +++ b/src/login/bin/pre-run @@ -0,0 +1,6 @@ +#!/bin/bash + +command -v python +env +export BK_ENV=env +python manage.py migrate diff --git a/src/login/bk_i18n/__init__.py b/src/login/bk_i18n/__init__.py new file mode 100755 index 000000000..9002de3a3 --- /dev/null +++ b/src/login/bk_i18n/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +default_app_config = "bk_i18n.apps.BkI18nAppConfig" diff --git a/src/login/bk_i18n/apps.py b/src/login/bk_i18n/apps.py new file mode 100755 index 000000000..5c40dca0d --- /dev/null +++ b/src/login/bk_i18n/apps.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class BkI18nAppConfig(AppConfig): + name = "bk_i18n" + + def ready(self): + import bk_i18n.signal_receivers # noqa diff --git a/src/login/bk_i18n/constants.py b/src/login/bk_i18n/constants.py new file mode 100755 index 000000000..5ada26ee3 --- /dev/null +++ b/src/login/bk_i18n/constants.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from common.constants import enum + +LanguageEnum = enum(ZH_CN="zh-cn", EN="en") + +DJANGO_LANG_TO_BK_LANG = {"zh-hans": LanguageEnum.ZH_CN, "en": LanguageEnum.EN} + +BK_LANG_TO_DJANGO_LANG = {v: k for k, v in DJANGO_LANG_TO_BK_LANG.items()} + +# note: Add synchronization when add login api +LOGIN_API_URL_SUFFIX_LIST = [ + "is_login", + "get_user", + "get_all_user", + "get_batch_user", +] diff --git a/src/login/bk_i18n/middlewares.py b/src/login/bk_i18n/middlewares.py new file mode 100755 index 000000000..081688bc6 --- /dev/null +++ b/src/login/bk_i18n/middlewares.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from builtins import object + +import pytz +from bk_i18n.constants import LOGIN_API_URL_SUFFIX_LIST +from django.conf import settings +from django.utils import timezone, translation +from django.utils.translation import trans_real as trans + + +class TimezoneMiddleware(object): + def process_request(self, request): + tzname = request.session.get(settings.TIMEZONE_SESSION_KEY) + if tzname: + timezone.activate(pytz.timezone(tzname)) + else: + timezone.deactivate() + + +class LanguageMiddleware(object): + def process_request(self, request): + language = request.session.get(translation.LANGUAGE_SESSION_KEY) + if language: + translation.activate(language) + request.LANGUAGE_CODE = translation.get_language() + + +class ApiLanguageMiddleware(object): + def process_request(self, request): + # check api url + full_path = request.get_full_path() + is_api_url = False + for i in LOGIN_API_URL_SUFFIX_LIST: + if full_path.startswith("/accounts/" + i + "/") or full_path.startswith("/login/accounts/" + i + "/"): + is_api_url = True + break + # only api url do + if is_api_url: + try: + language = request.META.get("HTTP_BLUEKING_LANGUAGE", "en") + language = trans.get_supported_language_variant(language) + except Exception: + language = "en" + if language: + translation.activate(language) + request.LANGUAGE_CODE = translation.get_language() diff --git a/src/login/bk_i18n/migrations/__init__.py b/src/login/bk_i18n/migrations/__init__.py new file mode 100755 index 000000000..27e830ad6 --- /dev/null +++ b/src/login/bk_i18n/migrations/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + diff --git a/src/login/bk_i18n/signal_receivers.py b/src/login/bk_i18n/signal_receivers.py new file mode 100755 index 000000000..364c38281 --- /dev/null +++ b/src/login/bk_i18n/signal_receivers.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from bk_i18n.constants import BK_LANG_TO_DJANGO_LANG, DJANGO_LANG_TO_BK_LANG +from components.usermgr_api import upsert_user +from django.conf import settings +from django.contrib.auth.signals import user_logged_in +from django.dispatch import receiver +from django.utils import translation +from django.utils.translation.trans_real import ( + check_for_language, + get_languages, + get_supported_language_variant, + language_code_re, + parse_accept_lang_header, +) + + +def _get_language_from_request(request, user): + """从请求中获取需要同步到用户个人信息的语言""" + supported_lang_codes = get_languages() + # session 有language,说明在登录页面有进行修改或设置,则需要同步到用户个人信息中 + lang_code = request.session.get(translation.LANGUAGE_SESSION_KEY) + if lang_code in supported_lang_codes and lang_code is not None and check_for_language(lang_code): + return lang_code + + # 个人信息中已有language + if user.language: + return None + + # session 情况不满足同步到用户个人信息,且目前个人信息中无language设置 + # 查询header头 + accept = request.META.get("HTTP_ACCEPT_LANGUAGE", "") + for accept_lang, unused in parse_accept_lang_header(accept): + if accept_lang == "*": + break + + if not language_code_re.search(accept_lang): + continue + + try: + return get_supported_language_variant(accept_lang) + except LookupError: + continue + + # 使用settings默认设置 + try: + return get_supported_language_variant(settings.LANGUAGE_CODE) + except LookupError: + return settings.LANGUAGE_CODE + + +@receiver(user_logged_in, dispatch_uid="update_user_i18n_info") +def update_user_i18n_info(sender, request, user, *args, **kwargs): + """登录后自动刷新用户语言等国际化所需信息""" + time_zone = user.time_zone + if not time_zone: + # 默认使用settings中配置 + time_zone = settings.TIME_ZONE + # sync time_zone to usermgr + upsert_user(username=user.username, time_zone=time_zone) + + # 设置language + lang_code = _get_language_from_request(request, user) + bk_lang_code = user.language + if lang_code: + # 蓝鲸约定的语言代号与Django的有不同,需要进行转换 + bk_lang_code = DJANGO_LANG_TO_BK_LANG[lang_code] + # sync language to usermgr + upsert_user(username=user.username, language=bk_lang_code) + request.user.language = bk_lang_code + + lang_code = BK_LANG_TO_DJANGO_LANG[bk_lang_code] + # set session for render html when logged in not redirect + request.session[translation.LANGUAGE_SESSION_KEY] = lang_code + translation.activate(lang_code) + request.LANGUAGE_CODE = translation.get_language() + request.session[settings.TIMEZONE_SESSION_KEY] = time_zone diff --git a/src/login/bkaccount/__init__.py b/src/login/bkaccount/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/bkaccount/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/bkaccount/admin.py b/src/login/bkaccount/admin.py new file mode 100755 index 000000000..384e4805c --- /dev/null +++ b/src/login/bkaccount/admin.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from bkaccount.models import Loignlog +from django.contrib import admin + + +class LoignlogAdmin(admin.ModelAdmin): + """ + The forms to add and change login log instances. + + The fields to be used in displaying the Loginlog model. + """ + + list_display = ["username", "login_time", "login_browser", "login_ip", "login_host", "app_id"] + search_fields = ["username"] + list_filter = ["app_id"] + + +admin.site.register(Loignlog, LoignlogAdmin) diff --git a/src/login/bkaccount/manager.py b/src/login/bkaccount/manager.py new file mode 100755 index 000000000..6c5cb2565 --- /dev/null +++ b/src/login/bkaccount/manager.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext as _ + + +class LoginLogManager(models.Manager): + """ + User login log manager + """ + + def record_login(self, _username, _login_browser, _login_ip, host, app_id): + try: + self.model( + username=_username, + login_browser=_login_browser, + login_ip=_login_ip, + login_host=host, + login_time=timezone.now(), + app_id=app_id, + ).save() + return (True, _("记录成功")) + except Exception: + return (False, _("用户登录记录失败")) diff --git a/src/login/bkaccount/migrations/0001_initial.py b/src/login/bkaccount/migrations/0001_initial.py new file mode 100755 index 000000000..25a0ebc2e --- /dev/null +++ b/src/login/bkaccount/migrations/0001_initial.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import models, migrations +import django.utils.timezone +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0006_require_contenttypes_0002'), + ] + + operations = [ + migrations.CreateModel( + name='BkToken', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('token', models.CharField(unique=True, max_length=255, verbose_name='\u767b\u5f55\u7968\u636e', db_index=True)), + ('is_logout', models.BooleanField(default=False, verbose_name='\u7968\u636e\u662f\u5426\u5df2\u7ecf\u6267\u884c\u8fc7\u9000\u51fa\u767b\u5f55\u64cd\u4f5c')), + ], + options={ + 'db_table': 'login_bktoken', + 'verbose_name': '\u767b\u5f55\u7968\u636e', + 'verbose_name_plural': '\u767b\u5f55\u7968\u636e', + }, + ), + migrations.CreateModel( + name='Loignlog', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('login_time', models.DateTimeField(verbose_name='\u767b\u5f55\u65f6\u95f4')), + ('login_browser', models.CharField(max_length=200, null=True, verbose_name='\u767b\u5f55\u6d4f\u89c8\u5668', blank=True)), + ('login_ip', models.CharField(max_length=50, null=True, verbose_name='\u7528\u6237\u767b\u5f55ip', blank=True)), + ('login_host', models.CharField(max_length=100, null=True, verbose_name='\u767b\u5f55HOST', blank=True)), + ('app_id', models.CharField(max_length=30, null=True, verbose_name=b'APP_ID', blank=True)), + ('user', models.ForeignKey(verbose_name='\u7528\u6237', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'login_bklog', + 'verbose_name': '\u7528\u6237\u767b\u5f55\u65e5\u5fd7', + 'verbose_name_plural': '\u7528\u6237\u767b\u5f55\u65e5\u5fd7', + }, + ), + ] diff --git a/src/login/bkaccount/migrations/0002_initial_user_data.py b/src/login/bkaccount/migrations/0002_initial_user_data.py new file mode 100755 index 000000000..e53413bee --- /dev/null +++ b/src/login/bkaccount/migrations/0002_initial_user_data.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals +from django.db import migrations +from django.conf import settings +from django.contrib.auth import get_user_model + + +def initial_user_data(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0001_initial'), + ] + + operations = [ + migrations.RunPython(initial_user_data), + ] diff --git a/src/login/bkaccount/migrations/0003_bktoken_inactive_expire_time.py b/src/login/bkaccount/migrations/0003_bktoken_inactive_expire_time.py new file mode 100755 index 000000000..948f76a9e --- /dev/null +++ b/src/login/bkaccount/migrations/0003_bktoken_inactive_expire_time.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0002_initial_user_data'), + ] + + operations = [ + migrations.AddField( + model_name='bktoken', + name='inactive_expire_time', + field=models.IntegerField(default=0, verbose_name='\u65e0\u64cd\u4f5c\u5931\u6548\u65f6\u95f4\u6233'), + ), + ] diff --git a/src/login/bkaccount/migrations/0004_auto_20170621_0929.py b/src/login/bkaccount/migrations/0004_auto_20170621_0929.py new file mode 100755 index 000000000..345773c7b --- /dev/null +++ b/src/login/bkaccount/migrations/0004_auto_20170621_0929.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0003_bktoken_inactive_expire_time'), + ] + + operations = [ + ] diff --git a/src/login/bkaccount/migrations/0005_initial_role.py b/src/login/bkaccount/migrations/0005_initial_role.py new file mode 100755 index 000000000..cc517b2e5 --- /dev/null +++ b/src/login/bkaccount/migrations/0005_initial_role.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations + + +def load_data(apps, schema_editor): + """ + 初始化 用户角色 + """ + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0004_auto_20170621_0929'), + ] + + operations = [ + migrations.RunPython(load_data) + ] diff --git a/src/login/bkaccount/migrations/0006_initial_bkuser_role.py b/src/login/bkaccount/migrations/0006_initial_bkuser_role.py new file mode 100755 index 000000000..3eb3ac8b6 --- /dev/null +++ b/src/login/bkaccount/migrations/0006_initial_bkuser_role.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations + + +def load_data(apps, schema_editor): + """ + 初始化已存在的用户的角色 + """ + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0005_initial_role'), + ] + + operations = [ + migrations.RunPython(load_data) + ] diff --git a/src/login/bkaccount/migrations/0007_userinfo.py b/src/login/bkaccount/migrations/0007_userinfo.py new file mode 100755 index 000000000..60b07dcf9 --- /dev/null +++ b/src/login/bkaccount/migrations/0007_userinfo.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0006_initial_bkuser_role'), + ] + + operations = [ + ] diff --git a/src/login/bkaccount/migrations/0008_auto_20171116_2026.py b/src/login/bkaccount/migrations/0008_auto_20171116_2026.py new file mode 100755 index 000000000..f37100899 --- /dev/null +++ b/src/login/bkaccount/migrations/0008_auto_20171116_2026.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0007_userinfo'), + ] + + operations = [ + ] diff --git a/src/login/bkaccount/migrations/0009_add_role_data.py b/src/login/bkaccount/migrations/0009_add_role_data.py new file mode 100755 index 000000000..57d169187 --- /dev/null +++ b/src/login/bkaccount/migrations/0009_add_role_data.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations + + +def load_data(apps, schema_editor): + """ + 新增 用户角色 + """ + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0008_auto_20171116_2026'), + ] + + operations = [ + ] diff --git a/src/login/bkaccount/migrations/0010_auto_20190704_1106.py b/src/login/bkaccount/migrations/0010_auto_20190704_1106.py new file mode 100755 index 000000000..251a89369 --- /dev/null +++ b/src/login/bkaccount/migrations/0010_auto_20190704_1106.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bkaccount', '0009_add_role_data'), + ] + + operations = [ + migrations.RemoveField( + model_name='loignlog', + name='user', + ), + migrations.AddField( + model_name='loignlog', + name='username', + field=models.CharField(max_length=128, null=True, verbose_name='\u7528\u6237\u540d', blank=True), + ), + ] diff --git a/src/login/bkaccount/migrations/__init__.py b/src/login/bkaccount/migrations/__init__.py new file mode 100755 index 000000000..27e830ad6 --- /dev/null +++ b/src/login/bkaccount/migrations/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + diff --git a/src/login/bkaccount/models.py b/src/login/bkaccount/models.py new file mode 100755 index 000000000..dd4949881 --- /dev/null +++ b/src/login/bkaccount/models.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from builtins import object + +from bkaccount.manager import LoginLogManager +from django.db import models + + +class Loignlog(models.Model): + """ + User login log + """ + + username = models.CharField("用户名", max_length=128, blank=True, null=True) + login_time = models.DateTimeField("登录时间") + login_browser = models.CharField("登录浏览器", max_length=200, blank=True, null=True) + login_ip = models.CharField("用户登录ip", max_length=50, blank=True, null=True) + login_host = models.CharField("登录HOST", max_length=100, blank=True, null=True) + app_id = models.CharField("APP_ID", max_length=30, blank=True, null=True) + + objects = LoginLogManager() + + def __unicode__(self): + return "%s(%s)" % (self.user.chname, self.user.username) + + class Meta(object): + db_table = "login_bklog" + verbose_name = "用户登录日志" + verbose_name_plural = "用户登录日志" + + +class BkToken(models.Model): + """ + 登录票据 + """ + + token = models.CharField("登录票据", max_length=255, unique=True, db_index=True) + # 是否已经退出登录 + is_logout = models.BooleanField("票据是否已经执行过退出登录操作", default=False) + # 无操作过期时间戳 + inactive_expire_time = models.IntegerField("无操作失效时间戳", default=0) + + def __uincode__(self): + return self.token + + class Meta(object): + db_table = "login_bktoken" + verbose_name = "登录票据" + verbose_name_plural = "登录票据" diff --git a/src/login/bkauth/__init__.py b/src/login/bkauth/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/bkauth/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/bkauth/actions.py b/src/login/bkauth/actions.py new file mode 100755 index 000000000..f3d712489 --- /dev/null +++ b/src/login/bkauth/actions.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +import urllib.error +import urllib.parse +import urllib.request +from builtins import str + +from bkauth.constants import REDIRECT_FIELD_NAME +from bkauth.utils import get_bk_token, is_safe_url, record_login_log, set_bk_token_invalid +from django.conf import settings +from django.contrib.auth import login as auth_login +from django.contrib.auth.forms import AuthenticationForm +from django.http import HttpResponseRedirect +from django.template.response import TemplateResponse + +""" +actions for login success/fail +""" + + +BK_LOGIN_URL = str(settings.LOGIN_URL) +BK_COOKIE_NAME = settings.BK_COOKIE_NAME + + +def login_failed_response(request, redirect_to, app_id): + """ + 登录失败跳转,目前重定向到登录,后续可返还支持自定义的错误页面 + """ + redirect_url = BK_LOGIN_URL + query = {} + if redirect_to: + query[REDIRECT_FIELD_NAME] = redirect_to + if app_id: + query["app_id"] = app_id + + if query: + redirect_url = "%s?%s" % (BK_LOGIN_URL, urllib.parse.urlencode(query)) + response = HttpResponseRedirect(redirect_url) + response = set_bk_token_invalid(request, response) + return response + + +def login_success_response(request, user_or_form, redirect_to, app_id): + """ + 用户验证成功后,登录处理 + """ + # 判读是form还是user + if isinstance(user_or_form, AuthenticationForm): + user = user_or_form.get_user() + username = user.username + # username = user_or_form.cleaned_data.get('username', '') + else: + user = user_or_form + username = user.username + + # 检查回调URL是否安全,防钓鱼 + if not is_safe_url(url=redirect_to, host=request.get_host()): + # 调整到根目录 + redirect_to = "/console/" + + # if from logout + if redirect_to == "/logout/": + redirect_to = "/console/" + + # 设置用户登录 + auth_login(request, user) + # 记录登录日志 + record_login_log(request, username, app_id) + + secure = False + # uncomment this if you need a secure cookie; + # the http domain will not access the bk_token in secure cookie + # secure = (settings.HTTP_SCHEMA == "https") + bk_token, expire_time = get_bk_token(username) + response = HttpResponseRedirect(redirect_to) + response.set_cookie( + BK_COOKIE_NAME, bk_token, expires=expire_time, domain=settings.BK_COOKIE_DOMAIN, httponly=True, secure=secure + ) + + # set cookie for app or platform + response.set_cookie( + settings.LANGUAGE_COOKIE_NAME, + request.user.language, + # max_age=settings.LANGUAGE_COOKIE_AGE, + expires=expire_time, + path=settings.LANGUAGE_COOKIE_PATH, + domain=settings.LANGUAGE_COOKIE_DOMAIN, + ) + return response + + +def login_redirect_response(request, redirect_url, is_from_logout): + """ + 登录重定向 + """ + response = HttpResponseRedirect(redirect_url) + # 来自注销,则需清除蓝鲸bk_token + if is_from_logout: + response = set_bk_token_invalid(request, response) + return response + + +def login_license_fail_response(request, template_name="account/login.html"): + """ + 证书认证,登录失败页面 + """ + response = TemplateResponse(request, template_name, {"custom_login": True}) + response = set_bk_token_invalid(request, response) + return response diff --git a/src/login/bkauth/constants.py b/src/login/bkauth/constants.py new file mode 100755 index 000000000..d591817c7 --- /dev/null +++ b/src/login/bkauth/constants.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +REDIRECT_FIELD_NAME = "c_url" diff --git a/src/login/bkauth/decorators.py b/src/login/bkauth/decorators.py new file mode 100755 index 000000000..d6d46d955 --- /dev/null +++ b/src/login/bkauth/decorators.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from functools import wraps + +from django.utils.decorators import available_attrs + + +def login_exempt(view_func): + """ + 登录豁免,被此装饰器修饰的action可以不校验登录 + """ + + def wrapped_view(*args, **kwargs): + return view_func(*args, **kwargs) + + wrapped_view.login_exempt = True + return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) diff --git a/src/login/bkauth/forms.py b/src/login/bkauth/forms.py new file mode 100755 index 000000000..21e7ad784 --- /dev/null +++ b/src/login/bkauth/forms.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from django import forms +from django.contrib.auth import authenticate +from django.contrib.auth.forms import AuthenticationForm + + +class BkAuthenticationForm(AuthenticationForm): + def clean(self): + username = self.cleaned_data.get("username") + password = self.cleaned_data.get("password") + + if username and password: + self.user_cache = authenticate( + username=username, + password=password, + language=getattr(self.request, "LANGUAGE_CODE", ""), + ) + if self.user_cache is None: + raise forms.ValidationError( + self.error_messages["invalid_login"], + code="invalid_login", + params={"username": self.username_field.verbose_name}, + ) + else: + self.confirm_login_allowed(self.user_cache) + + return self.cleaned_data diff --git a/src/login/bkauth/manager.py b/src/login/bkauth/manager.py new file mode 100755 index 000000000..335b047af --- /dev/null +++ b/src/login/bkauth/manager.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from django.contrib.auth.models import BaseUserManager +from django.utils import timezone + + +class BkUserManager(BaseUserManager): + """BK user manager""" + + def create_user(self, username, password=None): + """ + Create and saves a User with the given username and password + """ + if not username: + raise ValueError("'The given username must be set") + + now = timezone.now() + user = self.model(username=username, last_login=now) + user.set_password(password) + user.save(using=self._db) + + return user diff --git a/src/login/bkauth/middlewares.py b/src/login/bkauth/middlewares.py new file mode 100755 index 000000000..6d532f6d3 --- /dev/null +++ b/src/login/bkauth/middlewares.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from builtins import object, str + +from bk_i18n.constants import BK_LANG_TO_DJANGO_LANG +from bkauth.constants import REDIRECT_FIELD_NAME +from bkauth.utils import validate_bk_token +from common.log import logger +from django.conf import settings +from django.contrib.auth import authenticate, get_user_model +from django.contrib.auth.models import AnonymousUser +from django.contrib.auth.views import redirect_to_login +from django.http import HttpResponse +from django.shortcuts import resolve_url +from django.utils import translation +from django.utils.six.moves.urllib.parse import urlparse + +BK_LOGIN_URL = str(settings.LOGIN_URL) + + +def redirect_login(request): + """ + 重定向到登录页面. + + 登录态验证不通过时调用 + """ + if request.is_ajax(): + return HttpResponse(status=401) + + path = request.build_absolute_uri() + resolved_login_url = resolve_url(BK_LOGIN_URL) + # If the login url is the same scheme and net location then just + # use the path as the "next" url. + login_scheme, login_netloc = urlparse(resolved_login_url)[:2] + current_scheme, current_netloc = urlparse(path)[:2] + if (not login_scheme or login_scheme == current_scheme) and (not login_netloc or login_netloc == current_netloc): + path = settings.SITE_URL[:-1] + request.get_full_path() + return redirect_to_login(path, resolved_login_url, REDIRECT_FIELD_NAME) + + +class LoginMiddleware(object): + def process_request(self, request): + """设置user""" + # 静态资源不做登录态设置 + full_path = request.get_full_path() + if full_path.startswith(settings.STATIC_URL) or full_path == "/robots.txt": + return None + + # 静态资源不做登录态设置 + if full_path in [settings.SITE_URL + "i18n/setlang/", "/i18n/setlang/"]: + return None + + user = None + bk_token = request.COOKIES.get("bk_token") + + path_prefix = settings.FORCE_SCRIPT_NAME or "" + if bk_token and full_path.startswith("%s/oauth/authorize/" % path_prefix): + is_valid, username, message = validate_bk_token(request.COOKIES) + if is_valid: + try: + UserModel = get_user_model() + user = UserModel.objects.get(username=username) + user.bk_token = bk_token + except Exception: + logger.exception("get user via username=%s fail", username) + user = None + else: + user = authenticate(request=request) + if user: + # 设置timezone session + request.session[settings.TIMEZONE_SESSION_KEY] = user.time_zone + # 设置language session + request.session[translation.LANGUAGE_SESSION_KEY] = BK_LANG_TO_DJANGO_LANG[user.language] + + request.user = user or AnonymousUser() + + def process_view(self, request, view, args, kwargs): + # 静态资源不做登录态验证 + full_path = request.get_full_path() + if full_path.startswith(settings.STATIC_URL) or full_path == "/robots.txt": + return None + + # 静态资源不做登录态验证 + if full_path in [ + settings.SITE_URL + "i18n/setlang/", + "/i18n/setlang/", + settings.SITE_URL + "jsi18n/i18n/", + "/jsi18n/i18n/", + ]: + return None + + if getattr(view, "login_exempt", False): + return None + + if request.user.is_authenticated: + return None + + return redirect_login(request) diff --git a/src/login/bkauth/migrations/0001_initial.py b/src/login/bkauth/migrations/0001_initial.py new file mode 100644 index 000000000..3aeadbe11 --- /dev/null +++ b/src/login/bkauth/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2021-07-14 10:51 +from __future__ import unicode_literals + +import django.contrib.auth.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('username', models.CharField(max_length=255, primary_key=True, serialize=False)), + ], + bases=(models.Model, django.contrib.auth.models.AnonymousUser), + ), + ] diff --git a/src/login/bkauth/migrations/__init__.py b/src/login/bkauth/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/login/bkauth/models.py b/src/login/bkauth/models.py new file mode 100755 index 000000000..886db5cf9 --- /dev/null +++ b/src/login/bkauth/models.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from builtins import object + +from bkauth.manager import BkUserManager +from bkauth.utils import is_bk_token_valid +from components.usermgr_api import upsert_user +from django.contrib.auth import models +from django.db import models as db_models +from django.utils import timezone +from past.builtins import basestring + + +class User(models.AbstractBaseUser, models.AnonymousUser): + """Blueking User Model, It's abstract and will not create table in database""" + + username = db_models.CharField(primary_key=True, max_length=255) + USERNAME_FIELD = "username" + + objects = BkUserManager() + + def __init__(self, *args, **kwargs): + self.init_fields() + + # NOTE: 兼容老版本文档中: + # user = UserModel(username, display_name="mockadmin", email="mockadmin@mock.com",) + if len(args) == 1 and isinstance(args[0], basestring): + args = (None, timezone.now(), args[0]) + + super(User, self).__init__(*args) + for k, v in list(kwargs.items()): + setattr(self, k, v) + + def init_fields(self): + self.time_zone = None + self.language = None + self.display_name = None + self.telephone = None + self.email = None + self.wx_id = None + self.position = None + self.role = None + self.extras = None + self.status = None + self.logo_url = None + + self.time_zone = None + self.language = None + + self.bk_token = None + self.is_superuser = False + + self.password = "" + + def fill_with_userinfo(self, userinfo): + self.username = userinfo.get("username") + self.display_name = userinfo.get("display_name") + self.telephone = userinfo.get("telephone") + self.email = userinfo.get("email") + self.wx_id = userinfo.get("wx_id") + self.position = userinfo.get("position") + self.role = userinfo.get("role") + self.extras = userinfo.get("extras") + self.status = userinfo.get("status") + self.logo_url = userinfo.get("logo_url") + self.time_zone = userinfo.get("time_zone") + self.language = userinfo.get("language") + + role = 1 if userinfo.get("role") == 1 else 0 + self.is_superuser = role == 1 + + def sync_to_usermgr(self): + """ + fields supported: + username string required, 用户名,长度:1~255 + + display_name string optional, 显示名,长度:1~255 + telephone string optional, 手机号,必须是手机号格式,11位数字 + email string optional, 邮箱,必须是邮箱格式 + position string optional, 职位 + role int optional, 角色,默认0:0 普通用户, 1 超级管理员, 2 开发者, 3 职能化用户, 4 审计员 + language string optional, 默认 zh-cn,可选 zh-cn、en + time_zone string optional, 默认 Asia/Shanghai + + """ + if not self.username: + return False, "username should be setted" + data = {} + for key in ["display_name", "telephone", "email", "position", "role", "language", "time_zone"]: + if getattr(self, key) is not None: + data[key] = getattr(self, key) + + if not data: + return False, "all the fields are None" + + ok, message, _data = upsert_user(self.username, **data) + return ok, message + + @property + def is_authenticated(self): + if not self.bk_token: + return False + ok, _ = is_bk_token_valid(self.bk_token) + return ok + + @property + def is_anonymous(self): + return not (self.is_authenticated) + + class Meta(object): + app_label = "bkauth" diff --git a/src/login/bkauth/utils.py b/src/login/bkauth/utils.py new file mode 100755 index 000000000..f67023c05 --- /dev/null +++ b/src/login/bkauth/utils.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import datetime +import time +import unicodedata + +from bkaccount.models import BkToken, Loignlog +from common.encryption import decrypt, encrypt, salt +from common.log import logger +from common.utils.basic import escape_html_return_msg +from django.conf import settings +from django.utils import timezone +from django.utils.six.moves.urllib.parse import urlparse +from django.utils.translation import ugettext as _ + +BK_COOKIE_AGE = settings.BK_COOKIE_AGE +BK_INACTIVE_COOKIE_AGE = settings.BK_INACTIVE_COOKIE_AGE + + +def get_bk_token(username): + """ + 生成用户的登录态 + """ + bk_token = "" + expire_time = int(time.time()) + # 重试5次 + retry_count = 0 + while not bk_token and retry_count < 5: + now_time = int(time.time()) + expire_time = now_time + BK_COOKIE_AGE + inactive_expire_time = now_time + BK_INACTIVE_COOKIE_AGE + plain_token = "%s|%s|%s" % (expire_time, username, salt()) + bk_token = encrypt(plain_token) + try: + # BkToken.objects.create(token=bk_token) + BkToken.objects.create(token=bk_token, inactive_expire_time=inactive_expire_time) + except Exception: + # logger.exception(u"登录票据保存失败") + logger.exception("Login ticket failed to be saved during ticket generation") + # 循环结束前将bk_token置空后重新生成 + bk_token = "" if retry_count < 4 else bk_token + retry_count += 1 + return bk_token, datetime.datetime.fromtimestamp(expire_time, timezone.get_current_timezone()) + + +def is_bk_token_valid(bk_token): # NOQA + """ + 验证用户登录态 + """ + if not bk_token: + error_msg = _("缺少参数bk_token") + return False, error_msg + + try: + plain_bk_token = decrypt(bk_token) + except Exception: + plain_bk_token = "" + # logger.exception(u"参数[%s]解析失败" % bk_token) + logger.exception("Parameter[%s] parse failed" % bk_token) + + # 参数bk_token非法 + error_msg = _("参数bk_token非法") + if not plain_bk_token: + return False, error_msg + + try: + token_info = plain_bk_token.split("|") + if not token_info or len(token_info) < 3: + return False, error_msg + except Exception: + logger.exception("split token fail: %s" % bk_token) + return False, error_msg + + try: + # is_logout = BkToken.objects.get(token=bk_token).is_logout + bktoken_obj = BkToken.objects.get(token=bk_token) + is_logout = bktoken_obj.is_logout + inactive_expire_time = bktoken_obj.inactive_expire_time + except Exception: + error_msg = _("不存在bk_token[%s]的记录") % bk_token + return False, error_msg + + expire_time = int(token_info[0]) + now_time = int(time.time()) + # token已注销 + if is_logout: + error_msg = _("登录态已注销") + return False, error_msg + # token有效期已过 + if now_time > expire_time + settings.BK_TOKEN_OFFSET_ERROR_TIME: + error_msg = _("登录态已过期") + return False, error_msg + # token有效期大于当前时间的有效期 + if expire_time - now_time > BK_COOKIE_AGE + settings.BK_TOKEN_OFFSET_ERROR_TIME: + error_msg = _("登录态有效期不合法") + return False, error_msg + + # token 无操作有效期已过, + if now_time > inactive_expire_time + settings.BK_TOKEN_OFFSET_ERROR_TIME: + error_msg = _("长时间无操作,登录态已过期") + return False, error_msg + + # 更新 无操作有效期 + try: + BkToken.objects.filter(token=bk_token).update(inactive_expire_time=now_time + BK_INACTIVE_COOKIE_AGE) + except Exception: + logger.exception("update inactive_expire_time fail") + + username = token_info[1] + return True, username + + +@escape_html_return_msg +def validate_bk_token(data): + """ + 检查bk_token的合法性,并返回用户实例 + """ + bk_token = data.get(settings.BK_COOKIE_NAME) + # 验证Token参数 + is_valid, username = is_bk_token_valid(bk_token) + if not is_valid: + return False, None, username + + # TODO: ? use usermgr get user check if user exists? + return True, username, "" + + +def set_bk_token_invalid(request, response=None): + """ + 将登录票据设置为不合法 + """ + bk_token = request.COOKIES.get(settings.BK_COOKIE_NAME, None) + if bk_token: + BkToken.objects.filter(token=bk_token).update(is_logout=True) + if response is not None: + # delete cookie + response.delete_cookie(settings.BK_COOKIE_NAME, domain=settings.BK_COOKIE_DOMAIN) + return response + return None + + +def is_safe_url(url, host=None): + """ + 判断url是否与当前host的根域一致 + + 以下情况返回False: + 1)根域不一致 + 2)url的scheme不为:https(s) + 3)url为空 + """ + if url is not None: + url = url.strip() + if not url: + return False + # Chrome treats \ completely as / + url = url.replace("\\", "/") + # Chrome considers any URL with more than two slashes to be absolute, but + # urlparse is not so flexible. Treat any url with three slashes as unsafe. + if url.startswith("///"): + return False + url_info = urlparse(url) + # Forbid URLs like http:///example.com - with a scheme, but without a hostname. + # In that URL, example.com is not the hostname but, a path component. However, + # Chrome will still consider example.com to be the hostname, so we must not + # allow this syntax. + if not url_info.netloc and url_info.scheme: + return False + # Forbid URLs that start with control characters. Some browsers (like + # Chrome) ignore quite a few control characters at the start of a + # URL and might consider the URL as scheme relative. + if unicodedata.category(url[0])[0] == "C": + return False + url_domain = url_info.netloc.split(":")[0].split(".")[-2] if url_info.netloc else "" + host_domain = host.split(":")[0].split(".")[-2] if host else "" + return (not url_info.netloc or url_domain == host_domain) and ( + not url_info.scheme or url_info.scheme in ["http", "https"] + ) + + +def record_login_log(request, username, app_id): + """ + 记录用户登录日志 + """ + host = request.get_host() + login_browser = request.META.get("HTTP_USER_AGENT", "unknown") + # 获取用户ip + login_ip = request.META.get("HTTP_X_FORWARDED_FOR", "REMOTE_ADDR") + + Loignlog.objects.record_login(username, login_browser, login_ip, host, app_id) diff --git a/src/login/bkauth/views.py b/src/login/bkauth/views.py new file mode 100755 index 000000000..d939e0002 --- /dev/null +++ b/src/login/bkauth/views.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from functools import wraps + +from bkauth.actions import login_license_fail_response, login_success_response +from bkauth.constants import REDIRECT_FIELD_NAME +from bkauth.forms import BkAuthenticationForm +from bkauth.utils import is_safe_url, set_bk_token_invalid +from common.exceptions import AuthenticationError +from common.license import check_license +from common.mixins.exempt import LoginExemptMixin +from common.usermgr import get_categories_str +from django.conf import settings +from django.contrib.auth import logout as auth_logout +from django.contrib.sites.shortcuts import get_current_site +from django.http import HttpResponseForbidden, HttpResponseRedirect +from django.shortcuts import render +from django.template.response import TemplateResponse +from django.utils.decorators import available_attrs +from django.utils.module_loading import import_string +from django.utils.translation import ugettext as _ +from django.views.generic import View + + +def only_plain_xframe_options_exempt(view_func): + """ + only allow /plain/ to be opened by a iframe + add some code: from django.views.decorators.clickjacking import xframe_options_exempt + """ + + def wrapped_view(*args, **kwargs): + resp = view_func(*args, **kwargs) + + if not isinstance(resp, HttpResponseRedirect): + origin_url = resp._request.META.get("HTTP_REFERER") + login_host = resp._request.get_host() + + if resp._request.path_info == "/plain/" and is_safe_url(url=origin_url, host=login_host): + resp.xframe_options_exempt = True + + return resp + + return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) + + +class LoginView(LoginExemptMixin, View): + """ + 登录 & 登录弹窗 + """ + + is_plain = False + + @only_plain_xframe_options_exempt + def get(self, request): + # TODO1: from django.views.decorators.clickjacking import xframe_options_exempt + # TODO2: should check if the request from the legal domain + return self._login(request) + + @only_plain_xframe_options_exempt + def post(self, request): + return self._login(request) + + def _login(self, request): + # 判断调用方式 + if settings.LOGIN_TYPE != "custom_login": + return _bk_login(request) + + if settings.EDITION == "ee": + # 校验企业正式是否有效,无效则不可登录 + is_license_ok, message, vaild_start_time, vaild_end_time = check_license() + if not is_license_ok: + return login_license_fail_response(request) + + # 调用自定义login view + custom_login_view = import_string(settings.CUSTOM_LOGIN_VIEW) + return custom_login_view(request) + + +def _bk_login(request): + """ + 登录页面和登录动作 + """ + authentication_form = BkAuthenticationForm + # NOTE: account/login.html 为支持自适应大小的模板 + template_name = "account/login.html" + reset_password_url = "%s://%s/o/bk_user_manage/reset_password" % (settings.HTTP_SCHEMA, request.get_host()) + + redirect_to = request.POST.get(REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME, "")) + # support oauth2 redirect ?next= + if not redirect_to and "next" in request.GET: + redirect_to = request.GET.get("next") + + app_id = request.POST.get("app_id", request.GET.get("app_id", "")) + + if settings.EDITION == "ee": + # 校验企业证书是否有效,无效则不可登录 + is_license_ok, message, vaild_start_time, vaild_end_time = check_license() + else: + is_license_ok = True + template_name = "account/login_ce.html" + + error_message = "" + login_redirect_to = "" + + # POST + if request.method == "POST" and is_license_ok: + form = authentication_form(request, data=request.POST) + try: + if form.is_valid(): + return login_success_response(request, form, redirect_to, app_id) + except AuthenticationError as e: + login_redirect_to = e.redirect_to + error_message = e.message + else: + error_message = _(u"账户或者密码错误,请重新输入") + # GET + else: + form = authentication_form(request) + + # NOTE: get categories from usermgr + categories = get_categories_str() + + current_site = get_current_site(request) + context = { + "form": form, + "error_message": error_message, + REDIRECT_FIELD_NAME: redirect_to, + "site": current_site, + "site_name": current_site.name, + "app_id": app_id, + "is_license_ok": is_license_ok, + "reset_password_url": reset_password_url, + "login_redirect_to": login_redirect_to, + "categories": categories, + "is_plain": request.path_info == "/plain/", + } + + response = TemplateResponse(request, template_name, context) + response = set_bk_token_invalid(request, response) + return response + + +class LogoutView(LoginExemptMixin, View): + """ + 登出并重定向到登录页面 + """ + + def get(self, request): + auth_logout(request) + next_page = None + + if REDIRECT_FIELD_NAME in request.POST or REDIRECT_FIELD_NAME in request.GET: + next_page = request.POST.get(REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME)) + # Security check -- don't allow redirection to a different host. + if not is_safe_url(url=next_page, host=request.get_host()): + next_page = request.path + + if next_page: + # Redirect to this page until the session has been cleared. + response = HttpResponseRedirect(next_page) + else: + # Redirect to login url. + response = HttpResponseRedirect("%s?%s" % (settings.LOGIN_URL, "is_from_logout=1")) + + # 将登录票据设置为不合法 + response = set_bk_token_invalid(request, response) + return response + + +def csrf_failure(request, reason=""): + return HttpResponseForbidden(render(request, "csrf_failure.html"), content_type="text/html") diff --git a/src/login/common/__init__.py b/src/login/common/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/common/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/common/constants.py b/src/login/common/constants.py new file mode 100755 index 000000000..05456923f --- /dev/null +++ b/src/login/common/constants.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +def enum(**enums): + return type("Enum", (), enums) + + +DATETIME_FORMAT_STRING = "%Y-%m-%d %H:%M:%S" + +LICENSE_VAILD_CACHE_KEY = "BK_LICENSE_VALID" + + +# 用户管理与登录自身提供的用户信息字段KeyMap +USERMGR_BKLOGIN_FIELD_MAP = { + "display_name": "chname", + "telephone": "phone", + "wx_id": "wx_userid", + "email": "email", + "role": "role", + "language": "language", + "time_zone": "time_zone", + "qq": "qq", +} + +BKLOGIN_USERMGR_FIELD_MAP = {v: k for k, v in list(USERMGR_BKLOGIN_FIELD_MAP.items())} diff --git a/src/login/common/context_processors.py b/src/login/common/context_processors.py new file mode 100755 index 000000000..a536f6a98 --- /dev/null +++ b/src/login/common/context_processors.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" +import urllib.parse +from builtins import str + +from django.conf import settings +from django.utils import timezone + +""" +context_processor for common(setting) +** 除setting外的其他context_processor内容,均采用组件的方式(string) +""" + + +def site_settings(request): + real_static_url = urllib.parse.urljoin(str(settings.SITE_URL), str("." + settings.STATIC_URL)) + cur_domain = request.get_host() + return { + "LOGIN_URL": settings.LOGIN_URL, + "LOGOUT_URL": settings.LOGOUT_URL, + "STATIC_URL": real_static_url, + "SITE_URL": settings.SITE_URL, + "STATIC_VERSION": settings.STATIC_VERSION, + "CUR_DOMIAN": cur_domain, + "APP_PATH": request.get_full_path(), + "NOW": timezone.now(), + "EDITION": settings.EDITION, + # 本地 js 后缀名 + "JS_SUFFIX": settings.JS_SUFFIX, + # 本地 css 后缀名 + "CSS_SUFFIX": settings.CSS_SUFFIX, + } diff --git a/src/login/common/encryption.py b/src/login/common/encryption.py new file mode 100755 index 000000000..83a3ef381 --- /dev/null +++ b/src/login/common/encryption.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import hashlib +import random +from base64 import urlsafe_b64decode, urlsafe_b64encode +from builtins import chr, range + +from Crypto.Cipher import AES +from django.conf import settings +from django.utils.encoding import force_bytes, force_text + +""" +登录态加密方法. + +使用AES算法,ECB模式 +""" + + +def pad(text, blocksize=16): + """ + PKCS#5 Padding + """ + pad = blocksize - (len(text) % blocksize) + return text + pad * chr(pad) + + +def unpad(text): + """ + PKCS#5 Padding + """ + pad = ord(text[-1]) + return text[:-pad] + + +def decrypt(ciphertext, key="", base64=True): + """ + AES Decrypt + """ + if not key: + key = settings.SECRET_KEY + key = force_bytes(key) + + if base64: + # ciphertext = urlsafe_b64decode(str(ciphertext + "=" * (4 - len(ciphertext) % 4))) + ciphertext = urlsafe_b64decode(ciphertext + "=" * (4 - len(ciphertext) % 4)) + + data = ciphertext + key = hashlib.md5(key).digest() + cipher = AES.new(key, AES.MODE_ECB) + return unpad(force_text(cipher.decrypt(data))) + + +def encrypt(plaintext, key="", base64=True): + """ + AES Encrypt + """ + if not key: + key = settings.SECRET_KEY + + key = force_bytes(key) + key = hashlib.md5(key).digest() + cipher = AES.new(key, AES.MODE_ECB) + ciphertext = cipher.encrypt(pad(plaintext)) + + # 将密文base64加密 + if base64: + # ciphertext = urlsafe_b64encode(str(ciphertext)).rstrip(b"=") + ciphertext = urlsafe_b64encode(ciphertext).rstrip(b"=") + + return ciphertext + + +def salt(length=8): + """ + 生成长度为length 的随机字符串 + """ + aplhabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return "".join([random.choice(aplhabet) for _ in range(length)]) diff --git a/src/login/common/exceptions.py b/src/login/common/exceptions.py new file mode 100755 index 000000000..26cce0132 --- /dev/null +++ b/src/login/common/exceptions.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from __future__ import unicode_literals + +from common.constants import enum + +LoginErrorCodes = enum( + E1302000_DEFAULT_CODE=1302000, + E1302001_BASE_SETTINGS_ERROR=1302001, + E1302002_BASE_DATABASE_ERROR=1302002, + E1302003_BASE_HTTP_DEPENDENCE_ERROR=1302003, + E1302004_BASE_BKSUITE_DATABASE_ERROR=1302004, + E1302005_BASE_LICENSE_ERROR=1302005, + # E1302006_ENTERPRISE_LOGIN_ERROR=1302006, +) + + +class AuthenticationError(Exception): + message = "login error" + redirect_to = "" + + def __init__(self, message=None, redirect_to=None): + if message is not None: + self.message = message + if redirect_to is not None: + self.redirect_to = redirect_to diff --git a/src/login/common/http.py b/src/login/common/http.py new file mode 100755 index 000000000..2fa361a75 --- /dev/null +++ b/src/login/common/http.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import requests +from common.log import logger + +""" +请求登录的http基础方法 + +Rules: +1. POST/DELETE/PUT: json in - json out, 如果resp.json报错, 则是登录接口问题 +2. GET带参数 HEAD不带参数 +3. 以统一的header头发送请求 +""" + + +def _gen_header(): + headers = { + "Content-Type": "application/json", + } + return headers + + +def _http_request(method, url, headers=None, data=None, timeout=None, verify=False, cert=None): + try: + if method == "GET": + resp = requests.get(url=url, headers=headers, params=data, timeout=timeout, verify=verify, cert=cert) + elif method == "HEAD": + resp = requests.head(url=url, headers=headers, verify=verify, cert=cert) + elif method == "POST": + resp = requests.post(url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert) + elif method == "DELETE": + resp = requests.delete(url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert) + elif method == "PUT": + resp = requests.put(url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert) + else: + return False, None + except requests.exceptions.RequestException: + logger.exception("http request error! method: %s, url: %s" % (method, url)) + return False, None + else: + if resp.status_code != 200: + content = resp.content[:100] if resp.content else "" + error_msg = "http request fail! method: %s, url: %s, " "response_status_code: %s, response_content: %s" + logger.error(error_msg % (method, url, resp.status_code, content)) + return False, None + + return True, resp.json() + + +def http_get(url, data, verify=False, cert=None, timeout=None): + headers = _gen_header() + return _http_request(method="GET", url=url, headers=headers, data=data, verify=verify, cert=cert, timeout=timeout) + + +def http_post(url, data, verify=False, cert=None, timeout=None): + headers = _gen_header() + return _http_request(method="POST", url=url, headers=headers, data=data, verify=verify, cert=cert, timeout=timeout) + + +def http_delete(url, data, verify=False, cert=None, timeout=None): + headers = _gen_header() + return _http_request( + method="DELETE", url=url, headers=headers, data=data, verify=verify, cert=cert, timeout=timeout + ) diff --git a/src/login/common/license.py b/src/login/common/license.py new file mode 100755 index 000000000..c14a4e3be --- /dev/null +++ b/src/login/common/license.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import os +from builtins import str + +from common.constants import DATETIME_FORMAT_STRING, LICENSE_VAILD_CACHE_KEY +from common.http import http_post +from common.log import logger +from common.utils.time import parse_local_datetime +from django.conf import settings +from django.core.cache import cache +from django.utils import timezone, translation +from django.utils.translation import ugettext as _ + +""" +企业证书校验等相关通用函数 +""" + + +def _validate_cert_key_file(cert_file, key_file): + """ + 校验本地cert和key文件 + """ + # 检查cert/key文件是否存在 + if not os.path.isfile(cert_file): + logger.error("The local certificate is unavailable: certificate file (platform.cert) does not exist") + return False, _("证书文件(platform.cert)不存在: %s") % cert_file, None + if not os.path.isfile(key_file): + logger.error("The local certificate is unavailable: key file (platform.key) does not exist") + return False, _("密钥文件(platform.key)不存在: %s") % key_file, None + # 读取证书文件内容 + cert_raw_string = None + with open(cert_file) as f: + cert_raw_string = f.read() + if not cert_raw_string: + msg = "The local certificate is unavailable: certificate file (platform.cert) is empty or has been damaged" + logger.error(msg) + return False, _("证书文件(platform.cert)为空或已被损坏"), None + return True, "", cert_raw_string + + +def _validate_remote_license(cert_server_url, cert_file, key_file, cert_raw_string): + """ + 请求证书服务器校验证书 + """ + param = { + "certificate": cert_raw_string, + "platform": "open_paas", + "requesttime": timezone.now().strftime(DATETIME_FORMAT_STRING), + } + + ok, data = http_post(cert_server_url, param, verify=False, cert=(cert_file, key_file)) + # do retry + retry_count = 0 + while (not ok) and retry_count < 3: + logger.info("validate remote license http post failed! retry %s" % (retry_count + 1)) + ok, data = http_post(cert_server_url, param, verify=False, cert=(cert_file, key_file), timeout=30) + retry_count += 1 + + if not ok: + return False, "request license_server error", _("license_server请求校验证书异常"), None, None + + if data["result"]: + return False, data["message"], data["message_cn"], None, None + return True, "", "", data["validstarttime"], data["validendtime"] + + +def check_license(): + """ + 检查企业正式是否有效 + :return: True/False, message, + """ + # 本地测试环境需要 + # if settings.ENVIRONMENT in ['development']: + # valid_start_time = parse_local_datetime('2017-05-05 12:00:00') + # valid_end_time = parse_local_datetime('2017-09-01 00:00:00') + # return True, u"证书校验成功", valid_start_time, valid_end_time + + client_cert_file_path = str(settings.CLIENT_CERT_FILE_PATH) + client_key_file_path = str(settings.CLIENT_KEY_FILE_PATH) + certificate_server_url = str(settings.CERTIFICATE_SERVER_URL) + + # 本地证书文件检查 + is_valid, message, cert_raw_string = _validate_cert_key_file(client_cert_file_path, client_key_file_path) + if not is_valid: + return False, message, None, None + + # 远程检查证书 + # 先从缓存中获取 + remote_license_result = cache.get(LICENSE_VAILD_CACHE_KEY) + if not remote_license_result: + remote_license_result = _validate_remote_license( + certificate_server_url, client_cert_file_path, client_key_file_path, cert_raw_string + ) + # 设置缓存 + cache.set(LICENSE_VAILD_CACHE_KEY, remote_license_result) + + is_valid, message, message_cn, valid_start_time, valid_end_time = remote_license_result + + if not is_valid: + logger.error(message) + # TODO to write a function for selecting data of current lageuage + error_message = message_cn if translation.get_language() in ["zh-hans"] else message + return False, error_message, None, None + + try: + # 时间转换 + valid_start_time = parse_local_datetime(valid_start_time, zone=timezone.utc) + valid_end_time = parse_local_datetime(valid_end_time, zone=timezone.utc) + except Exception as error: + logger.exception("An error occurred while checking enterprise certificate conversion time:%s" % error) + return False, _("证书不可用,请求未返回有效期或返回格式有误"), None, None + return True, _("证书校验成功"), valid_start_time, valid_end_time diff --git a/src/login/common/log.py b/src/login/common/log.py new file mode 100755 index 000000000..0cce1b94b --- /dev/null +++ b/src/login/common/log.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import logging + +logger = logging.getLogger("root") + +""" +Usage: + + from common.log import logger + + logger.info("test") + logger.error("wrong1") + logger.exception("wrong2") + + # with traceback + try: + 1 / 0 + except Exception: + logger.exception("wrong3") +""" diff --git a/src/login/common/mixins/__init__.py b/src/login/common/mixins/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/common/mixins/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/common/mixins/exempt.py b/src/login/common/mixins/exempt.py new file mode 100755 index 000000000..d28d44bca --- /dev/null +++ b/src/login/common/mixins/exempt.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from builtins import object + +from bkauth.decorators import login_exempt +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt + + +class CsrfExemptMixin(object): + """ + Mixin allows you to request without `csrftoken`. + """ + + @method_decorator(csrf_exempt) + def dispatch(self, *args, **kwargs): + return super(CsrfExemptMixin, self).dispatch(*args, **kwargs) + + +class LoginExemptMixin(object): + """ + Mixin allows you to request without `login`. + """ + + @method_decorator(login_exempt) + def dispatch(self, *args, **kwargs): + return super(LoginExemptMixin, self).dispatch(*args, **kwargs) + + +class CsrfAndLoginExemptMixin(object): + """ + Mixin allows you to request without `login` and `csrftoken`. + """ + + @method_decorator(csrf_exempt) + @method_decorator(login_exempt) + def dispatch(self, *args, **kwargs): + return super(CsrfAndLoginExemptMixin, self).dispatch(*args, **kwargs) diff --git a/src/login/common/usermgr.py b/src/login/common/usermgr.py new file mode 100755 index 000000000..3c3c13886 --- /dev/null +++ b/src/login/common/usermgr.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +from builtins import str + +from cachetools import TTLCache, cached +from common.constants import BKLOGIN_USERMGR_FIELD_MAP +from common.log import logger +from components import usermgr_api + + +def _user_info(usermgr_userinfo): + """ + 用户信息转换 + """ + return { + "username": usermgr_userinfo.get("username"), + "chname": (usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["chname"]) or usermgr_userinfo.get("chname") or ""), + "qq": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["qq"]) or "", + "phone": (usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["phone"]) or usermgr_userinfo.get("phone") or ""), + "email": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["email"]) or "", + "role": str(usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["role"])) or "", + "wx_userid": ( + usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["wx_userid"]) or usermgr_userinfo.get("wx_userid") or "" + ), + "language": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["language"]) or "", + "time_zone": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["time_zone"]) or "", + } + + +def _user_info_v2(usermgr_userinfo): + """ + 用户信息转换 + """ + return { + "bk_username": usermgr_userinfo.get("username"), + "chname": (usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["chname"]) or usermgr_userinfo.get("chname") or ""), + "qq": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["qq"]) or "", + "phone": (usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["phone"]) or usermgr_userinfo.get("phone") or ""), + "email": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["email"]) or "", + "bk_role": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["role"]) or 0, + "wx_userid": ( + usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["wx_userid"]) or usermgr_userinfo.get("wx_userid") or "" + ), + "language": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["language"]) or "", + "time_zone": usermgr_userinfo.get(BKLOGIN_USERMGR_FIELD_MAP["time_zone"]) or "", + } + + +def _batch_query_users(username_list, version=None): + """ + 转换数据 + """ + if version == "v1": + user_info_func = _user_info + elif version == "v2": + user_info_func = _user_info_v2 + else: + # v3 or None, will not change the return fields + user_info_func = None + + ok, message, data = usermgr_api.batch_query_users(username_list=username_list) + if data and len(data) and (user_info_func is not None): + data = [user_info_func(i) for i in data] + return ok, message, data + + +# def get_raw_user(username): +# ok, message, _data = _batch_query_users(username_list=[username]) +# if ok: +# # 判断是否能拿到数据 +# if not _data or len(_data) != 1: +# return False, "user do not exists", {} +# _data = _data[0] +# return ok, message, _data + + +def get_user(username, version="v1"): + """ + 获取单个用户信息 + """ + ok, message, _data = _batch_query_users(username_list=[username], version=version) + if ok: + # 判断是否能拿到数据 + if not _data or len(_data) != 1: + return False, "user do not exists", {} + _data = _data[0] + return ok, message, _data + + +def get_categories(): + try: + ok, message, _data = usermgr_api.get_categories() + except Exception: + logger.exception("usermgr_api get_categories fail") + return [] + + if not ok: + logger.error("login get categories from usermgr fail: %s", message) + return [] + + default_cats = [d for d in _data if d.get("default")] + cats = [d for d in _data if not d.get("default")] + default_cats.extend(cats) + + data = [] + for c in default_cats: + data.append( + { + "id": c.get("id"), + "domain": c.get("domain"), + "is_default": c.get("default", False), + } + ) + + return data + + +@cached(cache=TTLCache(maxsize=1024, ttl=60)) +def get_categories_str(): + categories = get_categories() + + if not categories: + return "" + + return ";".join([c.get("domain") for c in categories]) diff --git a/src/login/common/utils/__init__.py b/src/login/common/utils/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/common/utils/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/common/utils/basic.py b/src/login/common/utils/basic.py new file mode 100755 index 000000000..3d54d1d01 --- /dev/null +++ b/src/login/common/utils/basic.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from django.utils.html import escape as html_escape +from past.builtins import basestring + + +def escape_html_return_msg(func): + """ + 装饰器:用于验证信息返回xss转义 + """ + + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + # 对于字符串类型,进行html转义 + return [html_escape(item) if isinstance(item, basestring) else item for item in result] + + return wrapper diff --git a/src/login/common/utils/time.py b/src/login/common/utils/time.py new file mode 100755 index 000000000..0590de8cd --- /dev/null +++ b/src/login/common/utils/time.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +import datetime + +from common.constants import DATETIME_FORMAT_STRING +from django.utils import timezone + + +def _parse_datetime(date_string, format_string, zone=None, target_zone=None): + """ + 将不带时区的字符串转为目标时区的时间 + :param date_string:时间字符串 + :param format_string:时间字符串格式 + :param zone:时间字符串的时区,默认为本地时区 + :param target_zone: 目标时区,默认为本地时区 + """ + # get a naive datetime + naive_dt = datetime.datetime.strptime(date_string, format_string) + # makes a naive datetime.datetime in a given time zone aware. + aware_dt = timezone.make_aware(naive_dt, zone) + # converts an aware datetime.datetime to local time. + target_aware_dt = timezone.localtime(aware_dt, target_zone) + return target_aware_dt + + +def parse_local_datetime(date_string, format_string=DATETIME_FORMAT_STRING, zone=None): + """ + 将不带时区的字符串转为本地时区的时间 + :param date_string:时间字符串 + :param format_string:时间字符串格式 + :param zone:时间字符串的时区,默认为本地时区 + """ + return _parse_datetime(date_string, format_string, zone=zone) + + +def parse_utc_datetime(date_string, format_string=DATETIME_FORMAT_STRING, zone=None): + """ + 将不带时区的字符串转为UTC时区的时间 + :param date_string:时间字符串 + :param format_string:时间字符串格式 + :param zone:时间字符串的时区,默认为本地时区 + """ + return _parse_datetime(date_string, format_string, zone=zone, target_zone=timezone.utc) diff --git a/src/login/components/__init__.py b/src/login/components/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/components/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/components/http.py b/src/login/components/http.py new file mode 100755 index 000000000..5301f0a35 --- /dev/null +++ b/src/login/components/http.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import unicode_literals + +import requests +from common.log import logger + +""" +all new components should use the http.py here! +""" + + +def _gen_header(): + headers = { + "Content-Type": "application/json", + } + return headers + + +def _http_request(method, url, headers=None, data=None, timeout=None, verify=False, cert=None, cookies=None): + try: + if method == "GET": + resp = requests.get( + url=url, headers=headers, params=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + elif method == "HEAD": + resp = requests.head(url=url, headers=headers, verify=verify, cert=cert, cookies=cookies) + elif method == "POST": + resp = requests.post( + url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + elif method == "DELETE": + resp = requests.delete( + url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + elif method == "PUT": + resp = requests.put( + url=url, headers=headers, json=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + else: + return False, None + except requests.exceptions.RequestException: + logger.exception("http request error! method: %s, url: %s, data: %s", method, url, data) + return False, None + else: + if resp.status_code != 200: + content = resp.content[:100] if resp.content else "" + error_msg = "http request fail! method: %s, url: %s, " "response_status_code: %s, response_content: %s" + # if isinstance(content, str): + # try: + # content = content.decode('utf-8') + # except Exception: + # content = content + logger.error(error_msg, method, url, resp.status_code, content) + return False, None + + return True, resp.json() + + +def http_get(url, data, headers=None, verify=False, cert=None, timeout=None, cookies=None): + if not headers: + headers = _gen_header() + return _http_request( + method="GET", url=url, headers=headers, data=data, verify=verify, cert=cert, timeout=timeout, cookies=cookies + ) + + +def http_post(url, data, headers=None, verify=False, cert=None, timeout=None, cookies=None): + if not headers: + headers = _gen_header() + return _http_request( + method="POST", url=url, headers=headers, data=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + + +def http_put(url, data, headers=None, verify=False, cert=None, timeout=None, cookies=None): + if not headers: + headers = _gen_header() + return _http_request( + method="PUT", url=url, headers=headers, data=data, timeout=timeout, verify=verify, cert=cert, cookies=cookies + ) + + +def http_delete(url, data, headers=None, verify=False, cert=None, timeout=None, cookies=None): + if not headers: + headers = _gen_header() + return _http_request( + method="DELETE", + url=url, + headers=headers, + data=data, + timeout=timeout, + verify=verify, + cert=cert, + cookies=cookies, + ) diff --git a/src/login/components/usermgr_api.py b/src/login/components/usermgr_api.py new file mode 100755 index 000000000..f49b7c49f --- /dev/null +++ b/src/login/components/usermgr_api.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import absolute_import, unicode_literals + +from common.log import logger +from django.conf import settings + +from .http import http_get, http_post + +""" +usermgr api +""" + +BK_USERMGR_HOST = settings.BK_USERMGR_API_URL + + +def _call_usermgr_api(http_func, url, data, headers=None): + # TODO: 后续添加Token Header进行服务间认证 + try: + ok, _data = http_func(url, data, headers=headers) + if not ok: + return False, -1, "verify from usermgr server fail", None + except Exception: + logger.exception("_call_usermgr_api fail: url=%s, data=%s", url, data) + return False, -1, "verify from usermgr server fail", None + + if not _data.get("result"): + if data and "password" in data: + data["password"] = "******" + logger.info("_call_usermgr_api fail: url=%s, data=%s, _data=%s", url, data, _data) + return False, _data.get("code", -1), _data.get("message", "usermgr api fail"), _data.get("data") + + return True, 0, "ok", _data.get("data") + + +def authenticate(username, password, language="", domain=""): + """ + 认证用户名和密码 + username: 用户名、电话号码、邮箱三选一,如果存在重名,会验证失败 + """ + path = "/api/v1/login/check/" + url = "{host}{path}".format(host=BK_USERMGR_HOST, path=path) + + data = { + "username": username, + "password": password, + } + if domain: + data["domain"] = domain + + ok, code, message, _data = _call_usermgr_api( + http_post, + url, + data, + headers={ + "Blueking-Language": language, + "Content-Type": "application/json", + }, + ) + return ok, code, message, _data + + +def batch_query_users(username_list=[], is_complete=False): + """ + 批量获取用户,可以获取所有 + """ + path = "/api/v1/login/profile/query/" + url = "{host}{path}".format(host=BK_USERMGR_HOST, path=path) + + data = { + "username_list": username_list, + "is_complete": is_complete, + } + + ok, _, message, _data = _call_usermgr_api(http_post, url, data) + return ok, message, _data + + +def upsert_user(username, **kwargs): + """ + 创建或更新用户 + 主要用于ee_login,企业第三方应用某些情况下需要支持将用户存储到用户管理 + """ + path = "/api/v1/login/profile/" + url = "{host}{path}".format(host=BK_USERMGR_HOST, path=path) + + data = { + "username": username, + } + data.update(kwargs) + ok, _, message, _data = _call_usermgr_api(http_post, url, data) + return ok, message, _data + + +def get_categories(): + path = "/api/v2/categories/" + url = "{host}{path}".format(host=BK_USERMGR_HOST, path=path) + + data = { + "no_page": True, + "fields": "domain,id,default", + "lookup_field": "enabled", + "exact_lookups": True, + } + + ok, _, message, _data = _call_usermgr_api(http_get, url, data) + if 'results' in _data: + _data = _data['results'] + return ok, message, _data diff --git a/src/login/conf/__init__.py b/src/login/conf/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/conf/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/conf/default.py b/src/login/conf/default.py new file mode 100755 index 000000000..0b2b1de49 --- /dev/null +++ b/src/login/conf/default.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +from __future__ import print_function, unicode_literals + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os +import sys + +from django.utils.functional import SimpleLazyObject + +try: + import pymysql + + pymysql.version_info = (1, 3, 13, "final", 0) + pymysql.install_as_MySQLdb() +except Exception: + pass + +PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) +PROJECT_ROOT, PROJECT_MODULE_NAME = os.path.split(PROJECT_PATH) +BASE_DIR = os.path.dirname(os.path.dirname(PROJECT_PATH)) + +EDITION = os.environ.get("BK_PAAS_EDITION", "ce") + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "o7(025idh*fj@)ohujum-ilfxl^n=@d&$xz!_$$7s$8jopd5r#" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ["*"] + +CSRF_COOKIE_NAME = "bklogin_csrftoken" + +# Application definition +INSTALLED_APPS = ( + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_prometheus", + "bkaccount", + "bkauth", + "bk_i18n", +) + +MIDDLEWARE_CLASSES = ( + "django_prometheus.middleware.PrometheusBeforeMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.auth.middleware.SessionAuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.security.SecurityMiddleware", + "bkauth.middlewares.LoginMiddleware", + "bk_i18n.middlewares.LanguageMiddleware", + "bk_i18n.middlewares.ApiLanguageMiddleware", + "bk_i18n.middlewares.TimezoneMiddleware", + "django_prometheus.middleware.PrometheusAfterMiddleware", +) + +ROOT_URLCONF = "urls" + +# mako template dir +MAKO_TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "templates") +MAKO_TEMPLATE_MODULE_DIR = os.path.join(PROJECT_ROOT, "templates_module") + + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.csrf", + "common.context_processors.site_settings", + "django.template.context_processors.i18n", + "django.contrib.messages.context_processors.messages", +) + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + # django template dir + "DIRS": ( + # 绝对路径,比如"/home/html/django_templates" or "C:/www/django/templates". + os.path.join(PROJECT_ROOT, "templates"), + ), + "APP_DIRS": True, + "OPTIONS": { + "context_processors": list(TEMPLATE_CONTEXT_PROCESSORS), + }, + }, +] + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ +SITE_URL = "/login/" + +STATIC_URL = "/static/" + +STATICFILES_DIRS = (os.path.join(PROJECT_ROOT, "static"),) + +STATIC_VERSION = "0.2.3" + +# CSS 文件后缀名 +CSS_SUFFIX = "min.css" +# JS 文件后缀名 +JS_SUFFIX = "min.js" + +# CSRF 验证失败处理函数 +CSRF_FAILURE_VIEW = "bkauth.views.csrf_failure" + +################## +# Login Config # +################## +# 蓝鲸登录方式:bk_login,自定义登录方式:custom_login +LOGIN_TYPE = "bk_login" +CUSTOM_LOGIN_VIEW = "" +CUSTOM_AUTHENTICATION_BACKEND = "" +try: + custom_conf_module_path = "ee_login.settings_login" + + custom_conf_module = __import__(custom_conf_module_path, globals(), locals(), ["*"]) + LOGIN_TYPE = getattr(custom_conf_module, "LOGIN_TYPE", "bk_login") + + CUSTOM_LOGIN_VIEW = getattr(custom_conf_module, "CUSTOM_LOGIN_VIEW", "") + CUSTOM_AUTHENTICATION_BACKEND = getattr(custom_conf_module, "CUSTOM_AUTHENTICATION_BACKEND", "") + # 支持自定义登录 patch 原有的所有URL 和 添加自定义 Application START + ROOT_URLCONF = getattr(custom_conf_module, "ROOT_URLCONF", None) or ROOT_URLCONF + if LOGIN_TYPE == "custom_login": + INSTALLED_APPS = tuple( # type: ignore + list(INSTALLED_APPS) + + getattr( + custom_conf_module, + "CUSTOM_INSTALLED_APPS", + [], + ) + ) + # 支持自定义登录 patch 原有的所有URL 和 添加自定义 Application END +except ImportError as e: + print("load custom_login settings fail!", e) + LOGIN_TYPE = "bk_login" +################## +# AUTHENTICATION # +################## +LOGIN_URL = SITE_URL + +FORCE_SCRIPT_NAME = "/login" + +LOGOUT_URL = "%slogout/" % SITE_URL + +LOGIN_COMPLETE_URL = SimpleLazyObject( + lambda: "%s://%s%s" + % ( + getattr(getattr(sys.modules["django.conf"], "settings"), "HTTP_SCHEMA"), + getattr(getattr(sys.modules["django.conf"], "settings"), "BK_LOGIN_PUBLIC_ADDR"), + getattr(getattr(sys.modules["django.conf"], "settings"), "SITE_URL"), + ) +) + +AUTH_USER_MODEL = "bkauth.User" + +AUTHENTICATION_BACKENDS_DICT = { + "bk_login": ["backends.bk.BkUserBackend"], + "custom_login": [CUSTOM_AUTHENTICATION_BACKEND], +} +AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS_DICT.get(LOGIN_TYPE, ["bkaccount.backends.BkRemoteUserBackend"]) + +WSGI_APPLICATION = "wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + } +} + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ +# TIME_ZONE = 'Etc/GMT%+d' % ((time.altzone if time.daylight else time.timezone) / 3600) +USE_I18N = True +USE_L10N = True + +# timezone +# Default time zone for localization in the UI. +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +TIME_ZONE = "Asia/Shanghai" +USE_TZ = True +TIMEZONE_SESSION_KEY = "django_timezone" + +# language +# 避免循环引用 +_ = lambda s: s # noqa +LANGUAGES = ( + ("en", _("English")), + ("zh-hans", _("简体中文")), +) +LANGUAGE_CODE = "zh-hans" +LANGUAGE_COOKIE_DOMAIN = SimpleLazyObject( + lambda: getattr(getattr(sys.modules["django.conf"], "settings"), "BK_COOKIE_DOMAIN") +) +LANGUAGE_COOKIE_NAME = "blueking_language" +LANGUAGE_COOKIE_PATH = "/" +LOCALE_PATHS = (os.path.join(PROJECT_ROOT, "locale"),) + +# cookie名称 +BK_COOKIE_NAME = "bk_token" +# cookie 有效期,默认为1天 +BK_COOKIE_AGE = 60 * 60 * 24 +# bk_token 校验有效期校验时间允许误差,防止多台机器时间不同步,默认1分钟 +BK_TOKEN_OFFSET_ERROR_TIME = 60 +# 无操作 失效期,默认2个小时. 长时间误操作, 登录态已过期 +BK_INACTIVE_COOKIE_AGE = 60 * 60 * 2 + + +# APP_ENGINE 状态查询超时时间 +EVENT_STATE_EXPIRE_SECONDS = 180 +HISTORY_EVENT_STATE_EXPIRE_SECONDS = 1800 + +################## +# 企业证书校验相关 # +################## +CLIENT_CERT_FILE_PATH = SimpleLazyObject( + lambda: os.path.join(getattr(getattr(sys.modules["django.conf"], "settings"), "CERTIFICATE_DIR"), "platform.cert") +) +CLIENT_KEY_FILE_PATH = SimpleLazyObject( + lambda: os.path.join(getattr(getattr(sys.modules["django.conf"], "settings"), "CERTIFICATE_DIR"), "platform.key") +) +CERTIFICATE_SERVER_URL = SimpleLazyObject( + lambda: "https://%s/certificate" + % getattr(getattr(sys.modules["django.conf"], "settings"), "CERTIFICATE_SERVER_DOMAIN") +) + +# cache config +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "TIMEOUT": 30, + "OPTIONS": {"MAX_ENTRIES": 1000}, + } +} + +# logging config +LOGGER_LEVEL = "DEBUG" + +LOGGING_DIR = os.environ.get("PAAS_LOGGING_DIR") or os.path.join(os.path.dirname(BASE_DIR), "logs") +if not os.path.exists(LOGGING_DIR): + os.mkdir(LOGGING_DIR) + +# 10M +LOG_MAX_BYTES = 1024 * 1024 * 10 +LOG_BACKUP_COUNT = 10 +LOG_CLASS = "logging.handlers.RotatingFileHandler" +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s [%(asctime)s] %(pathname)s %(lineno)d %(funcName)s %(process)d %(thread)d \n \t %(message)s \n", # noqa + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + "simple": {"format": "%(levelname)s %(message)s \n"}, + }, + "handlers": { + "null": { + "level": "DEBUG", + "class": "logging.NullHandler", + }, + "mail_admins": {"level": "ERROR", "class": "django.utils.log.AdminEmailHandler"}, + "console": {"level": "DEBUG", "class": "logging.StreamHandler", "formatter": "simple"}, + "root": { + "class": LOG_CLASS, + "formatter": "verbose", + "filename": os.path.join(LOGGING_DIR, "login.log"), + "maxBytes": LOG_MAX_BYTES, + "backupCount": LOG_BACKUP_COUNT, + }, + "wb_mysql": { + "class": LOG_CLASS, + "formatter": "verbose", + "filename": os.path.join(LOGGING_DIR, "login_mysql.log"), + "maxBytes": LOG_MAX_BYTES, + "backupCount": LOG_BACKUP_COUNT, + }, + }, + "loggers": { + "django": { + "handlers": ["null"], + "level": "ERROR", + "propagate": True, + }, + "django.request": { + "handlers": ["console"], + "level": "ERROR", + "propagate": True, + }, + "root": { + "handlers": ["console", "root"], + "level": LOGGER_LEVEL, + "propagate": True, + }, + "django.db.backends": { + "handlers": ["wb_mysql"], + "level": "ERROR", + "propagate": True, + }, + }, +} diff --git a/src/login/conf/settings_env.py b/src/login/conf/settings_env.py new file mode 100755 index 000000000..5b1f773fa --- /dev/null +++ b/src/login/conf/settings_env.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +import environ + +env = environ.Env() + +# Generic Django project settings +DEBUG = env.bool("DEBUG", False) + +# use the static root 'static' in production envs +if not DEBUG: + STATIC_ROOT = "static" + +# 数据库配置信息 +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": env.str("DATABASE_NAME", "bk_login"), + "USER": env.str("DATABASE_USER"), + "PASSWORD": env.str("DATABASE_PASSWORD"), + "HOST": env.str("DATABASE_HOST", ""), + "PORT": env.int("DATABASE_PORT"), + } +} + +# django secret key,同时用于加密登录态票据(bk_token) +SECRET_KEY = env.str("ENCRYPT_SECRET_KEY") +# 与 ESB 通信的密钥 +ESB_TOKEN = env.str("BK_PAAS_SECRET_KEY") + +# website +# domain +BK_LOGIN_PUBLIC_ADDR = env.str("BK_LOGIN_PUBLIC_ADDR") +# schema = http/https, default http +HTTP_SCHEMA = env.str("BK_LOGIN_HTTP_SCHEMA", "http") + +# session in cookie secure +IS_SESSION_COOKIE_SECURE = env.bool("IS_SESSION_COOKIE_SECURE", False) +if HTTP_SCHEMA == "https" and IS_SESSION_COOKIE_SECURE: + SESSION_COOKIE_SECURE = True + + +# cookie访问域 +BK_COOKIE_DOMAIN = "." + env.str("BK_DOMAIN") + + +# 用户管理API访问地址 +BK_USERMGR_API_URL = env.str("BK_USERMGR_API_URL", "http://bk-usermgr-svc") + +# external config +# license +CERTIFICATE_DIR = env.str("BK_LOGIN_LOGIN_CERT_PATH", "") +CERTIFICATE_SERVER_DOMAIN = env.str("BK_LOGIN_LOGIN_CERT_SERVER_LOCAL_ADDR", "") + + +# cookie 有效期,默认为1天 +BK_COOKIE_AGE = env.int("BK_LOGIN_LOGIN_COOKIE_AGE", 60 * 60 * 24) +# bk_token 校验有效期校验时间允许误差,防止多台机器时间不同步,默认1分钟 +BK_TOKEN_OFFSET_ERROR_TIME = env.int("BK_LOGIN_LOGIN_TOKEN_OFFSET_ERROR_TIME", 60) +# 无操作 失效期,默认2个小时. 长时间误操作, 登录态已过期 +BK_INACTIVE_COOKIE_AGE = env.int("BK_LOGIN_LOGIN_INACTIVE_COOKIE_AGE", 60 * 60 * 2) diff --git a/src/login/ee_login/__init__.py b/src/login/ee_login/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/ee_login/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/ee_login/settings_login.py b/src/login/ee_login/settings_login.py new file mode 100755 index 000000000..82ef8bbae --- /dev/null +++ b/src/login/ee_login/settings_login.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +# 蓝鲸登录方式:bk_login +# 企业内部Username-password登录方式:enterprise_ldap +# 自定义登录方式:custom_login +LOGIN_TYPE = "bk_login" + +# 默认bk_login,无需设置其他配置 + +########################### +# 自定义登录 custom_login # +########################### +# 配置自定义登录请求和登录回调的响应函数, 如:CUSTOM_LOGIN_VIEW = 'ee_official_login.oauth.google.views.login' +CUSTOM_LOGIN_VIEW = "" +# 配置自定义验证是否登录的认证函数, 如:CUSTOM_AUTHENTICATION_BACKEND = 'ee_official_login.oauth.google.backends.OauthBackend' +CUSTOM_AUTHENTICATION_BACKEND = "" diff --git a/src/login/ee_login/settings_login_mock.py b/src/login/ee_login/settings_login_mock.py new file mode 100755 index 000000000..60f5c1465 --- /dev/null +++ b/src/login/ee_login/settings_login_mock.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +# 蓝鲸登录方式:bk_login +# 企业内部Username-password登录方式:enterprise_ldap +# 自定义登录方式:custom_login +LOGIN_TYPE = "custom_login" + +# 默认bk_login,无需设置其他配置 + +########################### +# 自定义登录 custom_login # +########################### +# 配置自定义登录请求和登录回调的响应函数, 如:CUSTOM_LOGIN_VIEW = 'ee_official_login.oauth.google.views.login' +CUSTOM_LOGIN_VIEW = "ee_official_login.mock.views.login" +# 配置自定义验证是否登录的认证函数, 如:CUSTOM_AUTHENTICATION_BACKEND = 'ee_official_login.oauth.google.backends.OauthBackend' +CUSTOM_AUTHENTICATION_BACKEND = "ee_official_login.mock.backends.MockBackend" diff --git a/src/login/ee_login/settings_login_oauth_google.py b/src/login/ee_login/settings_login_oauth_google.py new file mode 100755 index 000000000..ba16df1dd --- /dev/null +++ b/src/login/ee_login/settings_login_oauth_google.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +# 蓝鲸登录方式:bk_login +# 企业内部Username-password登录方式:enterprise_ldap +# 自定义登录方式:custom_login +LOGIN_TYPE = "custom_login" + +# 默认bk_login,无需设置其他配置 + +########################### +# 自定义登录 custom_login # +########################### +# 配置自定义登录请求和登录回调的响应函数, 如:CUSTOM_LOGIN_VIEW = 'ee_official_login.oauth.google.views.login' +CUSTOM_LOGIN_VIEW = "ee_official_login.oauth.google.views.login" +# 配置自定义验证是否登录的认证函数, 如:CUSTOM_AUTHENTICATION_BACKEND = 'ee_official_login.oauth.google.backends.OauthBackend' +CUSTOM_AUTHENTICATION_BACKEND = "ee_official_login.oauth.google.backends.OauthBackend" diff --git a/src/login/ee_official_login/__init__.py b/src/login/ee_official_login/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/ee_official_login/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/ee_official_login/mock/__init__.py b/src/login/ee_official_login/mock/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/ee_official_login/mock/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/ee_official_login/mock/backends.py b/src/login/ee_official_login/mock/backends.py new file mode 100755 index 000000000..84830b888 --- /dev/null +++ b/src/login/ee_official_login/mock/backends.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from common.log import logger +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + + +class MockBackend(ModelBackend): + """ + mock认证服务 + + username == "admin" 且 password == "blueking" 时认证通过 + + 注意: 打logger.debug用于调试, 可以在日志路径下login.log查看到对应日志 + """ + + def authenticate(self, username=None, password=None): + if not (username == "admin" and password == "blueking"): + logger.debug("MockBackend authenticate fail, username/password should be admin/blueking") + return None + + # 获取User类 + UserModel = get_user_model() + # 初始化User对象 -> bkauth/models.py:User -> 从userinfo获取对应字段进行初始化 + user = UserModel() + user.username = username + user.display_name = "mockadmin" + user.email = "mockadmin@mock.com" + + # 同步用户到用户管理 sync to usermgr + # ok, message = user.sync_to_usermgr() + ok, message = True, "success" + if not ok: + logger.error("login success, but sync user to usermgr fail: %s", message) + return None + + return user diff --git a/src/login/ee_official_login/mock/views.py b/src/login/ee_official_login/mock/views.py new file mode 100755 index 000000000..59a1a1030 --- /dev/null +++ b/src/login/ee_official_login/mock/views.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from bkauth.actions import login_failed_response, login_success_response +from bkauth.constants import REDIRECT_FIELD_NAME +from bkauth.forms import BkAuthenticationForm +from bkauth.utils import set_bk_token_invalid +from common.log import logger +from django.contrib.auth import authenticate +from django.contrib.sites.shortcuts import get_current_site +from django.template.response import TemplateResponse + + +def login(request): + """ + 登录处理 + """ + redirect_to = request.GET.get(REDIRECT_FIELD_NAME, "") + + # 复用bkauth的登录页面 + if request.method == "POST": + form = BkAuthenticationForm(request, data=request.POST) + + username = form.data["username"] + password = form.data["password"] + + # will call MockBackend.authenticate + user = authenticate(username=username, password=password) + if user is None: + logger.debug("custom_login:mock user is None, will redirect_to=%s", redirect_to) + # 直接调用蓝鲸登录失败处理方法 + return login_failed_response(request, redirect_to, app_id=None) + # 成功,则调用蓝鲸登录成功的处理函数,并返回响应 + logger.debug("custom_login:mock login success, will redirect_to=%s", redirect_to) + return login_success_response(request, user, redirect_to, app_id=None) + # GET + else: + form = BkAuthenticationForm(request) + current_site = get_current_site(request) + context = { + "form": form, + REDIRECT_FIELD_NAME: redirect_to, + "site": current_site, + "site_name": current_site.name, + # set to default + "error_message": "", + "app_id": "", + "is_license_ok": True, + "reset_password_url": "", + "login_redirect_to": "", + } + + template_name = "account/login.html" + response = TemplateResponse(request, template_name, context) + response = set_bk_token_invalid(request, response) + return response diff --git a/src/login/ee_official_login/oauth/__init__.py b/src/login/ee_official_login/oauth/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/ee_official_login/oauth/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/ee_official_login/oauth/google/__init__.py b/src/login/ee_official_login/oauth/google/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/ee_official_login/oauth/google/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/ee_official_login/oauth/google/backends.py b/src/login/ee_official_login/oauth/google/backends.py new file mode 100755 index 000000000..c53c0bbc5 --- /dev/null +++ b/src/login/ee_official_login/oauth/google/backends.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" +from __future__ import print_function + +from common.log import logger +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend + +from .utils import get_access_token, get_scope_data + + +class OauthBackend(ModelBackend): + """ + 自定义认证方法 + + 注意: 打logger.debug用于调试, 可以在日志路径下login.log查看到对应日志 + """ + + def authenticate(self, code=None): + # Google登录验证 + try: + # 调用接口验证登录票据CODE,并获取access_token + access_token = get_access_token(code) + if not access_token: + logger.debug("OauthBackend get_access_token fail") + return None + # 通过access_token 获取用户信息 + userinfo = get_scope_data(access_token) + if not userinfo: + logger.debug("OauthBackend get_scope_data fail") + return None + + logger.debug("OauthBackend get userinfo=%s", userinfo) + + # 验证通过 + username = userinfo.get("username") + + # 获取User类 + UserModel = get_user_model() + # 初始化User对象 -> bkauth/models.py:User -> 从userinfo获取对应字段进行初始化 + # 新建用户时, username/display_name必须 + user = UserModel() + user.username = username + user.display_name = userinfo.get("display_name") + user.email = userinfo.get("email") + user.telephone = userinfo.get("telephone") + print(user) + + # 同步用户到用户管理 sync to usermgr + ok, message = user.sync_to_usermgr() + if not ok: + logger.error("login success, but sync user to usermgr fail: %s", message) + return None + + return user + + except Exception: + logger.exception("Google login backend validation error!") + return None diff --git a/src/login/ee_official_login/oauth/google/settings.py b/src/login/ee_official_login/oauth/google/settings.py new file mode 100755 index 000000000..44209387a --- /dev/null +++ b/src/login/ee_official_login/oauth/google/settings.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +# google oauth2.0 登录URL +GOOGLE_OAUTH_LOGIN_URL = "https://accounts.google.com/o/oauth2/auth" + +# 通过认证Code获取Access_token的API URL +ACCESS_TOKEN_URL = "https://www.googleapis.com/oauth2/v3/token" + +# 获取google 用户信息的API URL +SCOPE_URL = "https://www.googleapis.com/userinfo/v2/me" + +# Google OAuth 2.0 客户端 ID +CLIENT_ID = "" + +# Google OAuth 2.0 客户端 密钥 +CLIENT_SECRET = "" diff --git a/src/login/ee_official_login/oauth/google/utils.py b/src/login/ee_official_login/oauth/google/utils.py new file mode 100755 index 000000000..d9f4a995c --- /dev/null +++ b/src/login/ee_official_login/oauth/google/utils.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" +import random +import urllib.error +import urllib.parse +import urllib.request +from builtins import range, str + +import requests +from common.log import logger +from django.conf import settings as bk_settings + +from . import settings as google_setting + + +def gen_oauth_state_security_token(length=32): + """ + 生成随机的state,防止csrf + """ + allowed_chars = "abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789" + state = "".join(random.choice(allowed_chars) for _ in range(length)) + return state + + +def gen_oauth_login_url(extra_param): + """ + 生成跳转登录的URL + """ + # 由于google校验redirect_uri是精准匹配的,所有redirect_uri中无法带参数,只能放置在state中处理 + extra_param = {} if extra_param is None or not isinstance(extra_param, dict) else extra_param + extra_param["security_token"] = gen_oauth_state_security_token() + state = "&".join(["%s=%s" % (k, v) for k, v in list(extra_param.items()) if v is not None and v != ""]) + # 跳转到 google 登录的URL + google_oauth_login_url = "%s?%s" % ( + google_setting.GOOGLE_OAUTH_LOGIN_URL, + urllib.parse.urlencode( + { + "response_type": "code", + "client_id": google_setting.CLIENT_ID, + "redirect_uri": bk_settings.LOGIN_COMPLETE_URL, + "scope": "email", + "state": state, + } + ), + ) + return google_oauth_login_url, state + + +def get_access_token(code): + """ + 调用接口验证CODE,并获取access_token + """ + params = { + "grant_type": "authorization_code", + "code": code, + "redirect_uri": bk_settings.LOGIN_COMPLETE_URL, + "client_id": google_setting.CLIENT_ID, + "client_secret": google_setting.CLIENT_SECRET, + } + resp = requests.post(url=google_setting.ACCESS_TOKEN_URL, params=params) + if resp.status_code != 200: + # 记录错误日志 + content = resp.content[:100] if resp.content else "" + error_msg = ( + "http enterprise request error! type: %s, url: %s, data: %s, " + "response_status_code: %s, response_content: %s" + ) + logger.error(error_msg % ("POST", google_setting.ACCESS_TOKEN_URL, str(params), resp.status_code, content)) + return None + data = resp.json() + return data.get("access_token") + + +def get_scope_data(access_token): + """ + scope要求的数据 + """ + params = {"access_token": access_token} + resp = requests.get(google_setting.SCOPE_URL, params=params) + if resp.status_code != 200: + # 记录错误日志 + content = resp.content[:100] if resp.content else "" + error_msg = ( + "http enterprise request error! type: %s, url: %s, data: %s, " + "response_status_code: %s, response_content: %s" + ) + logger.error(error_msg % ("GET", google_setting.SCOPE_URL, str(params), resp.status_code, content)) + return None + data = resp.json() + userinfo = { + "username": data.get("email", ""), + "display_name": data.get("email", ""), + "email": data.get("email", ""), + "telephone": data.get("phone", ""), + } + return userinfo diff --git a/src/login/ee_official_login/oauth/google/views.py b/src/login/ee_official_login/oauth/google/views.py new file mode 100755 index 000000000..aa1f91055 --- /dev/null +++ b/src/login/ee_official_login/oauth/google/views.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" +import urllib.parse + +from bkauth import actions +from bkauth.constants import REDIRECT_FIELD_NAME +from common.log import logger +from django.contrib.auth import authenticate + +from .utils import gen_oauth_login_url + + +def login(request): + """ + 登录处理 + """ + # 获取用户实际请求的URL, 目前account.REDIRECT_FIELD_NAME = 'c_url' + redirect_to = request.GET.get(REDIRECT_FIELD_NAME, "") + # 获取用户实际访问的蓝鲸应用 + app_id = request.GET.get("app_id", "") + + # 来自注销 + is_from_logout = bool(request.GET.get("is_from_logout") or 0) + + # google登录回调后会自动添加code参数 + code = request.GET.get("code") + # 若没有code参数,则表示需要跳转到google登录 + if code is None or is_from_logout: + # 生成跳转到google登录的链接 + google_oauth_login_url, state = gen_oauth_login_url({"app_id": app_id, REDIRECT_FIELD_NAME: redirect_to}) + # 将state 设置于session,Oauth2.0特有的,防止csrf攻击的 + request.session["state"] = state + # 直接调用蓝鲸登录重定向方法 + response = actions.login_redirect_response(request, google_oauth_login_url, is_from_logout) + logger.debug( + "custom_login:oauth.google code is None or is_from_logout! code=%s, is_from_logout=%s", + code, + is_from_logout, + ) + return response + + # 已经有企业认证票据参数(如code参数),表示企业登录后的回调或企业认证票据还存在 + # oauth2.0 特有处理逻辑,防止csrf攻击 + # 处理state参数 + state = request.GET.get("state", "") + state_dict = dict(urllib.parse.parse_qsl(state)) + app_id = state_dict.get("app_id") + redirect_to = state_dict.get(REDIRECT_FIELD_NAME, "") + state_from_session = request.session.get("state") + # 校验state,防止csrf攻击 + if state != state_from_session: + logger.debug( + "custom_login:oauth.google state != state_from_session [state=%s, state_from_session=%s]", + state, + state_from_session, + ) + return actions.login_failed_response(request, redirect_to, app_id) + + # 验证用户登录是否OK + user = authenticate(code=code) + if user is None: + logger.debug("custom_login:oauth.google user is None, will redirect_to=%s", redirect_to) + # 直接调用蓝鲸登录失败处理方法 + return actions.login_failed_response(request, redirect_to, app_id) + # 成功,则调用蓝鲸登录成功的处理函数,并返回响应 + logger.debug("custom_login:oauth.google login success, will redirect_to=%s", redirect_to) + return actions.login_success_response(request, user, redirect_to, app_id) diff --git a/src/login/healthz/__init__.py b/src/login/healthz/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/healthz/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/healthz/urls.py b/src/login/healthz/urls.py new file mode 100755 index 000000000..5a9a849b1 --- /dev/null +++ b/src/login/healthz/urls.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from django.conf.urls import url +from healthz import views + +urlpatterns = [url("^$", views.healthz)] diff --git a/src/login/healthz/views.py b/src/login/healthz/views.py new file mode 100755 index 000000000..3d3dd6657 --- /dev/null +++ b/src/login/healthz/views.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +import os +from builtins import str + +from bkauth.decorators import login_exempt +from common.exceptions import LoginErrorCodes +from common.license import check_license +from django.conf import settings +from django.http import HttpResponse, JsonResponse +from django.utils.translation import ugettext as _ + +# ==================== helpers ========================= + +LOGIN_MODULE_CODE = "1302000" + + +def _gen_json_response(ok, code, message, data): + """ + ok: True/False + code: 平台 1302000 / 模块 1302100 / 具体错误 1302105 + message: 报错信息 + data: dict, 内容自定义 + """ + return JsonResponse({"ok": ok, "code:": code, "message": message, "data": data}, status=200) + + +def _gen_success_json_response(data): + """ + 成功 + """ + return _gen_json_response(ok=True, code=LOGIN_MODULE_CODE, message="OK", data=data) + + +def _gen_fail_json_response(code, message, data): + """ + 失败 + """ + return _gen_json_response(ok=False, code=code, message=message, data=data) + + +# ==================== check ========================= + + +def _check_settings(): + """ + check settings, 注意不暴露密码等敏感信息 + """ + # check settings, 注意不暴露密码等敏感信息 + try: + settings.ESB_TOKEN + { + "debug": settings.DEBUG, + "env": os.getenv("BK_ENV", "unknow"), + "cookie_domain": settings.BK_COOKIE_DOMAIN, + "mysql": { + "host": settings.DATABASES.get("default", {}).get("HOST"), + "port": settings.DATABASES.get("default", {}).get("PORT"), + "user": settings.DATABASES.get("default", {}).get("USER"), + "database": settings.DATABASES.get("default", {}).get("NAME"), + }, + } + except Exception as e: + return False, _(u"配置文件不正确, 缺失对应配置: %s") % str(e), LoginErrorCodes.E1302001_BASE_SETTINGS_ERROR + + return True, "ok", 0 + + +def _check_database(): + try: + from bkaccount.models import BkToken + + objs = BkToken.objects.all()[:3] + [o.token for o in objs] + except Exception as e: + return False, _(u"数据库连接存在问题: %s") % str(e), LoginErrorCodes.E1302002_BASE_DATABASE_ERROR + + return True, "ok", 0 + + +def _check_license(): + # check license + is_license_ok, message, valid_start_time, valid_end_time = check_license() + if not is_license_ok: + return False, _(u"企业证书无效:%s; 只影响桌面版本信息的展示") % message, LoginErrorCodes.E1302005_BASE_LICENSE_ERROR + + return True, "ok", 0 + + +@login_exempt +def healthz(request): + """ + health check + """ + data = {} + + # 强依赖 + _check_funcs = [ + ("settings", _check_settings), + ("database", _check_database), + # ("license", _check_license), + ] + + if settings.EDITION == "ee": + _check_funcs.append(("license", _check_license)) + + for name, func in _check_funcs: + is_health, message, code = func() + if is_health: + data[name] = "ok" + else: + return _gen_fail_json_response(code=code, message=message, data={}) + + return _gen_success_json_response(data) + + +@login_exempt +def ping(request): + return HttpResponse("pong", content_type="text/plain") diff --git a/src/login/metadata/__init__.py b/src/login/metadata/__init__.py new file mode 100755 index 000000000..1c6763228 --- /dev/null +++ b/src/login/metadata/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" diff --git a/src/login/metadata/urls.py b/src/login/metadata/urls.py new file mode 100755 index 000000000..ffb2f1290 --- /dev/null +++ b/src/login/metadata/urls.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from django.conf.urls import url +from metadata import views + +urlpatterns = [url("^website/$", views.website_metadata)] diff --git a/src/login/metadata/views.py b/src/login/metadata/views.py new file mode 100755 index 000000000..1378f7ef8 --- /dev/null +++ b/src/login/metadata/views.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + + +from bkauth.decorators import login_exempt +from django.shortcuts import render +from django.views.decorators.clickjacking import xframe_options_exempt + + +@xframe_options_exempt +@login_exempt +def website_metadata(request): + return render(request, "metadata/website.json") diff --git a/src/login/requirements.txt b/src/login/requirements.txt new file mode 100755 index 000000000..9bdfed9e1 --- /dev/null +++ b/src/login/requirements.txt @@ -0,0 +1,21 @@ +django==1.11.29 +django-braces==1.13.0 +dj-static==0.0.6 +pycrypto==2.6.1 +requests==2.21.0 +pymysql==0.6.7 +gunicorn==19.9.0 +xlrd==1.0.0 +xlwt==1.1.2 +gevent==21.1.2 +pytz==2016.6.1 +python-dateutil==2.8.1 +django_oauth_toolkit==0.12.0 +django-decorator-include==1.3 +cachetools==3.1.1 + +# for config +django-environ==0.4.5 +# prometheus metrics +django-prometheus==1.0.15 +future==0.18.2 \ No newline at end of file diff --git a/src/login/requirements_dev.txt b/src/login/requirements_dev.txt new file mode 100644 index 000000000..6e8a73c1e --- /dev/null +++ b/src/login/requirements_dev.txt @@ -0,0 +1,14 @@ +# for pre-commit +toml==0.10.1 +pyproject-flake8==0.0.1-alpha.2 +flake8-comprehensions==3.5.0 +pytest==6.2.2 +pytest-django==4.1.0 +django-dynamic-fixture==3.1.1 +converge==0.9.8 +mock==1.0.1 +black==21.7b0 +mypy==0.910 +# install extension stubs if missing +# types-requests==2.25.0 +isort==5.9.2 \ No newline at end of file diff --git a/src/login/settings.py b/src/login/settings.py new file mode 100755 index 000000000..c7c48ffc8 --- /dev/null +++ b/src/login/settings.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS +Community Edition) available. +Copyright (C) 2017-2018 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. +""" + +import os + +from conf.default import * # noqa + +""" +You can load different configurations depending on yourcurrent environment. + + This can be the following values: + + development + testing + production +""" + +ENVIRONMENT = os.environ.get("BK_ENV", "env") +# Inherit from environment specifics +conf_module = "conf.settings_%s" % ENVIRONMENT + +try: + module = __import__(conf_module, globals(), locals(), ["*"]) +except ImportError as e: + raise ImportError("Could not import conf '%s' (Is it on sys.path?): %s" % (conf_module, e)) + +for setting in dir(module): + if setting == setting.upper(): + locals()[setting] = getattr(module, setting) diff --git a/src/login/start.sh b/src/login/start.sh new file mode 100755 index 000000000..07284c533 --- /dev/null +++ b/src/login/start.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +export BK_ENV=env + +# python manage.py migrate + +command="gunicorn wsgi -w 16 --timeout 150 -b 0.0.0.0:5000 -k gevent --max-requests 1024 --access-logfile '-' --access-logformat '%(h)s %(l)s %(u)s %(t)s \"%(r)s\" %(s)s %(b)s \"%(f)s\" \"%(a)s\" in %(L)s seconds' --log-level INFO --log-file=-" + +## Run! +exec bash -c "$command" diff --git a/src/login/static/css/login.css b/src/login/static/css/login.css index 4628a1cbc..21bd7c0da 100755 --- a/src/login/static/css/login.css +++ b/src/login/static/css/login.css @@ -197,10 +197,6 @@ input[type=number]::-webkit-outer-spin-button { font-size: 12px } -.page-content .login-from .from-detail .is-danger-tip .icon-exclamation-circle-shape { - margin-right: 10px -} - .page-content .login-from .check-box { padding: 20px; text-align: right diff --git a/src/login/static/css/login.min.css b/src/login/static/css/login.min.css index 28f69a372..2293ac952 100755 --- a/src/login/static/css/login.min.css +++ b/src/login/static/css/login.min.css @@ -1,3 +1,3 @@ a,a:hover,button{text-decoration:none}.page-content,body,html{height:100%;position:relative}*{box-sizing:border-box}b,blockquote,body,button,dd,div,dl,dt,fieldset,form,h1,h2,h3,h4,h5,h6,i,input,li,ol,p,pre,span,td,textarea,th,ul{margin:0;padding:0}body,html{font-size:14px;font-family:"PingFang SC","Microsoft YaHei"}a,button{-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;transition:all .5s}li,ol,ul{list-style:none}h1,h2,h3,h4,h5,h6{font-weight:400}input::-webkit-input-placeholder{color:#c4c6cc}input:-moz-placeholder{color:#c4c6cc}input::-moz-placeholder{color:#c4c6cc}input:-ms-input-placeholder{color:#c4c6cc}textarea::-webkit-input-placeholder{color:#c4c6cc}textarea:-moz-placeholder{color:#c4c6cc}textarea::-moz-placeholder{color:#c4c6cc}textarea:-ms-input-placeholder{color:#c4c6cc}.pb110{padding-bottom:110px}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}.hide{display:none!important;visibility:hidden}#login-form{padding-top:42px}input[type=number]{-moz-appearance:textfield}input,input[disabled],select{background:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.page-content{width:100%;background:url(../img/logo/bg.png) center bottom no-repeat;margin:0 auto}.page-content .login-from{width:450px;min-height:452px;border-radius:2px;position:absolute;top:15%;left:50%;margin-left:-200px;z-index:100;overflow:visible}.page-content .login-from .logo-title{opacity:1;background:#fff;border-radius:8px 8px 0 0;box-shadow:0 2px 6px 0 rgba(0,0,0,0.10);padding:32px 36px}.page-content .login-from .logo-title .title{font-size:24px;font-weight:600;color:#313238}.page-content .login-from .logo-title img{vertical-align:text-bottom}.page-content .login-from .from-detail{position:relative;background:#fff;padding-bottom:44px;border-radius:0 0 8px 8px}.page-content .login-from .from-detail .is-danger-tip{position:absolute;color:#ea3636;top:11px;left:38px;font-size:12px}.page-content .login-from .from-detail .is-danger-tip .icon-exclamation-circle-shape{margin-right:10px}.page-content .login-from .check-box{padding:20px;text-align:right}.page-content .login-from .check-box .switch-btn{display:inline-block;width:52px;height:24px;border-radius:12px;overflow:hidden;background:url(../img/logo/button.png) -28px no-repeat;cursor:pointer;transition:.5s}.page-content .login-from .check-box .switch-btn.en{background-position:0 0}.page-content .login-from .form-login{width:100%;padding:0 38px}.page-content .login-from .form-login .label{color:#313238;margin-bottom:12px}.page-content .login-from .form-login .change-password{height:22px}.page-content .login-from .form-login .change-password span{color:#ea3636}.page-content .login-from .form-login .change-password a{color:#1768ef}.page-content .login-from .form-login.is-danger .group-control p{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input{border-color:#ff5656;color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input::-webkit-input-placeholder{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input:-moz-placeholder{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input::-moz-placeholder{color:#ff5656}.page-content .login-from .form-login.is-danger .group-control input:-ms-input-placeholder{color:#ff5656}.page-content .login-from .form-login.certificate-expired .group-control i{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input{border-color:#dde4eb;color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input:focus{border-color:#ff5656}.page-content .login-from .form-login.certificate-expired .group-control input::-webkit-input-placeholder{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input:-moz-placeholder{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input::-moz-placeholder{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .group-control input:-ms-input-placeholder{color:#cad3dc}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn{background:#9dafdd;cursor:not-allowed}.page-content .login-from .form-login.certificate-expired .btn-content .login-btn:hover{background:#9dafdd}.page-content .login-from .form-login .group-control{width:100%;height:40px;border-radius:2px;position:relative}.page-content .login-from .form-login .user{margin-bottom:32px}.page-content .login-from .form-login .group-control i{position:absolute;font-size:16px;top:12px;right:13px;color:#979ba5}.page-content .login-from .form-login .group-control i:hover{cursor:pointer}.page-content .login-from .form-login .group-control+.group-control{margin-top:15px} .page-content .login-from .form-login .group-control input{width:100%;height:100%;outline:0;border:1px solid #c4c6cc;padding:0 40px 0 12px;color:#737987;border-radius:2px}.page-content .login-from .form-login .action{margin-top:12px}.page-content .login-from .form-login .group-control input:focus{border-color:#3c96ff}.page-content .login-from .form-login .btn-content{font-size:0;padding-top:10px}.page-content .login-from .form-login .login-btn{width:100%;height:42px;display:inline-block;background-color:#5c7ac6;border-radius:2px;outline:0;border:0;font-size:14px;line-height:18px;letter-spacing:0;color:#fff;cursor:pointer;float:left}.page-content .login-from .form-login .login-btn:hover{background:#526eb5}.page-content .login-from .form-login .protocol-btn,.page-content .login-from .form-login .password-btn{font-size:14px;letter-spacing:0;color:#63656e;display:inline-block;cursor:pointer;float:right}.page-content .login-from .form-login .protocol-btn:hover,.page-content .login-from .form-login .password-btn:hover{color:#1768ef}.language-switcher{text-align:right;margin-top:13px}.language-switcher a img{height:24px}.footer{width:100%;height:148px;position:absolute;bottom:0;padding-top:80px;color:#73787e;font-size:12px;background:url(../img/logo/bg_footer.png) center center no-repeat}.footer .footer-menu{text-align:center}.footer .footer-menu a{color:#73787e}.footer .footer-menu p{line-height:24px}.protocol-pop{position:fixed;top:0;left:0;width:100%;height:100%;display:none;z-index:101}.protocol-pop .protocol-detail{width:1200px;height:700px;background-color:#fff;border-radius:2px;top:10%;left:50%;margin-left:-600px;position:absolute;padding:59px 23px 40px 37px}.protocol-pop .protocol-detail .del-text{position:absolute;top:0;right:0;width:27px;height:27px;line-height:26px;text-align:center;margin:4px 4px 0 0;border-radius:50%;background-repeat:no-repeat;background-size:11px 11px;background-position:50% 50%;cursor:pointer;display:inline-block}.protocol-pop .protocol-detail .del-text:hover{background-color:#f3f3f3}.protocol-pop .protocol-detail .del-text>i{font-size:10px;color:#50525f;font-weight:700}.protocol-pop .protocol-detail .detail-content{height:536px;overflow-y:auto}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar{width:6px;height:5px}.protocol-pop .protocol-detail .detail-content::-webkit-scrollbar-thumb{border-radius:20px;background:#a5a5a5;box-shadow:inset 0 0 6px rgba(204,204,204,.3)}.protocol-pop .protocol-detail .detail-content>.title{text-align:center;font-size:32px;font-weight:400;font-stretch:normal;line-height:36px;letter-spacing:1px;color:#4f515e;position:relative;margin-bottom:67px}.protocol-pop .protocol-detail .detail-content>.title:after{content:'';position:absolute;width:30px;height:2px;background:#5c7ac6;top:46px;left:50%;margin-left:-15px}.protocol-pop .protocol-detail .detail-content .detail-list{padding-right:23px}.protocol-pop .protocol-detail .detail-content .detail-list>.title{font-weight:700}.protocol-pop .protocol-detail .detail-content .detail-list P{text-align:left;font-size:12px;line-height:32px;letter-spacing:0;color:#7b7d8a}.protocol-pop .protocol-detail .consent-content{text-align:center;margin-top:25px}.protocol-pop .protocol-detail .consent-content .consent-btn{width:160px;height:42px;display:inline-block;background-color:#5c7ac6;border-radius:2px;border:0;font-size:16px;font-weight:400;font-stretch:normal;line-height:18px;letter-spacing:0;color:#fff}.protocol-pop .protocol-detail .consent-content .consent-btn:hover{background:#526eb5}.error-message-content{position:fixed;top:0;width:100%;height:40px;line-height:40px;text-align:center;display:none}.error-message-content i{cursor:pointer;color:#ff5656;display:inline-block}.error-message-content.is-chrome{background:#f8f6db}.error-message-content.is-certificate{background:#fbd9d9;color:#ff5656}.error-message-content span{color:#ff5656;display:inline-block;margin-right:20px}@media screen and (max-width:1680px){.page-content .login-from{top:10%}.protocol-pop .protocol-detail{top:5%}}@media screen and (max-height:786px) and (max-width:1366px){.page-content{width:100%;background:url(../img/logo/md_bg.png) center bottom no-repeat}.footer{padding-top:64px;height:126px;background:url(../img/logo/md_bg_footer.png) center center no-repeat}.protocol-pop .protocol-detail{height:600px}.protocol-pop .protocol-detail .detail-content{height:420px}.page-content .login-from{top:3%}.protocol-pop .protocol-detail{top:5%}}@media screen and (max-width:1280px){.protocol-pop .protocol-detail{height:600px}.protocol-pop .protocol-detail .detail-content{height:420px}.page-content{background:url(../img/logo/1280_bg.png) center bottom no-repeat}.page-content .login-from,.protocol-pop .protocol-detail{top:5%}}@media screen and (min-width:1921px){.page-content{background:url(../img/logo/big_bg.png) center bottom no-repeat}.footer{padding-top:100px;height:171px;background:url(../img/logo/big_bg_footer.png) center center no-repeat} -}@media screen and (min-height:1080px){.page-content{background:url(../img/logo/big_bg.png) center bottom no-repeat}}@media screen and (max-width:400px) and (max-height:400px){.page-content{background:0}.page-content .login-from,.protocol-pop .protocol-detail{top:0}.page-content .login-from .language-switcher{display:none}}.user-domain-list{position:absolute;border-radius:2px;background:#fff;border:1px solid #eee;width:100%;z-index:1000;margin-top:5px;box-shadow:0 0 5px 0 rgba(0,0,0,.1);display:none;font-size:13px}.user-domain-list:before{content:'请选择登录域';font-size:12px;padding:10px 20px;display:block}.user-domain-list li{line-height:44px;padding:0 20px;text-overflow:ellipsis;overflow:hidden;white-space:normal;cursor:pointer}.user-domain-list li strong{font-weight:normal;color:#4d66c6}.user-domain-list li.selected{background:rgba(105,157,244,.1)!important}.user-domain-list li:hover{background:#fefafa;color:#4d66c6} \ No newline at end of file +}@media screen and (min-height:1080px){.page-content{background:url(../img/logo/big_bg.png) center bottom no-repeat}}@media screen and (max-width:400px) and (max-height:400px){.page-content{background:0}.page-content .login-from,.protocol-pop .protocol-detail{top:0}.page-content .login-from .language-switcher{display:none}}.user-domain-list{position:absolute;border-radius:2px;background:#fff;border:1px solid #eee;width:100%;z-index:1000;margin-top:5px;box-shadow:0 0 5px 0 rgba(0,0,0,.1);display:none;font-size:13px}.user-domain-list:before{content:'请选择登录域';font-size:12px;padding:10px 20px;display:block}.user-domain-list li{line-height:44px;padding:0 20px;text-overflow:ellipsis;overflow:hidden;white-space:normal;cursor:pointer}.user-domain-list li strong{font-weight:normal;color:#4d66c6}.user-domain-list li.selected{background:rgba(105,157,244,.1)!important}.user-domain-list li:hover{background:#fefafa;color:#4d66c6} diff --git a/src/login/templates/401.html b/src/login/templates/401.html new file mode 100755 index 000000000..a15ef69bf --- /dev/null +++ b/src/login/templates/401.html @@ -0,0 +1,25 @@ +{% load i18n %} + + + + +{% trans '未登录蓝鲸智云平台(401页)' %} + + + + + + + + + + diff --git a/src/login/templates/403.html b/src/login/templates/403.html new file mode 100755 index 000000000..88925036a --- /dev/null +++ b/src/login/templates/403.html @@ -0,0 +1,24 @@ +{% load i18n %} + + + + +{% trans '您没有访问权限(403页)' %} + + + + + + + + + + diff --git a/src/login/templates/404.html b/src/login/templates/404.html new file mode 100755 index 000000000..0581ce225 --- /dev/null +++ b/src/login/templates/404.html @@ -0,0 +1,23 @@ +{% load i18n %} + + + + +{% trans '页面找不到(404页)' %} + + + + + + +
+ +

{% trans '页面找不到了' %}

+
+ + diff --git a/src/login/templates/500.html b/src/login/templates/500.html new file mode 100755 index 000000000..a10df0b4c --- /dev/null +++ b/src/login/templates/500.html @@ -0,0 +1,24 @@ +{% load i18n %} + + + + +{% trans '系统异常(500页)' %} + + + + + + +
+ +

{% trans '系统出现异常' %}

+

{% trans '努力恢复中,请稍后再试......' %}

+
+ + diff --git a/src/login/templates/50x.html b/src/login/templates/50x.html new file mode 100755 index 000000000..d171d5646 --- /dev/null +++ b/src/login/templates/50x.html @@ -0,0 +1,23 @@ +{% load i18n %} + + + + +{% trans '服务故障,努力修复中...' %} + + + + + +
+ +

{% trans '服务故障,努力修复中...' %}

+

{% trans '服务出现故障,我们正在紧急修复,给您带来不便,敬请谅解。' %}

+
+ + diff --git a/src/login/templates/account/agreement.part b/src/login/templates/account/agreement.part new file mode 100755 index 000000000..ca7038899 --- /dev/null +++ b/src/login/templates/account/agreement.part @@ -0,0 +1,127 @@ +{% load i18n %} +
+
+ +
+ {% blocktrans trimmed %} +
腾讯蓝鲸智云软件许可及服务协议
+
+

【首部及导言】

+

欢迎您使用腾讯蓝鲸智云软件及服务。

+

为使用腾讯蓝鲸智云软件(以下简称“本软件”)及服务,您应当阅读并遵守《腾讯蓝鲸智云软件许可及服务协议》(以下简称“本协议”),以及《腾讯服务协议》。请您务必审慎阅读、充分理解各条款内容,特别是免除或者限制责任的条款,以及开通或使用某项服务的单独协议,并选择接受或不接受。限制、免责条款可能以加粗形式提示您注意。

+

除非您已阅读并接受本协议所有条款,否则您无权下载、安装或使用本软件及相关服务。您的下载、安装、使用、登录等行为即视为您已阅读并同意上述协议的约束。

+

一、【协议的范围】

+

1.1【协议适用主体范围】

+

本协议是您与腾讯之间关于您下载、安装、使用、复制本软件,以及使用腾讯相关服务所订立的协议。

+

1.2【协议关系及冲突条款】

+

本协议被视为《腾讯服务协议》(链接地址:http://www.qq.com/contract.shtml,若链接地址变更的,则以变更后的链接地址所对应的内容为准;其他链接地址变更的情形,均适用前述约定。)的补充协议,是其不可分割的组成部分,与其构成统一整体。本协议与上述内容存在冲突的,以本协议为准。

+

本协议内容同时包括腾讯可能不断发布的关于本服务的相关协议、业务规则等内容。上述内容一经正式发布,即为本协议不可分割的组成部分,您同样应当遵守。

+

二、【关于本服务】

+

2.1【本服务的内容】

+

本服务内容是指蓝鲸智云产品以及相关服务,包括但不限于提供的基础运维平台(如“配置平台”、“作业平台”、“管控平台”等),PaaS服务(如“AppEngine”、“开发者中心”、“应用开发框架”、“组件”、“前端Magicbox”等),SaaS服务(如监控告警、持续集成、持续部署、辅助运营等),以及支撑上述服务的其他相关产品,为用户提供完善的基础服务设施,以使用户快速、便捷的创建、部署和管理应用的软件许可及服务(以下简称“本服务”)。

+

2.2 【本服务的形式】

+

您使用本服务需要下载腾讯蓝鲸智云产品软件,对于这些软件,腾讯给予您一项个人的、不可转让及非排他性的许可。您仅可为访问或使用本服务的目的而使用这些软件及服务。

+

2.3 【本服务许可的范围】

+

2.3.1 腾讯给予您一项不可转让及非排他性的许可,以使用本软件。您可以为非商业目的在单一台终端设备上安装、使用、显示、运行本软件。

+

2.3.2 您可以为使用本软件及服务的目的复制本软件的一个副本,仅用作备份。备份副本必须包含原软件中含有的所有著作权信息。

+

2.3.3 本条及本协议其他条款未明示授权的其他一切权利仍由腾讯保留,您在行使这些权利时须另外取得腾讯的书面许可。腾讯如果未行使前述任何权利,并不构成对该权利的放弃。

+

三、【软件的获取】

+

3.1 您可以直接从腾讯的网站上获取本软件,也可以从得到腾讯授权的第三方获取。

+

3.2 如果您从未经腾讯授权的第三方获取本软件或与本软件名称相同的安装程序,腾讯无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。

+

四、【软件的安装与卸载】

+

4.1 腾讯可能为不同的需求开发了不同的软件版本,您应当根据实际情况选择下载合适的版本进行安装。

+

4.2 下载安装程序后,您需要按照该程序提示的步骤正确安装。

+

4.3 为提供更加优质、安全的服务,在本软件安装时腾讯可能推荐您安装其他软件,您可以选择安装或不安装。

+

4.4 如果您不再需要使用本软件或者需要安装新版软件,可以自行卸载。如果您愿意帮助腾讯改进产品服务,请告知卸载的原因。

+

五、【软件的更新】

+

5.1 为了改善用户体验、完善服务内容,腾讯将不断努力开发新的服务,并为您不时提供软件更新(这些更新可能会采取软件替换、修改、功能强化、版本升级等形式)。

+

5.2 为了保证本软件及服务的安全性和功能的一致性,腾讯有权不经向您特别通知而对软件进行更新,或者对软件的部分功能效果进行改变或限制。

+

5.3 本软件新版本发布后,旧版本的软件可能无法使用。腾讯不保证旧版本软件继续可用及相应的客户服务,请您随时核对并下载最新版本。

+

六、【用户个人信息保护】

+

6.1保护用户个人信息是腾讯的一项基本原则。腾讯将按照本协议及《隐私政策》(链接地址:http://www.qq.com/privacy.htm)的规定收集、使用、储存和分享您的个人信息。本协议对个人信息保护规定的内容与上述《隐私政策》有相冲突的,及本协议对个人信息保护相关内容未作明确规定的,均应以《隐私政策》的内容为准。

+

6.2腾讯将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可腾讯不会向第三方公开、透露用户个人信息。腾讯对相关信息采用专业加密存储与传输方式,保障用户个人信息的安全。

+

6.3 您在注册帐号或使用本服务的过程中,可能需要提供一些必要的信息,若国家法律法规或政策有特殊规定的,您需要提供真实的身份信息。基于某些产品功能,腾讯会需要您提供使用该功能的非关联用户身份的相关信息,您同意腾讯基于上述目的收集上述信息,若您不提供或提供的信息不完整,则无法使用本服务或在使用过程中受到限制。

+

七、【主权利义务条款】

+

7.1 【帐号使用规范】

+

7.1.1 用户有责任妥善保管注册帐户信息及帐户密码的安全,用户需要对注册帐户以及密码下的行为承担法律责任。用户同意在任何情况下不向他人透露帐户及密码信息。在您怀疑他人在使用您的帐号时,请您及时处理。

+

7.1.2 管理员账号使用者,则可以创建多个账号,所创建的所有账号,由创建的人对账号承担保密的责任。

+

7.1.3 非管理员账号使用者,应妥善保管账号密码的安全,若存在密码修改等事宜,应求助所创建账号的管理员。

+

7.1.4 腾讯蓝鲸智云产品的账户密码安全,均由软件使用者承担相关法律责任。

+

7.2【用户注意事项】

+

7.2.1 您理解并同意:为了向您提供有效的服务,本软件会利用您终端的处理器和带宽等资源。本软件使用过程中可能产生数据流量的费用,用户需自行向运营商了解相关资费信息,并自行承担相关费用。

+

7.2.2 您理解并同意:

+

7.2.2.1 您在本软件的应用市场中添加的第三方软件,由第三方享有一切合法权利。因第三方软件引发的任何纠纷,由该第三方负责解决,腾讯不承担任何责任。 腾讯不对第三方软件或技术提供客服支持,若用户需要获取支持,请与该软件或技术提供商联系。

+

7.2.2.2 本软件对涉及到的第三方软件的安装信息及升级信息等一切信息的安全性、合法性、兼容性、无害性等不承担任何担保及保证,由此而产生的任何法律纠纷,由该第三方负责解决,腾讯不承担任何责任。

+

7.2.2.3 本软件所涉及到的任何第三方软件,其一切法律权利归第三方权利人所享有,用户下载、安装、使用第三方软件受该软件许可协议所约束。在第三方软件使用过程中所产生的任何纠纷,均由该第三方负责解决,腾讯不承担任何责任。

+

7.2.2.4 本软件供用户用来下载、安装腾讯和/或第三方软件,并不能识别用户利用本软件下载、安装的第三方软件是否有合法来源。若您为有关软件的权利人,不愿本软件为您的软件提供用户下载、安装、使用的服务,可按本协议约定的联系方式联系我们(联系邮箱:【*】,联系电话:【*】),我们将会积极配合进行处理。

+

7.2.3 您在使用本软件某一特定服务时,该服务可能会另有单独的协议、相关业务规则等(以下统称为“单独协议”),您在使用该项服务前请阅读并同意相关的单独协议。

+

7.2.4 您理解并同意腾讯将会尽其商业上的合理努力保障您在本软件及服务中的数据存储安全,但是,腾讯并不能就此提供完全保证,包括但不限于以下情形:

+

7.2.4.1 腾讯不对您在本软件及服务中相关数据的删除或储存失败负责;

+

7.2.4.2 腾讯有权根据实际情况自行决定单个用户在本软件及服务中数据的最长储存期限,并在服务器上为其分配数据最大存储空间等。您可根据自己的需要自行备份本软件及服务中的相关数据;

+

7.2.4.3 如果您停止使用本软件及服务或服务被终止或取消,腾讯可以从服务器上永久地删除您的数据。服务停止、终止或取消后,腾讯没有义务向您返还任何数据。

+

7.3【第三方产品和服务】

+

7.3.1 您在本软件的应用市场中添加第三方提供的产品或服务时,除遵守本协议约定外,还应遵守第三方的用户协议。腾讯和第三方对可能出现的纠纷在法律规定和约定的范围内各自承担责任。

+

7.3.2 腾讯不保证您在应用市场中添加的第三方产品或服务的安全性、准确性、有效性及其他不确定的风险,由此若引发的任何争议及损害,与腾讯无关,腾讯不承担任何责任。

+

八、【用户行为规范】

+

8.1【信息内容规范】

+

8.1.1 本条所述信息内容是指用户使用本软件及服务过程中所制作、复制、发布、传播的任何内容。

+

8.1.2 您理解并同意,腾讯蓝鲸智云一直致力于为用户提供完善的基础服务设施,您不得利用本软件及服务制作、复制、发布、传播如下干扰腾讯蓝鲸智云正常运营,以及侵犯其他用户或第三方合法权益的内容,包括但不限于:

+

8.1.2.1 发布、传送、传播、储存违反国家法律、危害国家安全统一、社会稳定、公序良俗、社会公德以及侮辱、诽谤、淫秽或含有任何性或性暗示的、暴力的内容;

+

8.1.2.2 发布、传送、传播、储存侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的内容;

+

8.1.2.3 涉及他人隐私、个人信息或资料的;

+

8.1.2.4 发表、传送、传播骚扰、广告信息及垃圾信息;

+

8.1.2.5 其他违反法律法规、政策及公序良俗、社会公德或干扰【腾讯蓝鲸智云】正常运营和侵犯其他用户或第三方合法权益内容的信息。

+

8.2【软件使用规范】

+

除非法律允许或腾讯书面许可,您使用本软件过程中不得从事下列行为:

+

8.2.1 删除本软件及其副本上关于著作权的信息;

+

8.2.2 对本软件进行反向工程、反向汇编、反向编译,或者以其他方式尝试发现本软件的源代码;

+

8.2.3 对腾讯拥有知识产权的内容进行使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版、建立镜像站点等;

+

8.2.4 对本软件或者本软件运行过程中释放到任何终端内存中的数据、软件运行过程中客户端与服务器端的交互数据,以及本软件运行所必需的系统数据,进行复制、修改、 增加、删除、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非腾讯经授权的第三方工具/服务接入本软件和相关系统;

+

8.2.5 通过修改或伪造软件运行中的指令、数据,增加、删减、变动软件的功能或运行效果,或者将用于上述用途的软件、方法进行运营或向公众传播,无论这些行为是否为商业目的;

+

8.2.6 通过非腾讯开发、授权的第三方软件、插件、外挂、系统,登录或使用腾讯软件及服务,或制作、发布、传播上述工具;

+

8.2.7 自行或者授权他人、第三方软件对本软件及其组件、模块、数据进行干扰;

+

8.2.8 其他未经腾讯明示授权的行为。

+

8.3【服务运营规范】

+

除非法律允许或腾讯书面许可,您使用本服务过程中不得从事下列行为:

+

8.3.1 提交、发布虚假信息,或冒充、利用他人名义的;

+

8.3.2 诱导其他用户点击链接页面或分享信息的;

+

8.3.3 虚构事实、隐瞒真相以误导、欺骗他人的;

+

8.3.4 侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的;

+

8.3.5 未经腾讯书面许可利用帐号和任何功能,以及第三方运营平台进行推广或互相推广的;

+

8.3.6 利用帐号或本软件及服务从事任何违法犯罪活动的;

+

8.3.7 制作、发布与以上行为相关的方法、工具,或对此类方法、工具进行运营或传播,无论这些行为是否为商业目的;

+

8.3.8 其他违反法律法规规定、侵犯其他用户合法权益、干扰产品正常运营或腾讯未明示授权的行为。

+

8.4 【对自己行为负责】

+

您充分了解并同意,您必须为自己注册帐号下的一切行为负责,包括您所发表的任何内容以及由此产生的任何后果。您应对本服务中的内容自行加以判断,并承担因使用内容而引起的所有风险,包括因对内容的正确性、完整性或实用性的依赖而产生的风险。腾讯无法且不会对因前述风险而导致的任何损失或损害承担责任。

+

8.5【违约处理】

+

8.5.1 如果腾讯发现或收到他人举报或投诉用户违反本协议约定的,腾讯有权不经通知随时对相关内容进行删除,并视行为情节对违规帐号处以包括但不限于警告、限制或禁止使用全部或部分功能、帐号封禁直至注销的处罚,并公告处理结果。

+

8.5.2 您理解并同意,腾讯有权依合理判断对违反有关法律法规或本协议规定的行为进行处罚,对违法违规的任何用户采取适当的法律行动,并依据法律法规保存有关信息向有关部门报告等,用户应独自承担由此而产生的一切法律责任。

+

8.5.3 您理解并同意,因您违反本协议或相关服务条款的规定,导致或产生第三方主张的任何索赔、要求或损失,您应当独立承担责任;腾讯因此遭受损失的,您也应当一并赔偿。

+

九、【知识产权声明】

+

9.1 腾讯是本软件的知识产权权利人。本软件的一切著作权、商标权、专利权、商业秘密等知识产权,以及与本软件相关的所有信息内容(包括但不限于文字、图片、音频、视频、图表、界面设计、版面框架、有关数据或电子文档等)均受中华人民共和国法律法规和相应的国际条约保护,腾讯享有上述知识产权。

+

9.2 未经腾讯书面同意,您不得为任何商业或非商业目的自行或许可任何第三方实施、利用、转让上述知识产权,腾讯保留追究上述行为法律责任的权利。

+

十、【终端安全责任】

+

10.1 您理解并同意,本软件同大多数互联网软件一样,可能会受多种因素影响,包括但不限于用户原因、网络服务质量、社会环境等;也可能会受各种安全问题的侵扰,包括但不限于他人非法利用用户资料,进行现实中的骚扰;用户下载安装的其他软件或访问的其他网站中可能含有病毒、木马程序或其他恶意程序,威胁您的终端设备信息和数据安全,继而影响本软件的正常使用等。因此,您应加强信息安全及个人信息的保护意识,注意密码保护,以免遭受损失。

+

10.2 您不得制作、发布、使用、传播用于窃取其他用户帐号及个人信息、财产的恶意程序。

+

10.3 维护软件安全与正常使用是腾讯和您的共同责任,腾讯将按照行业标准合理审慎地采取必要技术措施保护您的终端设备信息和数据安全,但是您承认和同意腾讯并不能就此提供完全保证。

+

十一、【第三方软件或技术】

+

11.1 本软件可能会使用第三方软件或技术(包括本软件可能使用的开源代码和公共领域代码等,下同),这种使用已经获得合法授权。

+

11.2 本软件如果使用了第三方的软件或技术,腾讯将按照相关法规或约定,对相关的协议或其他文件,可能通过本协议附件、在本软件安装包特定文件夹中打包等形式进行展示,它们可能会以“软件使用许可协议”、“授权协议”、“开源代码许可证”或其他形式来表达。前述通过各种形式展现的相关协议或其他文件,均是本协议不可分割的组成部分,与本协议具有同等的法律效力,您应当遵守这些要求。如果您没有遵守这些要求,该第三方或者国家机关可能会对您提起诉讼、罚款或采取其他制裁措施,并要求腾讯给予协助,您应当自行承担法律责任。

+

11.3 如因本软件使用的第三方软件或技术引发的任何纠纷,应由该第三方负责解决,腾讯不承担任何责任。腾讯不对第三方软件或技术提供客服支持,若您需要获取支持,请与第三方联系。

+

十二、【其他】

+

12.1 您使用本软件即视为您已阅读并同意受本协议的约束。腾讯有权在必要时修改本协议条款。您可以在本软件的最新版本中查阅相关协议条款。本协议条款变更后,如果您继续使用本软件,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本软件。

+

12.2 本协议签订地为中华人民共和国广东省深圳市南山区。

+

12.3 本协议的成立、生效、履行、解释及纠纷解决,适用中华人民共和国大陆地区法律(不包括冲突法)。

+

12.4 若您和腾讯之间发生任何纠纷或争议,首先应友好协商解决;协商不成的,您同意将纠纷或争议提交本协议签订地有管辖权的人民法院管辖。

+

12.5 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。

+

12.6 本协议条款无论因何种原因部分无效或不可执行,其余条款仍有效,对双方具有约束力。

+

12.7 本协议可能由多种语言书就。如果存在中文版本与其他语言的版本相冲突的地方,以中文版本为准。(正文完)

+

腾讯公司

+
+ {% endblocktrans %} +
+ +
+
\ No newline at end of file diff --git a/src/login/templates/account/base.html b/src/login/templates/account/base.html new file mode 100755 index 000000000..6af0109ba --- /dev/null +++ b/src/login/templates/account/base.html @@ -0,0 +1,175 @@ +{% load i18n %} + + + + + + {% trans '用户管理|蓝鲸智云企业版' %} + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ {% block body_content %}{% endblock %} +
+ + + +
+ + + + + + + + + + + + + + + + + + + + {% block script %}{% endblock %} + + diff --git a/src/login/templates/account/login.html b/src/login/templates/account/login.html new file mode 100755 index 000000000..d010ac6d8 --- /dev/null +++ b/src/login/templates/account/login.html @@ -0,0 +1,115 @@ +{% load i18n %} + + + + + + + + + {% trans '登录|蓝鲸智云企业版' %} + + +
+ + {% endif %} +
+ +
+ + + {% if "/plain/" not in APP_PATH %} + + {% endif %} +
+ + {% include "account/agreement.part" %} + +
+ {% trans '您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果' %} +
+ +
+ {% trans '企业证书校验无效,请联系系统管理员处理' %} +
+ + + + + + + diff --git a/src/login/templates/account/login_ce.html b/src/login/templates/account/login_ce.html new file mode 100755 index 000000000..e2ef81620 --- /dev/null +++ b/src/login/templates/account/login_ce.html @@ -0,0 +1,103 @@ +{% load i18n %} + + + + + + + + + 登录|蓝鲸智云 + + {% if is_plain %} + + {% endif %} + + +
+ + +
+ + {% include "account/agreement.part" %} + +
+ 您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果 +
+ + + + + + + diff --git a/src/login/templates/account/login_ce_i18n.html b/src/login/templates/account/login_ce_i18n.html new file mode 100755 index 000000000..32d7ae1a1 --- /dev/null +++ b/src/login/templates/account/login_ce_i18n.html @@ -0,0 +1,77 @@ +{% load i18n %} + + + + + + + + + {% trans '登录|蓝鲸智云' %} + + +
+ + +
+ + {% include "account/agreement.part" %} + +
+ {% trans '您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果' %} +
+ + + + + + + diff --git a/src/login/templates/account/no_right.html b/src/login/templates/account/no_right.html new file mode 100755 index 000000000..ce1bc6059 --- /dev/null +++ b/src/login/templates/account/no_right.html @@ -0,0 +1,8 @@ +{% extends "account/base.html" %} +{% load i18n %} +{% block body_content %} +
+
{% trans '你不是管理员, 没有用户管理的权限!' %}
+
{% trans '请找管理员申请权限!' %}
+
+{% endblock %} \ No newline at end of file diff --git a/src/login/templates/account/user_table.part b/src/login/templates/account/user_table.part new file mode 100755 index 000000000..b9d932039 --- /dev/null +++ b/src/login/templates/account/user_table.part @@ -0,0 +1,116 @@ +{% load i18n %} + + + + + + + + + + + + + + {% if records %} + {% for obj in records %} + + + + + + + + + {% endfor %} + {% else %} + + {% endif %} + +
{% trans '用户名' %}{% trans '中文名' %}{% trans '联系电话' %}{% trans '常用邮箱' %}{% trans '角色' %}{% trans '操作' %}
+ + + + + + + + + {% if request.user.is_superuser %} + + {% else %} + + {% endif %} + + + + + + {% if request.user.is_superuser %} + + {% endif %} +
{% trans '没有数据' %}
+ +
+ + +
diff --git a/src/login/templates/account/users.html b/src/login/templates/account/users.html new file mode 100755 index 000000000..049d69ade --- /dev/null +++ b/src/login/templates/account/users.html @@ -0,0 +1,102 @@ +{% extends "account/base.html" %} +{% load i18n %} +{% block body_content %} + - - {% include "account/agreement.part" %} - -
- {% trans '您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果' %} -
- -
- {% trans '企业证书校验无效,请联系系统管理员处理' %} -
- - - - - - - diff --git a/src/login/templates/account/login_ce.html b/src/login/templates/account/login_ce.html deleted file mode 100755 index e2ef81620..000000000 --- a/src/login/templates/account/login_ce.html +++ /dev/null @@ -1,103 +0,0 @@ -{% load i18n %} - - - - - - - - - 登录|蓝鲸智云 - - {% if is_plain %} - - {% endif %} - - -
- - -
- - {% include "account/agreement.part" %} - -
- 您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果 -
- - - - - - - diff --git a/src/login/templates/account/login_ce_i18n.html b/src/login/templates/account/login_ce_i18n.html deleted file mode 100755 index 32d7ae1a1..000000000 --- a/src/login/templates/account/login_ce_i18n.html +++ /dev/null @@ -1,77 +0,0 @@ -{% load i18n %} - - - - - - - - - {% trans '登录|蓝鲸智云' %} - - -
- - -
- - {% include "account/agreement.part" %} - -
- {% trans '您的浏览器非Chrome,建议您使用最新版本的Chrome浏览,以保证最好的体验效果' %} -
- - - - - - - diff --git a/src/login/templates/account/no_right.html b/src/login/templates/account/no_right.html deleted file mode 100755 index ce1bc6059..000000000 --- a/src/login/templates/account/no_right.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n %} -{% block body_content %} -
-
{% trans '你不是管理员, 没有用户管理的权限!' %}
-
{% trans '请找管理员申请权限!' %}
-
-{% endblock %} \ No newline at end of file diff --git a/src/login/templates/account/user_table.part b/src/login/templates/account/user_table.part deleted file mode 100755 index b9d932039..000000000 --- a/src/login/templates/account/user_table.part +++ /dev/null @@ -1,116 +0,0 @@ -{% load i18n %} - - - - - - - - - - - - - - {% if records %} - {% for obj in records %} - - - - - - - - - {% endfor %} - {% else %} - - {% endif %} - -
{% trans '用户名' %}{% trans '中文名' %}{% trans '联系电话' %}{% trans '常用邮箱' %}{% trans '角色' %}{% trans '操作' %}
- - - - - - - - - {% if request.user.is_superuser %} - - {% else %} - - {% endif %} - - - - - - {% if request.user.is_superuser %} - - {% endif %} -
{% trans '没有数据' %}
- -
- - -
diff --git a/src/login/templates/account/users.html b/src/login/templates/account/users.html deleted file mode 100755 index 049d69ade..000000000 --- a/src/login/templates/account/users.html +++ /dev/null @@ -1,102 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n %} -{% block body_content %} -