Skip to content

Commit c223980

Browse files
committed
feat: Implement core infrastructure, authentication, models, GitLab, Jenkins, SonarQube, and Zentao plugins, along with comprehensive documentation and tests.
1 parent 99d45f0 commit c223980

51 files changed

Lines changed: 1537 additions & 2865 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

devops_collector/auth/router.py

Lines changed: 52 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
"""TODO: Add module description."""
1+
"""认证模块路由。
2+
3+
处理用户注册、登录、获取当前用户信息以及 GitLab OAuth 绑定。
4+
"""
25
from datetime import timedelta, datetime
36
from typing import Optional
47
from fastapi import APIRouter, Depends, HTTPException, status, Request
@@ -10,28 +13,19 @@
1013
from devops_collector.models.base_models import User, UserOAuthToken
1114
from devops_collector.auth.database import SessionLocal
1215
from devops_collector.config import Config
16+
1317
router = APIRouter(prefix='/auth', tags=['Authentication'])
1418

1519
def get_db():
16-
'''"""TODO: Add description.
17-
18-
Args:
19-
TODO
20-
21-
Returns:
22-
TODO
23-
24-
Raises:
25-
TODO
26-
"""'''
20+
"""获取数据库会话的依赖项。"""
2721
db = SessionLocal()
2822
try:
2923
yield db
3024
finally:
3125
db.close()
3226

3327
@router.get('/gitlab/bind')
34-
async def bind_gitlab(request: Request, token: str=Depends(services.oauth2_scheme), db: Session=Depends(get_db)):
28+
async def bind_gitlab(request: Request, token: str = Depends(services.oauth2_scheme), db: Session = Depends(get_db)):
3529
"""发起 GitLab OAuth 绑定。"""
3630
if not Config.GITLAB_CLIENT_ID or not Config.GITLAB_REDIRECT_URI:
3731
raise HTTPException(500, 'GitLab OAuth not configured')
@@ -43,24 +37,46 @@ async def bind_gitlab(request: Request, token: str=Depends(services.oauth2_schem
4337
raise HTTPException(401, 'User not found')
4438
except Exception:
4539
raise HTTPException(401, 'Invalid token')
40+
4641
state = str(current_user.global_user_id)
47-
auth_url = f'{Config.GITLAB_URL}/oauth/authorize?client_id={Config.GITLAB_CLIENT_ID}&redirect_uri={Config.GITLAB_REDIRECT_URI}&response_type=code&scope=api&state={state}'
42+
auth_url = (
43+
f'{Config.GITLAB_URL}/oauth/authorize?'
44+
f'client_id={Config.GITLAB_CLIENT_ID}&'
45+
f'redirect_uri={Config.GITLAB_REDIRECT_URI}&'
46+
f'response_type=code&scope=api&state={state}'
47+
)
4848
return RedirectResponse(auth_url)
4949

5050
@router.get('/gitlab/callback')
51-
async def gitlab_callback(code: str, state: str=None, db: Session=Depends(get_db)):
51+
async def gitlab_callback(code: str, state: str = None, db: Session = Depends(get_db)):
5252
"""GitLab OAuth 回调处理。"""
5353
async with httpx.AsyncClient() as client:
54-
resp = await client.post(f'{Config.GITLAB_URL}/oauth/token', data={'client_id': Config.GITLAB_CLIENT_ID, 'client_secret': Config.GITLAB_CLIENT_SECRET, 'code': code, 'grant_type': 'authorization_code', 'redirect_uri': Config.GITLAB_REDIRECT_URI})
54+
resp = await client.post(
55+
f'{Config.GITLAB_URL}/oauth/token',
56+
data={
57+
'client_id': Config.GITLAB_CLIENT_ID,
58+
'client_secret': Config.GITLAB_CLIENT_SECRET,
59+
'code': code,
60+
'grant_type': 'authorization_code',
61+
'redirect_uri': Config.GITLAB_REDIRECT_URI
62+
}
63+
)
5564
if resp.status_code != 200:
5665
raise HTTPException(400, f'GitLab Auth Failed: {resp.text}')
5766
token_data = resp.json()
67+
5868
user_id = state
5969
if not user_id:
6070
raise HTTPException(400, 'Invalid State')
71+
6172
token_rec = db.query(UserOAuthToken).filter_by(user_id=user_id, provider='gitlab').first()
6273
if not token_rec:
63-
token_rec = UserOAuthToken(user_id=user_id, provider='gitlab', access_token=token_data['access_token'], token_type=token_data.get('token_type', 'Bearer'))
74+
token_rec = UserOAuthToken(
75+
user_id=user_id,
76+
provider='gitlab',
77+
access_token=token_data['access_token'],
78+
token_type=token_data.get('token_type', 'Bearer')
79+
)
6480
db.add(token_rec)
6581
else:
6682
token_rec.access_token = token_data['access_token']
@@ -69,70 +85,46 @@ async def gitlab_callback(code: str, state: str=None, db: Session=Depends(get_db
6985
return RedirectResponse(url='/static/iteration.html?bind_success=true')
7086

7187
@router.post('/register', response_model=schemas.UserResponse)
72-
def register(user: schemas.UserRegisterRequest, db: Session=Depends(get_db)):
73-
'''"""TODO: Add description.
74-
75-
Args:
76-
user: TODO
77-
db: TODO
78-
79-
Returns:
80-
TODO
81-
82-
Raises:
83-
TODO
84-
"""'''
88+
def register(user: schemas.UserRegisterRequest, db: Session = Depends(get_db)):
89+
"""注册新用户。"""
8590
db_user = services.get_user_by_email(db, email=user.email)
8691
if db_user:
8792
raise HTTPException(status_code=400, detail='Email already registered')
8893
return services.create_user(db=db, user_data=user)
8994

9095
@router.post('/login', response_model=schemas.Token)
91-
def login_for_access_token(form_data: OAuth2PasswordRequestForm=Depends(), db: Session=Depends(get_db)):
92-
'''"""TODO: Add description.
93-
94-
Args:
95-
form_data: TODO
96-
db: TODO
97-
98-
Returns:
99-
TODO
100-
101-
Raises:
102-
TODO
103-
"""'''
96+
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
97+
"""登录获取访问令牌。"""
10498
user = services.authenticate_user(db, form_data.username, form_data.password)
10599
if not user:
106-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Incorrect username or password', headers={'WWW-Authenticate': 'Bearer'})
100+
raise HTTPException(
101+
status_code=status.HTTP_401_UNAUTHORIZED,
102+
detail='Incorrect username or password',
103+
headers={'WWW-Authenticate': 'Bearer'}
104+
)
107105
access_token_expires = timedelta(minutes=services.ACCESS_TOKEN_EXPIRE_MINUTES)
108-
access_token = services.create_access_token(data={'sub': user.primary_email, 'user_id': str(user.global_user_id)}, expires_delta=access_token_expires)
106+
access_token = services.create_access_token(
107+
data={'sub': user.primary_email, 'user_id': str(user.global_user_id)},
108+
expires_delta=access_token_expires
109+
)
109110
return {'access_token': access_token, 'token_type': 'bearer'}
110111

111112
@router.get('/me', response_model=schemas.UserResponse)
112-
def read_users_me(token: str=Depends(services.oauth2_scheme), db: Session=Depends(get_db)):
113-
'''"""TODO: Add description.
114-
115-
Args:
116-
token: TODO
117-
db: TODO
118-
119-
Returns:
120-
TODO
121-
122-
Raises:
123-
TODO
124-
"""'''
113+
def read_users_me(token: str = Depends(services.oauth2_scheme), db: Session = Depends(get_db)):
114+
"""获取当前登录用户信息。"""
125115
try:
126116
payload = services.jwt.decode(token, services.SECRET_KEY, algorithms=[services.ALGORITHM])
127117
email: str = payload.get('sub')
128118
if email is None:
129119
raise HTTPException(status_code=401, detail='Invalid token')
130120
except Exception:
131121
raise HTTPException(status_code=401, detail='Invalid token')
122+
132123
user = services.get_user_by_email(db, email=email)
133124
if user is None:
134125
raise HTTPException(status_code=401, detail='User not found')
135-
token = db.query(UserOAuthToken).filter_by(user_id=user.global_user_id, provider='gitlab').first()
126+
127+
token_obj = db.query(UserOAuthToken).filter_by(user_id=user.global_user_id, provider='gitlab').first()
136128
resp = schemas.UserResponse.from_orm(user)
137-
resp.gitlab_connected = True if token else False
129+
resp.gitlab_connected = True if token_obj else False
138130
return resp

devops_collector/auth/services.py

Lines changed: 37 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""TODO: Add module description."""
2-
from datetime import datetime, timedelta
3-
from typing import Optional
1+
import uuid
2+
import logging
3+
from datetime import datetime, timedelta, timezone
4+
from typing import Optional, Any, Union
45
from jose import JWTError, jwt
56
from passlib.context import CryptContext
67
from sqlalchemy.orm import Session
@@ -9,95 +10,54 @@
910
from devops_collector.models.base_models import User, UserCredential
1011
from devops_collector.config import Config
1112
from uuid import UUID
13+
1214
SECRET_KEY = getattr(Config, 'SECRET_KEY', 'your-secret-key-keep-it-secret')
1315
ALGORITHM = 'HS256'
1416
ACCESS_TOKEN_EXPIRE_MINUTES = 30
1517
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
1618
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='auth/login')
1719

18-
def verify_password(plain_password, hashed_password):
19-
'''"""TODO: Add description.
20-
21-
Args:
22-
plain_password: TODO
23-
hashed_password: TODO
24-
25-
Returns:
26-
TODO
27-
28-
Raises:
29-
TODO
30-
"""'''
20+
def verify_password(plain_password: str, hashed_password: str) -> bool:
21+
"""验证明文密码是否与哈希密码匹配。"""
3122
return pwd_context.verify(plain_password, hashed_password)
3223

33-
def get_password_hash(password):
34-
'''"""TODO: Add description.
35-
36-
Args:
37-
password: TODO
38-
39-
Returns:
40-
TODO
41-
42-
Raises:
43-
TODO
44-
"""'''
24+
def get_password_hash(password: str) -> str:
25+
"""生成密码的 BCRPYT 哈希。"""
4526
return pwd_context.hash(password)
4627

47-
def create_access_token(data: dict, expires_delta: Optional[timedelta]=None):
48-
'''"""TODO: Add description.
49-
50-
Args:
51-
data: TODO
52-
expires_delta: TODO
53-
54-
Returns:
55-
TODO
56-
57-
Raises:
58-
TODO
59-
"""'''
28+
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
29+
"""生成 JWT 访问令牌。"""
6030
to_encode = data.copy()
6131
if expires_delta:
62-
expire = datetime.utcnow() + expires_delta
32+
expire = datetime.now(timezone.utc) + expires_delta
6333
else:
64-
expire = datetime.utcnow() + timedelta(minutes=15)
34+
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
35+
36+
to_encode.update({"exp": expire})
6537
for k, v in to_encode.items():
66-
if isinstance(v, UUID):
38+
if isinstance(v, (UUID, uuid.UUID)):
6739
to_encode[k] = str(v)
6840
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
6941
return encoded_jwt
7042

71-
def get_user_by_email(db: Session, email: str):
72-
'''"""TODO: Add description.
73-
74-
Args:
75-
db: TODO
76-
email: TODO
77-
78-
Returns:
79-
TODO
80-
81-
Raises:
82-
TODO
83-
"""'''
43+
def get_user_by_email(db: Session, email: str) -> Optional[User]:
44+
"""根据主邮箱获取当前有效用户。"""
8445
return db.query(User).filter(User.primary_email == email, User.is_current == True).first()
8546

86-
def create_user(db: Session, user_data):
87-
'''"""TODO: Add description.
88-
89-
Args:
90-
db: TODO
91-
user_data: TODO
92-
93-
Returns:
94-
TODO
95-
96-
Raises:
97-
TODO
98-
"""'''
47+
def create_user(db: Session, user_data: Any) -> User:
48+
"""创建新用户及其认证凭据。"""
9949
hashed_password = get_password_hash(user_data.password)
100-
db_user = User(global_user_id=uuid.uuid4(), primary_email=user_data.email, full_name=user_data.full_name, employee_id=user_data.employee_id, is_active=True, is_survivor=True, sync_version=1, is_current=True, is_deleted=False)
50+
db_user = User(
51+
global_user_id=uuid.uuid4(),
52+
primary_email=user_data.email,
53+
full_name=user_data.full_name,
54+
employee_id=user_data.employee_id,
55+
is_active=True,
56+
is_survivor=True,
57+
sync_version=1,
58+
is_current=True,
59+
is_deleted=False
60+
)
10161
db.add(db_user)
10262
db.flush()
10363
db_cred = UserCredential(user_id=db_user.global_user_id, password_hash=hashed_password)
@@ -106,20 +66,8 @@ def create_user(db: Session, user_data):
10666
db.refresh(db_user)
10767
return db_user
10868

109-
def authenticate_user(db: Session, email: str, password: str):
110-
'''"""TODO: Add description.
111-
112-
Args:
113-
db: TODO
114-
email: TODO
115-
password: TODO
116-
117-
Returns:
118-
TODO
119-
120-
Raises:
121-
TODO
122-
"""'''
69+
def authenticate_user(db: Session, email: str, password: str) -> Union[User, bool]:
70+
"""验证用户凭据并返回用户对象。"""
12371
user = get_user_by_email(db, email)
12472
if not user:
12573
return False
@@ -129,18 +77,8 @@ def authenticate_user(db: Session, email: str, password: str):
12977
return False
13078
return user
13179

132-
def get_current_user(token: str=Depends(oauth2_scheme)):
133-
'''"""TODO: Add description.
134-
135-
Args:
136-
token: TODO
137-
138-
Returns:
139-
TODO
140-
141-
Raises:
142-
TODO
143-
"""'''
80+
def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
81+
"""FastAPI 依赖项:从令牌中获取当前认证用户。"""
14482
from devops_collector.auth.database import SessionLocal
14583
auth_db = SessionLocal()
14684
try:
@@ -158,18 +96,8 @@ def get_current_user(token: str=Depends(oauth2_scheme)):
15896
finally:
15997
auth_db.close()
16098

161-
def get_current_active_user(current_user: User=Depends(get_current_user)):
162-
'''"""TODO: Add description.
163-
164-
Args:
165-
current_user: TODO
166-
167-
Returns:
168-
TODO
169-
170-
Raises:
171-
TODO
172-
"""'''
99+
def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
100+
"""FastAPI 依赖项:验证当前用户是否处于激活状态。"""
173101
if not current_user.is_active:
174102
raise HTTPException(status_code=400, detail='Inactive user')
175103
return current_user

0 commit comments

Comments
 (0)