Skip to content

Commit

Permalink
feat(bk-login): support local login
Browse files Browse the repository at this point in the history
  • Loading branch information
nannan00 committed Oct 16, 2023
1 parent cf327c3 commit 0d9c37e
Show file tree
Hide file tree
Showing 60 changed files with 6,179 additions and 25 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/bk-user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: create idp plugin symbolic link
run: |
ln -s $(pwd)/src/idp-plugins/idp_plugin $(pwd)/src/bk-login/bklogin
ln -s $(pwd)/src/idp-plugins/idp_plugin $(pwd)/src/bk-user/bkuser
- name: Format with black
run: |
pip install black==23.7.0 click==8.1.6
Expand Down Expand Up @@ -52,6 +56,10 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: create idp plugin symbolic link
run: |
ln -s $(pwd)/src/idp-plugins/idp_plugin $(pwd)/src/bk-login/bklogin
ln -s $(pwd)/src/idp-plugins/idp_plugin $(pwd)/src/bk-user/bkuser
- name: Set up Poetry
uses: abatilo/[email protected]
with:
Expand Down
50 changes: 48 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,41 @@ repos:
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
exclude: ^(src/pages/|src/bk-login/pages)
- id: trailing-whitespace
exclude: ^(src/pages/|src/bk-login/pages)
- repo: local
hooks:
- id: Name check
name: Check sensitive info of name
verbose: true
language: system
entry: bash -c "if [[ -d pre_commit_hooks ]]; then pre_commit_hooks/rtx.sh $@; fi"
exclude: ^(.*/\.env|.*/poetry\.lock|.*/pyproject\.toml|src/bk-user/logs|src/pages/node_modules/)
exclude: >
(?x)^(
.*/\.env|
.*/poetry\.lock|
.*/pyproject\.toml|
src/bk-user/logs|
src/pages/node_modules/
)$
- id: IP check
name: Check sensitive info of ip
verbose: true
language: system
exclude: ^(.*/\.env|.*/poetry\.lock|.*/pyproject\.toml|\.github/workflows/bk-user.yml|src/bk-user/logs|src/pages/node_modules/)
exclude: >
(?x)^(
.*/\.env|
.*/poetry\.lock|
.*/pyproject\.toml|
\.github/workflows/bk-user.yml|
src/bk-user/logs|src/pages/node_modules/|
src/bk-login/pages/src/views/components/protocol.vue|
src/pages/static/bk_icon_font/iconcool.json|
src/pages/static/bk_icon_font/iconcool.js|
src/pages/paas-server/index.js|
src/bk-login/pages/paas-server/index.js
)$
entry: bash -c "if [[ -d pre_commit_hooks ]]; then pre_commit_hooks/ip.sh $@; fi"
- repo: local
hooks:
Expand All @@ -51,3 +72,28 @@ repos:
require_serial: true
language: system
entry: bash -c "cd src/bk-user && lint-imports"
- repo: local
hooks:
- id: black
name: black
language: python
types: [python]
entry: black --config=src/bk-login/pyproject.toml
files: src/bk-login/
- id: ruff
name: ruff
language: python
types: [python]
entry: ruff --config=src/bk-login/pyproject.toml --force-exclude --fix
files: src/bk-login/
- id: mypy
name: mypy
language: python
types: [python]
entry: mypy --config-file=src/bk-login/pyproject.toml
files: src/bk-login/
- id: import-linter
name: import-linter
require_serial: true
language: system
entry: bash -c "cd src/bk-login && lint-imports"
39 changes: 39 additions & 0 deletions src/bk-login/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
FROM node:18.17.1-bullseye-slim AS StaticBuilding
ENV NPM_VERSION 9.6.7

COPY src/bk-login/pages /
WORKDIR /
RUN npm install
RUN npm run build

FROM python:3.10.12-slim-bullseye
USER root

RUN rm /etc/apt/sources.list && \
echo "deb https://mirrors.tencent.com/debian bullseye main" >> /etc/apt/sources.list && \
echo "deb https://mirrors.tencent.com/debian-security bullseye-security main" >> /etc/apt/sources.list && \
echo "deb https://mirrors.tencent.com/debian bullseye-updates main" >> /etc/apt/sources.list

RUN mkdir ~/.pip && printf '[global]\nindex-url = https://mirrors.tencent.com/pypi/simple/' > ~/.pip/pip.conf

RUN apt-get update && apt-get install -y default-libmysqlclient-dev build-essential pkg-config

ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8

RUN pip install --upgrade pip setuptools
RUN pip install poetry==1.5.1

WORKDIR /app
COPY src/bk-login/pyproject.toml /app
COPY src/bk-login/poetry.lock /app
RUN poetry config virtualenvs.create false && poetry install --only main

COPY src/bk-login/bklogin /app/bklogin
COPY src/bk-login/bin /app/bin
COPY src/bk-login/manage.py /app

COPY --from=StaticBuilding /dist /app/staticfiles
COPY --from=StaticBuilding /dist/index.html /app/templates/index.html

CMD ["bash", "/app/bin/start.sh"]
4 changes: 4 additions & 0 deletions src/bk-login/bin/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

LISTEN_PORT="${PORT:=8000}"
gunicorn bklogin.wsgi -w 8 --threads 2 --max-requests 1024 --max-requests-jitter 50 --worker-class gevent -b [::]:$LISTEN_PORT --access-logfile - --error-logfile - --access-logformat '[%(h)s] %({request_id}i)s %(u)s %(t)s "%(r)s" %(s)s %(D)s %(b)s "%(f)s" "%(a)s"'
10 changes: 10 additions & 0 deletions src/bk-login/bklogin/authentication/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 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.
"""
16 changes: 16 additions & 0 deletions src/bk-login/bklogin/authentication/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 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.apps import AppConfig


class AuthenticationConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "bklogin.authentication"
15 changes: 15 additions & 0 deletions src/bk-login/bklogin/authentication/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 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"

SIGN_IN_TENANT_ID_SESSION_KEY = "sign_in_tenant_id"

SUPPORT_SIGN_IN_TENANT_USER_IDS_SESSION_KEY = "support_sign_in_tenant_user_ids"
159 changes: 159 additions & 0 deletions src/bk-login/bklogin/authentication/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 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
import logging
import random
import string
import time
from typing import Tuple
from urllib.parse import unquote

from blue_krill.encrypt.handler import EncryptHandler
from django.conf import settings
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.translation import gettext_lazy as _

from .models import BkToken

logger = logging.getLogger(__name__)


class BkTokenProcessor:
def __init__(self, encrypt_secret_key: bytes):
# Token加密密钥
self.encrypt_secret_key = encrypt_secret_key

@staticmethod
def _salt(length: int = 8) -> str:
"""生成长度为length 的随机字符串"""
allow_chars = string.ascii_letters + string.digits
return "".join([random.choice(allow_chars) for __ in range(length)])

def generate(self, username: str, expires: int) -> str:
"""token生成"""
# 加盐
plain_token = "%s|%s|%s" % (expires, username, self._salt())

# 加密
return EncryptHandler(secret_key=self.encrypt_secret_key).encrypt(plain_token)

def parse(self, bk_token: str) -> Tuple[str, int]:
"""token解析"""
try:
plain_bk_token = EncryptHandler(secret_key=self.encrypt_secret_key).decrypt(bk_token)
except Exception:
logger.exception("参数[%s] 解析失败", bk_token)
plain_bk_token = ""

error_msg = _("参数 bk_token 非法")
if not plain_bk_token:
raise ValueError(error_msg)

try:
token_info = plain_bk_token.split("|")
except Exception:
logger.exception("分割 bk_token[%s] 失败", bk_token)
raise ValueError(error_msg)

if not token_info or len(token_info) < 3: # noqa: PLR2004
raise ValueError(error_msg)

return token_info[1], int(token_info[0])


class BkTokenManager:
def __init__(self):
# Token加密密钥
self.bk_token_processor = BkTokenProcessor(encrypt_secret_key=force_bytes(settings.ENCRYPT_SECRET_KEY))
# Token 过期间隔
self.cookie_age = settings.BK_TOKEN_COOKIE_AGE
# Token 无操作失效间隔
self.inactive_age = settings.BK_TOKEN_INACTIVE_AGE
# Token 校验时间允许误差
self.offset_error_age = settings.BK_TOKEN_OFFSET_ERROR_AGE

# Token生成失败的重试次数
self.allowed_retry_count = 5

def get_bk_token(self, username: str) -> Tuple[str, datetime.datetime]:
"""
生成用户的登录态
"""
bk_token = ""
expire_time = int(time.time())
# 重试5次
retry_count = 0
while not bk_token and retry_count < self.allowed_retry_count:
now_time = int(time.time())
# Token过期时间
expire_time = now_time + self.cookie_age
# Token 无操作失效时间
inactive_expire_time = now_time + self.inactive_age
# 生成bk_token
bk_token = self.bk_token_processor.generate(username, expire_time)
# DB记录
try:
BkToken.objects.create(token=bk_token, inactive_expire_time=inactive_expire_time)
except Exception: # noqa: PERF203
logger.exception("Login ticket failed to be saved during ticket generation")
# 循环结束前将bk_token置空后重新生成
bk_token = "" if retry_count + 1 < self.allowed_retry_count else bk_token
retry_count += 1

return bk_token, datetime.datetime.fromtimestamp(expire_time, timezone.get_current_timezone())

def is_bk_token_valid(self, bk_token: str) -> Tuple[bool, str, str]:
"""
验证用户登录态
"""
if not bk_token:
return False, "", _("参数 bk_token 缺失")

bk_token = unquote(bk_token)
# 解析bk_token获取username和过期时间
try:
username, expire_time = self.bk_token_processor.parse(bk_token)
except ValueError as error:
return False, "", str(error)

# 检查DB是存在
try:
bk_token_obj = BkToken.objects.get(token=bk_token)
is_logout = bk_token_obj.is_logout
inactive_expire_time = bk_token_obj.inactive_expire_time
except Exception:
return False, "", _("不存在 bk_token[%s] 的记录").format(bk_token)

# token已注销
if is_logout:
return False, "", _("登录态已注销")

now_time = int(time.time())
# token有效期已过
if now_time > expire_time + self.offset_error_age:
return False, "", _("登录态已过期")

# token有效期大于当前时间的有效期
if expire_time - now_time > self.cookie_age + self.offset_error_age:
return False, "", _("登录态有效期不合法")

# token 无操作有效期已过
if now_time > inactive_expire_time + self.inactive_age:
return False, "", _("长时间无操作,登录态已过期")

# 更新 无操作有效期
try:
BkToken.objects.filter(token=bk_token).update(inactive_expire_time=now_time + self.inactive_age)
except Exception:
logger.exception("update inactive_expire_time fail")

return True, username, ""
23 changes: 23 additions & 0 deletions src/bk-login/bklogin/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.21 on 2023-09-27 02:34

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='BkToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='登录票据')),
('is_logout', models.BooleanField(default=False, verbose_name='票据是否已经执行过退出登录操作')),
('inactive_expire_time', models.IntegerField(default=0, verbose_name='无操作失效时间戳')),
],
),
]
Empty file.
23 changes: 23 additions & 0 deletions src/bk-login/bklogin/authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 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.db import models


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)
Loading

0 comments on commit 0d9c37e

Please sign in to comment.