Skip to content

Commit 4c8b463

Browse files
feat(X-Pack): Add CAS Authentication Mechanism
1 parent dc4595b commit 4c8b463

37 files changed

+2808
-20
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""048_authentication_ddl
2+
3+
Revision ID: 073bf544b373
4+
Revises: c1b794a961ce
5+
Create Date: 2025-10-30 14:11:29.786938
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '073bf544b373'
14+
down_revision = 'c1b794a961ce'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('sys_authentication',
22+
sa.Column('id', sa.BigInteger(), nullable=False),
23+
sa.Column('name', sa.String(255), nullable=False),
24+
sa.Column('type', sa.Integer(), nullable=False),
25+
sa.Column('config', sa.Text(), nullable=True),
26+
sa.Column('enable', sa.Boolean(), nullable=False),
27+
sa.Column('valid', sa.Boolean(), nullable=False),
28+
sa.Column('create_time', sa.BigInteger(), nullable=False),
29+
sa.PrimaryKeyConstraint('id')
30+
)
31+
op.create_index(op.f('ix_sys_authentication_id'), 'sys_authentication', ['id'], unique=False)
32+
33+
# ### end Alembic commands ###
34+
35+
36+
def downgrade():
37+
# ### commands auto generated by Alembic - please adjust! ###
38+
op.drop_index(op.f('ix_sys_authentication_id'), table_name='sys_authentication')
39+
op.drop_table('sys_authentication')
40+
# ### end Alembic commands ###
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""049_user_platform_ddl
2+
3+
Revision ID: b58a71ca6ae3
4+
Revises: 073bf544b373
5+
Create Date: 2025-11-04 12:31:56.481582
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'b58a71ca6ae3'
14+
down_revision = '073bf544b373'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('sys_user_platform',
22+
sa.Column('uid', sa.BigInteger(), nullable=False),
23+
sa.Column('origin', sa.Integer(), server_default='0', nullable=False),
24+
sa.Column('platform_uid', sa.String(255), nullable=False),
25+
sa.Column('id', sa.BigInteger(), nullable=False),
26+
sa.PrimaryKeyConstraint('id')
27+
)
28+
op.create_index(op.f('ix_sys_user_platform_id'), 'sys_user_platform', ['id'], unique=False)
29+
30+
op.add_column('sys_user', sa.Column('origin', sa.Integer(), server_default='0', nullable=False))
31+
32+
# ### end Alembic commands ###
33+
34+
35+
def downgrade():
36+
# ### commands auto generated by Alembic - please adjust! ###
37+
38+
op.drop_column('sys_user', 'origin')
39+
40+
op.drop_index(op.f('ix_sys_user_platform_id'), table_name='sys_user_platform')
41+
op.drop_table('sys_user_platform')
42+
# ### end Alembic commands ###

backend/apps/system/api/login.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Annotated
2-
from fastapi import APIRouter, Depends, HTTPException
2+
from fastapi import APIRouter, Depends, HTTPException, Request
33
from fastapi.security import OAuth2PasswordRequestForm
4+
from apps.system.schemas.logout_schema import LogoutSchema
45
from apps.system.schemas.system_schema import BaseUserDTO
56
from common.core.deps import SessionDep, Trans
67
from common.utils.crypto import sqlbot_decrypt
@@ -9,6 +10,7 @@
910
from datetime import timedelta
1011
from common.core.config import settings
1112
from common.core.schemas import Token
13+
from sqlbot_xpack.authentication.manage import logout as xpack_logout
1214
router = APIRouter(tags=["login"], prefix="/login")
1315

1416
@router.post("/access-token")
@@ -30,4 +32,10 @@ async def local_login(
3032
user_dict = user.to_dict()
3133
return Token(access_token=create_access_token(
3234
user_dict, expires_delta=access_token_expires
33-
))
35+
))
36+
37+
@router.post("/logout")
38+
async def logout(session: SessionDep, request: Request, dto: LogoutSchema):
39+
if dto.origin != 0:
40+
return await xpack_logout(session, request, dto)
41+
return None

backend/apps/system/models/system_model.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,16 @@ class AssistantBaseModel(SQLModel):
5555

5656
class AssistantModel(SnowflakeBase, AssistantBaseModel, table=True):
5757
__tablename__ = "sys_assistant"
58-
58+
59+
60+
class AuthenticationBaseModel(SQLModel):
61+
name: str = Field(max_length=255, nullable=False)
62+
type: int = Field(nullable=False, default=0)
63+
config: Optional[str] = Field(sa_type = Text(), nullable=True)
64+
65+
66+
class AuthenticationModel(SnowflakeBase, AuthenticationBaseModel, table=True):
67+
__tablename__ = "sys_authentication"
68+
create_time: Optional[int] = Field(default=0, sa_type=BigInteger())
69+
enable: bool = Field(default=False, nullable=False)
70+
valid: bool = Field(default=False, nullable=False)

backend/apps/system/models/user.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,20 @@ class BaseUserPO(SQLModel):
1515
password: str = Field(default_factory=default_md5_pwd, max_length=255)
1616
email: str = Field(max_length=255)
1717
status: int = Field(default=0, nullable=False)
18+
origin: int = Field(nullable=False, default=0)
1819
create_time: int = Field(default_factory=get_timestamp, sa_type=BigInteger(), nullable=False)
1920
language: str = Field(max_length=255, default="zh-CN")
2021

2122
class UserModel(SnowflakeBase, BaseUserPO, table=True):
2223
__tablename__ = "sys_user"
2324

25+
26+
class UserPlatformBase(SQLModel):
27+
uid: int = Field(nullable=False, sa_type=BigInteger())
28+
origin: int = Field(nullable=False, default=0)
29+
platform_uid: str = Field(max_length=255, nullable=False)
30+
31+
class UserPlatformModel(SnowflakeBase, UserPlatformBase, table=True):
32+
__tablename__ = "sys_user_platform"
33+
34+

backend/apps/system/schemas/auth.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
from typing import Optional
23
from pydantic import BaseModel
34
from enum import Enum
45

@@ -16,4 +17,5 @@ class CacheName(Enum):
1617
ASSISTANT_INFO = "assistant:info"
1718
ASSISTANT_DS = "assistant:ds"
1819
def __str__(self):
19-
return self.value
20+
return self.value
21+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import Optional
2+
from pydantic import BaseModel
3+
4+
5+
class LogoutSchema(BaseModel):
6+
token: Optional[str] = None
7+
flag: Optional[str] = 'default'
8+
origin: Optional[int] = 0
9+
data: Optional[str] = None

backend/apps/system/schemas/system_schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class UserCreator(BaseUser):
5353
name: str = Field(min_length=1, max_length=100, description="用户名")
5454
email: str = Field(min_length=1, max_length=100, description="用户邮箱")
5555
status: int = 1
56+
origin: Optional[int] = 0
5657
oid_list: Optional[list[int]] = None
5758

5859
""" @field_validator("email")
@@ -70,7 +71,7 @@ class UserGrid(UserEditor):
7071
create_time: int
7172
language: str = "zh-CN"
7273
# space_name: Optional[str] = None
73-
origin: str = ''
74+
# origin: str = ''
7475

7576

7677
class PwdEditor(BaseModel):

backend/common/utils/http_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Tuple
2+
import requests
3+
from urllib.parse import urlparse
4+
from requests.exceptions import RequestException, Timeout
5+
6+
def verify_url(url: str, timeout: int = 5) -> Tuple[bool, str]:
7+
try:
8+
parsed = urlparse(url)
9+
if not all([parsed.scheme, parsed.netloc]):
10+
return False, "无效的 URL 格式"
11+
12+
if parsed.scheme not in ['http', 'https']:
13+
return False, "URL 必须以 http 或 https 开头"
14+
15+
response = requests.get(
16+
url,
17+
timeout=timeout,
18+
verify=False # 忽略 SSL 证书验证
19+
)
20+
21+
if response.status_code < 400:
22+
return True, "URL 可达"
23+
else:
24+
return False, f"服务器返回错误状态码: {response.status_code}"
25+
26+
except Timeout:
27+
return False, f"连接超时 (>{timeout}秒)"
28+
except RequestException as e:
29+
return False, f"连接失败: {str(e)}"
30+
except Exception as e:
31+
return False, f"验证过程发生错误: {str(e)}"

backend/common/utils/whitelist.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
"/system/assistant/info/*",
3434
"/system/assistant/app/*",
3535
"/system/assistant/picture/*",
36-
"/datasource/uploadExcel"
36+
"/datasource/uploadExcel",
37+
"/system/authentication/platform/status",
38+
"/system/authentication/login/*",
39+
"/system/authentication/sso/*",
3740
]
3841

3942
class WhitelistChecker:

0 commit comments

Comments
 (0)