Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pingcap/tidb/parser v0.0.0-20230922051344-241e8464cde0
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.7.3
github.com/rfyiamcool/cronlib v1.2.1
Expand Down Expand Up @@ -156,6 +157,7 @@ require (
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I=
github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
Expand Down Expand Up @@ -913,6 +915,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type CustomTheme struct {

type SecuritySettings struct {
TokenExpirationTime int64 `json:"token_expiration_time" bson:"token_expiration_time"`
MFAEnabled bool `json:"mfa_enabled" bson:"mfa_enabled"`
}

type PrivacySettings struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ func (c *SystemSettingColl) UpdateConcurrencySetting(workflowConcurrency, buildC
return err
}

func (c *SystemSettingColl) UpdateSecuritySetting(tokenExpirationTime int64) error {
func (c *SystemSettingColl) UpdateSecuritySetting(tokenExpirationTime int64, mfaEnabled bool) error {
id, _ := primitive.ObjectIDFromHex(setting.LocalClusterID)
change := bson.M{"$set": bson.M{
"security.token_expiration_time": tokenExpirationTime,
"security.mfa_enabled": mfaEnabled,
}}
query := bson.M{"_id": id}
_, err := c.UpdateOne(context.TODO(), query, change)
Expand Down Expand Up @@ -138,7 +139,7 @@ func (c *SystemSettingColl) InitSystemSettings() error {
},
},
Privacy: &models.PrivacySettings{ImprovementPlan: true},
Security: &models.SecuritySettings{TokenExpirationTime: 24},
Security: &models.SecuritySettings{TokenExpirationTime: 24, MFAEnabled: false},
})
}
return nil
Expand Down
24 changes: 14 additions & 10 deletions pkg/microservice/aslan/core/system/handler/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ import (

"github.com/koderover/zadig/v2/pkg/microservice/aslan/core/system/service"
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
"github.com/koderover/zadig/v2/pkg/tool/log"
)

// @Summary 更新安全与隐私设置
// @Description 更新系统安全与隐私设置,包括 token 过期时间、MFA 开关和改进计划开关
// @Tags system
// @Accept json
// @Produce json
// @Param body body service.SecurityAndPrivacySettings true "body"
// @Success 200
// @Router /api/aslan/system/security [post]
func CreateOrUpdateSecuritySettings(c *gin.Context) {
ctx, err := internalhandler.NewContextWithAuthorization(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
Expand All @@ -43,14 +50,16 @@ func CreateOrUpdateSecuritySettings(c *gin.Context) {

data, err := c.GetRawData()
if err != nil {
log.Errorf("upsert security settings GetRawData err : %s", err)
ctx.RespErr = fmt.Errorf("upsert security settings get raw data err: %w", err)
return
Comment thread
PetrusZ marked this conversation as resolved.
}
if err = json.Unmarshal(data, args); err != nil {
log.Errorf("upsert security settings Unmarshal err : %s", err)
ctx.RespErr = fmt.Errorf("upsert security settings unmarshal err: %w", err)
return
}

detail := fmt.Sprintf("token expiration: %d \n improvement plan: %v", args.TokenExpirationTime, args.ImprovementPlan)
detailEn := fmt.Sprintf("Token Expiration: %d \n Improvement Plan: %v", args.TokenExpirationTime, args.ImprovementPlan)
detail := fmt.Sprintf("token expiration: %d \n mfa enabled: %v \n improvement plan: %v", args.TokenExpirationTime, args.MFAEnabled, args.ImprovementPlan)
detailEn := fmt.Sprintf("Token Expiration: %d \n MFA Enabled: %v \n Improvement Plan: %v", args.TokenExpirationTime, args.MFAEnabled, args.ImprovementPlan)
internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "安全与隐私", detail, detailEn, string(data), types.RequestBodyTypeJSON, ctx.Logger)

// authorization checks
Expand All @@ -59,11 +68,6 @@ func CreateOrUpdateSecuritySettings(c *gin.Context) {
return
}

if err != nil {
ctx.RespErr = fmt.Errorf("failed to update sonar integration: %s", err)
return
}

if args.TokenExpirationTime > 8640 {
ctx.RespErr = errors.New("token expiration time cannot be greater than 8640 hour")
return
Expand Down
40 changes: 39 additions & 1 deletion pkg/microservice/aslan/core/system/service/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ limitations under the License.
package service

import (
"encoding/json"
"time"

configbase "github.com/koderover/zadig/v2/pkg/config"
"github.com/koderover/zadig/v2/pkg/setting"
aslanclient "github.com/koderover/zadig/v2/pkg/shared/client/aslan"
"github.com/koderover/zadig/v2/pkg/tool/cache"
"go.uber.org/zap"

commonrepo "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/mongodb"
)

const securitySettingsCacheTTL = 30 * time.Second

func CreateOrUpdateSecuritySettings(args *SecurityAndPrivacySettings, logger *zap.SugaredLogger) error {
err := commonrepo.NewSystemSettingColl().UpdateSecuritySetting(args.TokenExpirationTime)
err := commonrepo.NewSystemSettingColl().UpdateSecuritySetting(args.TokenExpirationTime, args.MFAEnabled)
if err != nil {
logger.Errorf("failed to update security settings, error: %s", err)
return err
Expand All @@ -34,6 +43,10 @@ func CreateOrUpdateSecuritySettings(args *SecurityAndPrivacySettings, logger *za
logger.Errorf("failed to update privacy settings, error: %s", err)
}

if cacheErr := syncSystemSecuritySettingsCache(logger); cacheErr != nil {
logger.Warnf("failed to sync security settings cache: %v", cacheErr)
}

return err
}

Expand All @@ -44,8 +57,10 @@ func GetSecuritySettings(logger *zap.SugaredLogger) (*SecurityAndPrivacySettings
return nil, err
}
var tokenExpirationTime int64 = 24
var mfaEnabled bool
if systemSetting.Security != nil {
tokenExpirationTime = systemSetting.Security.TokenExpirationTime
mfaEnabled = systemSetting.Security.MFAEnabled
}

var improvementPlan bool = true
Expand All @@ -54,6 +69,29 @@ func GetSecuritySettings(logger *zap.SugaredLogger) (*SecurityAndPrivacySettings
}
return &SecurityAndPrivacySettings{
TokenExpirationTime: tokenExpirationTime,
MFAEnabled: mfaEnabled,
ImprovementPlan: improvementPlan,
}, nil
}

func syncSystemSecuritySettingsCache(logger *zap.SugaredLogger) error {
settings, err := GetSecuritySettings(logger)
if err != nil {
return err
}

payload, err := json.Marshal(&aslanclient.SystemSetting{
TokenExpirationTime: settings.TokenExpirationTime,
MFAEnabled: settings.MFAEnabled,
ImprovementPlan: settings.ImprovementPlan,
})
if err != nil {
return err
}

return cache.NewRedisCache(configbase.RedisCommonCacheTokenDB()).Write(
setting.SystemSecuritySettingsCacheKey,
string(payload),
securitySettingsCacheTTL,
)
}
1 change: 1 addition & 0 deletions pkg/microservice/aslan/core/system/service/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ type OpenAPICluster struct {

type SecurityAndPrivacySettings struct {
TokenExpirationTime int64 `json:"token_expiration_time"`
MFAEnabled bool `json:"mfa_enabled"`
ImprovementPlan bool `json:"improvement_plan"`
}

Expand Down
20 changes: 20 additions & 0 deletions pkg/microservice/user/core/handler/login/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import (
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
)

// @Summary 本地登录
// @Description 本地账号密码登录,若命中 MFA 策略则返回 mfa_required、required_action 和 mfa_challenge_token,由前端继续完成 MFA 流程
// @Tags user
// @Accept json
// @Produce json
// @Param body body login.LoginArgs true "body"
// @Success 200 {object} login.User
// @Router /api/v1/login [post]
func LocalLogin(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
Expand All @@ -45,6 +53,12 @@ type getCaptchaResp struct {
Content string `json:"content"`
}

// @Summary 获取登录验证码
// @Description 当登录失败次数达到阈值后,前端可调用该接口获取验证码
// @Tags user
// @Produce json
// @Success 200 {object} getCaptchaResp
// @Router /api/v1/captcha [get]
func GetCaptcha(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
Expand All @@ -65,6 +79,12 @@ type LocalLogoutResp struct {
RedirectURL string `json:"redirect_url"`
}

// @Summary 退出登录
// @Description 清理当前用户登录态;若为第三方登录,可返回额外登出跳转地址
// @Tags user
// @Produce json
// @Success 200 {object} LocalLogoutResp
// @Router /api/v1/logout [get]
func LocalLogout(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
Expand Down
84 changes: 84 additions & 0 deletions pkg/microservice/user/core/handler/login/mfa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2021 The KodeRover Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

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.
*/

package login

import (
"github.com/gin-gonic/gin"

loginsvc "github.com/koderover/zadig/v2/pkg/microservice/user/core/service/login"
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
)

// @Summary 初始化登录态 MFA 配置
// @Description 基于 mfa_challenge_token 生成当前登录挑战所需的 MFA 配置数据,返回 secret、二维码和 required_action
// @Tags user
// @Accept json
// @Produce json
// @Param body body loginsvc.MFASetupArgs true "body"
// @Success 200 {object} loginsvc.MFASetupResp
// @Router /api/v1/login/mfa/setup [post]
func MFASetup(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()

args := &loginsvc.MFASetupArgs{}
if err := c.ShouldBindJSON(args); err != nil {
ctx.RespErr = err
return
}
ctx.Resp, ctx.RespErr = loginsvc.SetupMFA(args, ctx.Logger)
}

// @Summary 完成登录态 MFA 绑定
// @Description 在登录挑战阶段提交 OTP 完成首次 MFA 绑定,成功后返回正式登录态和恢复码
// @Tags user
// @Accept json
// @Produce json
// @Param body body loginsvc.MFAEnrollArgs true "body"
// @Success 200 {object} loginsvc.User
// @Router /api/v1/login/mfa/enroll [post]
func MFAEnroll(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()

args := &loginsvc.MFAEnrollArgs{}
if err := c.ShouldBindJSON(args); err != nil {
ctx.RespErr = err
return
}
ctx.Resp, ctx.RespErr = loginsvc.EnrollMFA(args, ctx.Logger)
}

// @Summary 完成登录态 MFA 验证
// @Description 在登录挑战阶段提交 OTP 或恢复码完成 MFA 验证,成功后返回正式登录态
// @Tags user
// @Accept json
// @Produce json
// @Param body body loginsvc.MFAVerifyArgs true "body"
// @Success 200 {object} loginsvc.User
// @Router /api/v1/login/mfa/verify [post]
func MFAVerify(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()

args := &loginsvc.MFAVerifyArgs{}
if err := c.ShouldBindJSON(args); err != nil {
ctx.RespErr = err
return
}
ctx.Resp, ctx.RespErr = loginsvc.VerifyMFA(args, ctx.Logger)
}
Loading
Loading