* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package test
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"console-service/pkg/auth"
"console-service/pkg/utils/authutil"
)
var _ = Describe("登录", func() {
It("未提供sessionID", func() {
resp, err := testHttpClient.Get(serverAddr + "/rest/auth/login")
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(302))
location := resp.Header.Get("Location")
u, err := url.Parse(location)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/oauth2/oauth/authorize"))
state := u.Query().Get("state")
Expect(state).NotTo(BeEmpty())
found := false
for _, cookie := range resp.Cookies() {
if cookie.Name == "state" {
Expect(cookie.Value).To(Equal(state))
found = true
break
}
}
Expect(found).To(BeTrue(), "响应未设置 state cookie")
})
It("提供不存在的sessionID", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/login", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "non-existing-sessionID"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(302))
location := resp.Header.Get("Location")
u, err := url.Parse(location)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/oauth2/oauth/authorize"))
state := u.Query().Get("state")
Expect(state).NotTo(BeEmpty())
found := false
for _, cookie := range resp.Cookies() {
if cookie.Name == "state" {
Expect(cookie.Value).To(Equal(state))
found = true
break
}
}
Expect(found).To(BeTrue(), "响应未设置 state cookie")
for _, cookie := range resp.Cookies() {
if cookie.Name == "sessionID" {
Expect(cookie.Value).To(BeEmpty())
break
}
}
})
It("提供过期的sessionID", func() {
err := auth.StoreSession(k8sClient, expiredSession, false)
Expect(err).NotTo(HaveOccurred())
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/login", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: expiredSession.GetSessionID()})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(302))
location := resp.Header.Get("Location")
u, err := url.Parse(location)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/oauth2/oauth/authorize"))
state := u.Query().Get("state")
Expect(state).NotTo(BeEmpty())
found := false
for _, cookie := range resp.Cookies() {
if cookie.Name == "state" {
Expect(cookie.Value).To(Equal(state))
found = true
break
}
}
Expect(found).To(BeTrue(), "响应未设置 state cookie")
for _, cookie := range resp.Cookies() {
if cookie.Name == "sessionID" {
Expect(cookie.Value).To(BeEmpty())
break
}
}
})
It("提供有效的sessionID", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/login", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "12345678"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(302))
location := resp.Header.Get("Location")
u, err := url.Parse(location)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/"))
})
})
var _ = Describe("登录回调", func() {
It("错误提示", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?error=access_denied&error_description=Invalid+credentials", nil)
Expect(err).NotTo(HaveOccurred())
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(200))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
Expect(string(body)).To(ContainSubstring("access_denied"))
Expect(string(body)).To(ContainSubstring("Invalid credentials"))
Expect(string(body)).To(ContainSubstring("<html"))
Expect(string(body)).To(ContainSubstring("</html>"))
})
It("缺少状态码参数", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?code=validcode", nil)
Expect(err).NotTo(HaveOccurred())
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
})
It("缺少验证码参数", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?state=validstate", nil)
Expect(err).NotTo(HaveOccurred())
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
})
It("无效的状态码", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?code=validcode&state=wrongstate", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "state", Value: "correctstate"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusSeeOther))
})
It("无效的验证码", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?code=invalidcode&state=correctstate", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "state", Value: "correctstate"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError))
})
It("有效的状态码与验证码", func() {
secrets, err := k8sClient.CoreV1().Secrets("session-secret").List(context.Background(), metav1.ListOptions{})
originalCount := len(secrets.Items)
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/callback?code=validcode&state=correctstate", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "state", Value: "correctstate"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusFound))
location := resp.Header.Get("Location")
u, err := url.Parse(location)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/"))
foundState := false
foundSessionID := false
foundWsID := false
for _, cookie := range resp.Cookies() {
if cookie.Name == "state" && cookie.Value == "" {
foundState = true
}
if cookie.Name == "sessionID" && cookie.Value != "" {
foundSessionID = true
}
if cookie.Name == "wsID" && cookie.Value != "" {
foundWsID = true
}
}
Expect(foundState).To(BeTrue(), "响应未包含删除 state cookie")
Expect(foundSessionID).To(BeTrue(), "响应未包含 sessionID cookie")
Expect(foundWsID).To(BeTrue(), "响应未包含 wsID cookie")
secrets, err = k8sClient.CoreV1().Secrets("session-secret").List(context.Background(), metav1.ListOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(len(secrets.Items)).To(BeNumerically(">", originalCount), "session-secret 命名空间应包含至少一个 secret")
})
})
var _ = Describe("登出", func() {
It("未提供sessionID", func() {
req, err := http.NewRequest("POST", serverAddr+"/rest/auth/logout", nil)
Expect(err).NotTo(HaveOccurred())
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusNoContent))
})
It("有效的sessionID", func() {
err := auth.StoreSession(k8sClient, &auth.SessionStore{
"AccessToken": []byte("xxxxxxxx"),
"RefreshToken": []byte("xxxxxxxx"),
"SessionID": []byte("xxxxxxxx"),
"AccessExpiry": []byte(strconv.FormatInt(time.Now().Add(2*time.Hour).Unix(), 10)),
"RefreshExpiry": []byte(strconv.FormatInt(time.Now().Add(2*time.Hour).Unix(), 10)),
}, false)
Expect(err).NotTo(HaveOccurred())
req, err := http.NewRequest("POST", serverAddr+"/rest/auth/logout", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "xxxxxxxx"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusNoContent))
found := false
for _, c := range resp.Cookies() {
if c.Name == "sessionID" {
Expect(c.Value).To(BeEmpty())
found = true
break
}
}
Expect(found).To(BeTrue(), "响应未包含清除 sessionID 的 Set-Cookie")
_, err = auth.GetSession(k8sClient, "xxxxxxxx")
Expect(err).To(HaveOccurred())
})
})
var _ = Describe("获取当前用户", func() {
It("未提供sessionID", func() {
resp, err := testHttpClient.Get(serverAddr + "/rest/auth/user")
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(Equal("cookie not found"))
})
It("无效的sessionID(过期)", func() {
err := auth.StoreSession(k8sClient, expiredSession, false)
Expect(err).NotTo(HaveOccurred())
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/user", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: expiredSession.GetSessionID()})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(Equal("cannot get token from sessionID"))
})
It("有效的sessionID,无效的accessCode", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/user", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "12345678"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(Equal("cannot extract user from token"))
})
It("有效的sessionID,有效的accessCode", func() {
token := &auth.AccessRefreshToken{
AccessToken: authutil.GenerateToken("user"),
AccessTokenExpiry: time.Now().Add(2 * time.Hour),
RefreshToken: "dummy-refresh-token",
RefreshTokenExpiry: time.Now().Add(2 * time.Hour),
}
session, err := auth.NewStoreSession(token)
Expect(err).NotTo(HaveOccurred())
Expect(auth.StoreSession(k8sClient, session, false)).To(Succeed())
req, err := http.NewRequest("GET", serverAddr+"/rest/auth/user", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: session.GetSessionID()})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusOK))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(Equal("get current user succeed"))
Expect(res.Data).To(Equal("user"))
Expect(auth.DeleteSession(k8sClient, session.GetSessionID())).To(Succeed())
})
})
var _ = Describe("时间偏移", func() {
It("时间偏移", func() {
req, err := http.NewRequest("GET", serverAddr+"/rest/console/v1beta1/time-offset", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "12345678"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusOK))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data map[string]string `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Data["currentServerTime"]).NotTo(BeEmpty())
Expect(res.Data["offset"]).To(Or(HavePrefix("UTC+"), HavePrefix("UTC-")))
})
})
var _ = Describe("登录状态websocket", func() {
It("Upgrade失败,未提供wsID", func() {
req, err := http.NewRequest("GET", serverAddr+"/ws/auth/login-status", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "12345678"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(Equal("websocket connection request failed, fail to get cookieNameWsID from cookie"))
})
It("Upgrade失败,未提供Upgrade请求头", func() {
req, err := http.NewRequest("GET", serverAddr+"/ws/auth/login-status", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "sessionID", Value: "12345678"})
req.AddCookie(&http.Cookie{Name: "wsID", Value: "12345678"})
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
body, err := io.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
var res struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
Expect(json.Unmarshal(body, &res)).To(Succeed())
Expect(res.Msg).To(ContainSubstring("the client is not using the websocket protocol: 'upgrade' token"))
})
It("Upgrade成功 - 验证心跳包并登出", func() {
dialer := websocket.Dialer{}
wsURL := strings.Replace(serverAddr, "http", "ws", 1) + "/ws/auth/login-status"
header := http.Header{}
header.Set("Cookie", "sessionID=12345678; wsID=12345678")
conn, resp, err := dialer.Dial(wsURL, header)
Expect(err).NotTo(HaveOccurred())
defer conn.Close()
Expect(resp.StatusCode).To(Equal(http.StatusSwitchingProtocols))
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
messageType, message, err := conn.ReadMessage()
Expect(err).NotTo(HaveOccurred())
Expect(messageType).To(Equal(websocket.TextMessage))
var heartbeatMsg struct {
LoginStatus string `json:"loginStatus"`
}
Expect(json.Unmarshal(message, &heartbeatMsg)).To(Succeed())
Expect(heartbeatMsg.LoginStatus).To(Equal("true"))
req, err := http.NewRequest("GET", serverAddr+"/rest/alert/v1/test", nil)
Expect(err).NotTo(HaveOccurred())
req.AddCookie(&http.Cookie{Name: "wsID", Value: "12345678"})
_, err = testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
messageType, message, err = conn.ReadMessage()
Expect(err).NotTo(HaveOccurred())
Expect(messageType).To(Equal(websocket.TextMessage))
Expect(json.Unmarshal(message, &heartbeatMsg)).To(Succeed())
Expect(heartbeatMsg.LoginStatus).To(Equal("false"))
})
})
var _ = Describe("OAuth登录重定向", func() {
BeforeEach(func() {
_, err := k8sClient.CoreV1().Namespaces().Get(context.Background(), "session-secret", metav1.GetOptions{})
if err != nil {
_, err = k8sClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "session-secret"},
}, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())
}
})
It("登录成功后重定向到原始页面", func() {
targetURL := "/container_platform/workload/pods"
req, err := http.NewRequest("GET", serverAddr+targetURL, nil)
Expect(err).NotTo(HaveOccurred())
resp, err := testHttpClient.Do(req)
Expect(err).NotTo(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusSeeOther))
location := resp.Header.Get("Location")
Expect(location).To(ContainSubstring("/rest/auth/login"))
var referrerCookie *http.Cookie
for _, cookie := range resp.Cookies() {
if cookie.Name == "X-OpenFuyao-Referrer" {
referrerCookie = cookie
break
}
}
Expect(referrerCookie).NotTo(BeNil(), "应该设置 X-OpenFuyao-Referrer Cookie")
Expect(referrerCookie.Value).To(ContainSubstring(targetURL), "Referrer Cookie 应包含原始 URL")
loginReq, err := http.NewRequest("GET", serverAddr+"/rest/auth/login", nil)
Expect(err).NotTo(HaveOccurred())
loginResp, err := testHttpClient.Do(loginReq)
Expect(err).NotTo(HaveOccurred())
defer loginResp.Body.Close()
Expect(loginResp.StatusCode).To(Equal(http.StatusFound))
var stateCookie *http.Cookie
var stateValue string
for _, cookie := range loginResp.Cookies() {
if cookie.Name == "state" {
stateCookie = cookie
stateValue = cookie.Value
break
}
}
Expect(stateCookie).NotTo(BeNil(), "登录请求应设置 state Cookie")
callbackURL := serverAddr + "/rest/auth/callback?code=validcode&state=" + stateValue
callbackReq, err := http.NewRequest("GET", callbackURL, nil)
Expect(err).NotTo(HaveOccurred())
callbackReq.AddCookie(stateCookie)
callbackReq.AddCookie(referrerCookie)
callbackResp, err := testHttpClient.Do(callbackReq)
Expect(err).NotTo(HaveOccurred())
defer callbackResp.Body.Close()
Expect(callbackResp.StatusCode).To(Equal(http.StatusFound))
redirectLocation := callbackResp.Header.Get("Location")
u, err := url.Parse(redirectLocation)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal(targetURL), "登录成功后应重定向到原始页面")
for _, cookie := range callbackResp.Cookies() {
if cookie.Name == "X-OpenFuyao-Referrer" {
Expect(cookie.Value).To(BeEmpty(), "Referrer Cookie 应被清除")
break
}
}
})
It("无 Referrer Cookie 时重定向到根路径", func() {
loginReq, err := http.NewRequest("GET", serverAddr+"/rest/auth/login", nil)
Expect(err).NotTo(HaveOccurred())
loginResp, err := testHttpClient.Do(loginReq)
Expect(err).NotTo(HaveOccurred())
defer loginResp.Body.Close()
Expect(loginResp.StatusCode).To(Equal(http.StatusFound))
var stateCookie *http.Cookie
var stateValue string
for _, cookie := range loginResp.Cookies() {
if cookie.Name == "state" {
stateCookie = cookie
stateValue = cookie.Value
break
}
}
Expect(stateCookie).NotTo(BeNil())
callbackURL := serverAddr + "/rest/auth/callback?code=validcode&state=" + stateValue
callbackReq, err := http.NewRequest("GET", callbackURL, nil)
Expect(err).NotTo(HaveOccurred())
callbackReq.AddCookie(stateCookie)
callbackResp, err := testHttpClient.Do(callbackReq)
Expect(err).NotTo(HaveOccurred())
defer callbackResp.Body.Close()
Expect(callbackResp.StatusCode).To(Equal(http.StatusFound))
redirectLocation := callbackResp.Header.Get("Location")
u, err := url.Parse(redirectLocation)
Expect(err).NotTo(HaveOccurred())
Expect(u.Path).To(Equal("/"), "无 Referrer Cookie 时应重定向到根路径")
})
})