Skip to content

Commit 10461f5

Browse files
committed
support sso token callback
Signed-off-by: Patrick Zhao <zhaoyu@koderover.com>
1 parent 80e63ed commit 10461f5

File tree

7 files changed

+171
-4
lines changed

7 files changed

+171
-4
lines changed

pkg/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func SecretKey() string {
9898
return viper.GetString(setting.ENVSecretKey)
9999
}
100100

101+
func SsoTokenSecret() string {
102+
return viper.GetString(setting.ENVSsoTokenSecret)
103+
}
104+
101105
func AslanServiceAddress() string {
102106
s := AslanServiceInfo()
103107
return GetServiceAddress(s.Name, s.Port)

pkg/microservice/user/config/consts.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
)
2222

2323
const (
24-
AppState = setting.ProductName + "user"
25-
SystemIdentityType = "system"
26-
OauthIdentityType = "oauth"
27-
FeiShuEmailHost = "smtp.feishu.cn"
24+
AppState = setting.ProductName + "user"
25+
SystemIdentityType = "system"
26+
SsoTokenIdentityType = "sso_token"
27+
OauthIdentityType = "oauth"
28+
FeiShuEmailHost = "smtp.feishu.cn"
2829

2930
UserGroupCacheKeyFormat = "user_group_%s"
3031
)

pkg/microservice/user/core/handler/login/local.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ limitations under the License.
1717
package login
1818

1919
import (
20+
"net/http"
21+
"net/url"
22+
2023
"github.com/gin-gonic/gin"
2124

25+
configbase "github.com/koderover/zadig/v2/pkg/config"
2226
"github.com/koderover/zadig/v2/pkg/microservice/user/core/service/login"
2327
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
2428
)
@@ -40,6 +44,23 @@ func LocalLogin(c *gin.Context) {
4044
ctx.Resp, ctx.RespErr = resp, err
4145
}
4246

47+
func SsoTokenCallback(c *gin.Context) {
48+
ctx := internalhandler.NewContext(c)
49+
defer func() { internalhandler.JSONResponse(c, ctx) }()
50+
51+
ssoToken := c.Query("token")
52+
token, err := login.SsoTokenCallback(ssoToken, ctx.Logger)
53+
if err != nil {
54+
ctx.RespErr = err
55+
return
56+
}
57+
58+
v := url.Values{}
59+
v.Add("token", token)
60+
redirectUrl := configbase.SystemAddress() + "/signin?" + v.Encode()
61+
c.Redirect(http.StatusSeeOther, redirectUrl)
62+
}
63+
4364
type getCaptchaResp struct {
4465
ID string `json:"id"`
4566
Content string `json:"content"`

pkg/microservice/user/core/handler/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (*Router) Inject(router *gin.RouterGroup) {
7878
general.GET("/callback", login.Callback)
7979
general.GET("/login", login.Login)
8080
general.POST("/login", login.LocalLogin)
81+
general.GET("/sso/token-callback", login.SsoTokenCallback)
8182
general.GET("/login-enabled", login.ThirdPartyLoginEnabled)
8283
general.GET("/captcha", login.GetCaptcha)
8384
general.GET("/logout", login.LocalLogout)

pkg/microservice/user/core/service/login/local.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ limitations under the License.
1717
package login
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"time"
2223

24+
"github.com/go-sql-driver/mysql"
2325
"github.com/golang-jwt/jwt"
26+
"github.com/google/uuid"
2427
"github.com/mojocn/base64Captcha"
2528
"github.com/patrickmn/go-cache"
2629
"go.uber.org/zap"
@@ -29,12 +32,14 @@ import (
2932
configbase "github.com/koderover/zadig/v2/pkg/config"
3033
"github.com/koderover/zadig/v2/pkg/microservice/user/config"
3134
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository"
35+
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/models"
3236
"github.com/koderover/zadig/v2/pkg/microservice/user/core/repository/orm"
3337
"github.com/koderover/zadig/v2/pkg/microservice/user/core/service/common"
3438
"github.com/koderover/zadig/v2/pkg/setting"
3539
"github.com/koderover/zadig/v2/pkg/shared/client/aslan"
3640
"github.com/koderover/zadig/v2/pkg/shared/client/plutusvendor"
3741
zadigCache "github.com/koderover/zadig/v2/pkg/tool/cache"
42+
"github.com/koderover/zadig/v2/pkg/tool/log"
3843
)
3944

4045
type LoginArgs struct {
@@ -271,3 +276,133 @@ func GetCaptcha(logger *zap.SugaredLogger) (string, string, error) {
271276
}
272277
return id, b64s, nil
273278
}
279+
280+
type SsoTokenClaims struct {
281+
UserID string `json:"userId"`
282+
Account string `json:"account"`
283+
jwt.StandardClaims
284+
}
285+
286+
func SsoTokenCallback(tokenString string, logger *zap.SugaredLogger) (string, error) {
287+
parsedToken, err := jwt.ParseWithClaims(tokenString, &SsoTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
288+
return []byte(configbase.SsoTokenSecret()), nil
289+
})
290+
if err != nil {
291+
return "", err
292+
}
293+
294+
claims, ok := parsedToken.Claims.(*SsoTokenClaims)
295+
if !ok || !parsedToken.Valid {
296+
return "", fmt.Errorf("invalid token")
297+
}
298+
299+
log.Infof("[SsoTokenCallback]: userId: %s, account: %s", claims.UserID, claims.Account)
300+
301+
var userLogin *models.UserLogin
302+
identityType := config.SsoTokenIdentityType
303+
user, err := orm.GetUser(claims.UserID, identityType, repository.DB)
304+
if err != nil {
305+
err = fmt.Errorf("SsoTokenLogin get user account:%s error, error msg:%s", claims.UserID, err.Error())
306+
log.Errorf(err.Error())
307+
return "", err
308+
}
309+
if user == nil {
310+
uid, _ := uuid.NewUUID()
311+
user := &models.User{
312+
Name: claims.Account,
313+
Email: fmt.Sprintf("%s-%s@poc.example", claims.UserID, claims.Account),
314+
IdentityType: identityType,
315+
Account: claims.UserID,
316+
UID: uid.String(),
317+
}
318+
319+
tx := repository.DB.Begin()
320+
defer func() {
321+
if r := recover(); r != nil {
322+
tx.Rollback()
323+
}
324+
}()
325+
err = orm.CreateUser(user, tx)
326+
if err != nil {
327+
tx.Rollback()
328+
logger.Errorf("[SsoTokenCallback] CreateUser :%v error, error msg:%s", user, err.Error())
329+
var mysqlErr *mysql.MySQLError
330+
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
331+
return "", fmt.Errorf("存在相同用户名")
332+
}
333+
return "", fmt.Errorf("创建用户失败, error: %s", err.Error())
334+
}
335+
userLogin := &models.UserLogin{
336+
UID: user.UID,
337+
LastLoginTime: time.Now().Unix(),
338+
LoginId: user.Account,
339+
LoginType: int(config.AccountLoginType),
340+
}
341+
err = orm.CreateUserLogin(userLogin, tx)
342+
if err != nil {
343+
tx.Rollback()
344+
err = fmt.Errorf("[SsoTokenCallback] CreateUserLogin:%v error, error msg:%s", user, err.Error())
345+
log.Errorf(err.Error())
346+
return "", err
347+
}
348+
if tx.Commit().Error != nil {
349+
return "", fmt.Errorf("创建用户登录信息失败, error: %s", tx.Commit().Error)
350+
}
351+
} else {
352+
userLogin, err = orm.GetUserLogin(user.UID, claims.UserID, config.AccountLoginType, repository.DB)
353+
if err != nil {
354+
err = fmt.Errorf("SsoTokenLogin get user:%s user login not exist, error msg:%s", claims.UserID, err.Error())
355+
log.Errorf(err.Error())
356+
return "", err
357+
}
358+
}
359+
360+
if userLogin != nil {
361+
err = CheckSignature(userLogin.LastLoginTime, logger)
362+
if err != nil {
363+
return "", err
364+
}
365+
}
366+
367+
userLogin.LastLoginTime = time.Now().Unix()
368+
err = orm.UpdateUserLogin(userLogin.UID, userLogin, repository.DB)
369+
if err != nil {
370+
err = fmt.Errorf("[SsoTokenCallback] user:%s update user login info error, error msg:%s", claims.UserID, err.Error())
371+
log.Errorf(err.Error())
372+
return "", err
373+
}
374+
375+
systemSettings, err := aslan.New(configbase.AslanServiceAddress()).GetSystemSecurityAndPrivacySettings()
376+
if err != nil {
377+
err = fmt.Errorf("failed to get system security settings, error: %s", err)
378+
log.Errorf(err.Error())
379+
return "", err
380+
}
381+
382+
token, err := CreateToken(&Claims{
383+
Name: user.Name,
384+
UID: user.UID,
385+
Email: user.Email,
386+
PreferredUsername: user.Account,
387+
StandardClaims: jwt.StandardClaims{
388+
Audience: setting.ProductName,
389+
ExpiresAt: time.Now().Add(time.Duration(systemSettings.TokenExpirationTime) * time.Hour).Unix(),
390+
},
391+
FederatedClaims: FederatedClaims{
392+
ConnectorId: user.IdentityType,
393+
UserId: user.Account,
394+
},
395+
})
396+
if err != nil {
397+
err = fmt.Errorf("[SsoTokenCallback] user:%s create token error, error msg:%s", claims.UserID, err.Error())
398+
log.Errorf(err.Error())
399+
return "", err
400+
}
401+
402+
err = zadigCache.NewRedisCache(config.RedisUserTokenDB()).Write(user.UID, token, time.Duration(systemSettings.TokenExpirationTime)*time.Hour)
403+
if err != nil {
404+
logger.Errorf("failed to write token into cache, error: %s\n warn: this will cause login failure", err)
405+
}
406+
407+
return token, nil
408+
}

pkg/microservice/user/core/service/permission/authn.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ func IsPublicURL(reqPath, method string) bool {
136136
return true
137137
}
138138

139+
if realPath == "/api/v1/sso/token-callback" && method == http.MethodGet {
140+
return true
141+
}
142+
139143
if realPath == "/api/v1/captcha" && method == http.MethodGet {
140144
return true
141145
}

pkg/setting/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ const (
109109
ENVClientSecret = "CLIENT_SECRET"
110110
ENVRedirectURI = "REDIRECT_URI"
111111
ENVSecretKey = "SECRET_KEY"
112+
ENVSsoTokenSecret = "SSO_TOKEN_SECRET"
112113
ENVMysqlUserDB = "MYSQL_USER_DB"
113114
ENVScopes = "SCOPES"
114115
ENVTokenExpiresAt = "TOKEN_EXPIRES_AT"

0 commit comments

Comments
 (0)