Revert "fix: remove sso related code" (#21657)

This commit is contained in:
Lynn authored and GitHub committed 2026-10-29 14:32:50 +08:00
1 parent eee9b25f4b
commit 849ccb4d3a
16 files changed
+135 -47

No files matched your search

+3
View File
@@ -496,6 +496,9 @@ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
# Used for ThreadPoolExecutor
THREAD_POOL_MAX_WORKERS=128
#Option to disable login form for SSO
DISABLE_PASSWORD_LOGIN=false
# -----------------------------------------------------------------------------
# Knowledge compilation
# -----------------------------------------------------------------------------
+1
View File
@@ -215,6 +215,7 @@ user_default_llm:
# http_secret_key:
# site:
# switch: false
# disable_password_login: false
# permission:
# switch: false
# component: false
+12 -12
View File
@@ -62,7 +62,7 @@ func putVariable(t *testing.T, h *Handler, body string) setVariableResponse {
}
// TestSetVariableFlattensNativeJSONScalars covers the reported regression: the
// admin console sends a JSON boolean for a toggle, and a string-typed request
// admin console toggles SSO with a JSON boolean, and a string-typed request
// field used to reject it as a missing var_name.
func TestSetVariableFlattensNativeJSONScalars(t *testing.T) {
gin.SetMode(gin.TestMode)
@@ -74,15 +74,15 @@ func TestSetVariableFlattensNativeJSONScalars(t *testing.T) {
wantValue string
}{
{
name: "bool true keeps its literal",
varName: "mail.enabled",
body: `{"var_name":"mail.enabled","var_value":true}`,
name: "sso enable sends true",
varName: "feishu|sso.enabled",
body: `{"var_name":"feishu|sso.enabled","var_value":true}`,
wantValue: "true",
},
{
name: "bool false keeps its literal",
varName: "mail.enabled",
body: `{"var_name":"mail.enabled","var_value":false}`,
name: "sso disable sends false",
varName: "feishu|sso.enabled",
body: `{"var_name":"feishu|sso.enabled","var_value":false}`,
wantValue: "false",
},
{
@@ -137,15 +137,15 @@ func TestSetVariableUpdatesExistingSetting(t *testing.T) {
gin.SetMode(gin.TestMode)
handler, db := setupSetVariableHandler(t)
if response := putVariable(t, handler, `{"var_name":"mail.enabled","var_value":true}`); response.Code != int(common.CodeSuccess) {
if response := putVariable(t, handler, `{"var_name":"feishu|sso.enabled","var_value":true}`); response.Code != int(common.CodeSuccess) {
t.Fatalf("enable: code = %d, message = %s", response.Code, response.Message)
}
if response := putVariable(t, handler, `{"var_name":"mail.enabled","var_value":false}`); response.Code != int(common.CodeSuccess) {
if response := putVariable(t, handler, `{"var_name":"feishu|sso.enabled","var_value":false}`); response.Code != int(common.CodeSuccess) {
t.Fatalf("disable: code = %d, message = %s", response.Code, response.Message)
}
var stored entity.SystemSettings
if err := db.Where("name = ?", "mail.enabled").First(&stored).Error; err != nil {
if err := db.Where("name = ?", "feishu|sso.enabled").First(&stored).Error; err != nil {
t.Fatalf("load stored setting: %v", err)
}
if stored.Value != "false" {
@@ -190,9 +190,9 @@ func TestSetVariableRejectsUnusableRequests(t *testing.T) {
// A value rejected by ValidateSystemSettingValue surfaces as a
// server error, which is the pre-existing contract of this endpoint.
name: "bool-typed setting rejects non bool",
body: `{"var_name":"mail.enabled","var_value":"yes"}`,
body: `{"var_name":"feishu|sso.enabled","var_value":"yes"}`,
wantCode: common.CodeServerError,
wantMessage: "invalid bool value for mail.enabled: expected true or false",
wantMessage: "invalid bool value for feishu|sso.enabled: expected true or false",
},
{
// Falsy scalars are legitimate values, not missing fields.
+1
View File
@@ -262,6 +262,7 @@ const (
EnvFirecrawlRelayTimeout = "FIRECRAWL_RELAY_TIMEOUT"
EnvRAGFlowSecretKey = "RAGFLOW_SECRET_KEY"
EnvEnableRegister = "ENABLE_REGISTER"
EnvDisablePasswordLogin = "DISABLE_PASSWORD_LOGIN"
EnvMinioHost = "MINIO_HOST"
EnvMinioRegion = "MINIO_REGION"
EnvLang = "LANG"
+1 -1
View File
@@ -105,7 +105,7 @@ func (h *AuthHandler) BetaAuthMiddleware() gin.HandlerFunc {
ctx := c.Request.Context()
auth := c.GetHeader("Authorization")
if auth == "" {
if cookie, err := c.Cookie(authCookie); err == nil {
if cookie, err := c.Cookie(oauthAuthCookie); err == nil {
auth = cookie
}
}
+8 -6
View File
@@ -41,12 +41,14 @@ func jsonInternalError(c *gin.Context, err error) {
// HandleNoRoute handles requests to undefined routes
func HandleNoRoute(c *gin.Context) {
// Python parity: these malformed paths resolved to a Werkzeug
// MethodNotAllowed in the Python API, which server_error_response renders
// as HTTP 200 / code 100 with the exception's repr() as the message. gin
// instead falls through to NoRoute, so emit the same body here to keep the
// error paths byte-for-byte aligned.
if (c.Request.Method == http.MethodPost && c.Request.URL.Path == "/api/v1/chats//sessions") ||
// Python parity: GET /api/v1/auth/login/ (an empty OAuth channel) resolves
// to a Werkzeug MethodNotAllowed in the Python API, which
// server_error_response renders as HTTP 200 / code 100 with the
// exception's repr() as the message. gin instead falls through to
// NoRoute, so emit the same body here to keep the auth error paths
// byte-for-byte aligned.
if (c.Request.Method == http.MethodGet && c.Request.URL.Path == "/api/v1/auth/login/") ||
(c.Request.Method == http.MethodPost && c.Request.URL.Path == "/api/v1/chats//sessions") ||
(c.Request.Method == http.MethodPatch && strings.HasSuffix(c.Request.URL.Path, "/sessions/")) {
common.ResponseWithCodeData(c, common.CodeExceptionError, false, "<MethodNotAllowed '405: Method Not Allowed'>")
return
+37 -17
View File
@@ -45,21 +45,25 @@ func NewUserHandler(userService *service.UserService) *UserHandler {
}
}
// authCookie carries the signed access token to the SPA after login so its
// JavaScript can send it back as an Authorization header on later requests.
// Not HttpOnly because the SPA has to read it; the server validates the token
// signature and expiry on every request.
const authCookie = "ragflow_auth"
// oauthAuthCookie is the cookie the callback writes on success, so the SPA
// can pick up the signed access token after the redirect. The frontend
// reads it and either re-issues the value as an Authorization header on
// subsequent API calls or hands it off to its own token store. Not
// HttpOnly so the SPA's JS can read it.
const oauthAuthCookie = "ragflow_auth"
// setAuthCookie writes the signed access token so the SPA can send it back as
// a bearer token. Not HttpOnly so the SPA can copy it into its Authorization
// header. The server checks the token signature and expiry on each request.
func setAuthCookie(c *gin.Context, token string) {
// codeql[go/cookie-httponly-not-set] Intentional: the SPA reads the cookie
// via document.cookie, and the API validates the token signature and
// expiry on each request.
// setOAuthAuthCookie writes the signed access token so the SPA can pick it
// up after the redirect. Not HttpOnly so the SPA can copy it into its
// Authorization header on subsequent fetches. The server checks the token
// signature and expiry on each request.
func setOAuthAuthCookie(c *gin.Context, token string) {
// the SPA's bootstrap credential after the OAuth redirect. The
// SPA reads it via document.cookie and copies it into the
// Authorization header. Setting HttpOnly would break the login
// flow. The API validates the token signature and expiry on each request.
// codeql[go/cookie-httponly-not-set] Intentional: this cookie is
http.SetCookie(c.Writer, &http.Cookie{
Name: authCookie,
Name: oauthAuthCookie,
Value: token,
Path: "/",
MaxAge: 60 * 60 * 24 * 7,
@@ -106,7 +110,7 @@ func (h *UserHandler) Register(c *gin.Context) {
}
c.Header("Authorization", authToken)
setAuthCookie(c, authToken)
setOAuthAuthCookie(c, authToken)
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Allow-Headers", "*")
@@ -184,7 +188,7 @@ func (h *UserHandler) Login(c *gin.Context) {
// Set Authorization header with signed token
c.Header("Authorization", authToken)
setAuthCookie(c, authToken)
setOAuthAuthCookie(c, authToken)
// Set CORS headers
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "*")
@@ -275,7 +279,7 @@ func (h *UserHandler) LoginByEmail(c *gin.Context) {
operationLog.DurationMS = time.Since(startAt).Milliseconds()
return
}
setAuthCookie(c, authToken)
setOAuthAuthCookie(c, authToken)
c.Header("Authorization", authToken)
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "*")
@@ -339,7 +343,7 @@ func (h *UserHandler) Logout(c *gin.Context) {
}()
http.SetCookie(c.Writer, &http.Cookie{
Name: authCookie,
Name: oauthAuthCookie,
Value: "",
Path: "/",
MaxAge: -1,
@@ -476,6 +480,22 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
common.SuccessWithData(c, true, "password changed successfully")
}
// GetLoginChannels get all supported authentication channels
// @Summary Get Login Channels
// @Description Get all supported OAuth authentication channels
// @Tags users
// @Success 200 {object} map[string]interface{}
// @Router /v1/user/login/channels [get]
func (h *UserHandler) GetLoginChannelsDeprecated(c *gin.Context) {
channels, code, err := h.userService.GetLoginChannels()
if err != nil {
common.ResponseWithCodeData(c, code, []interface{}{}, "Load channels failure, error: "+err.Error())
return
}
common.SuccessWithData(c, channels, "success")
}
// SetTenantInfo update tenant information
// @Summary Set Tenant Info
// @Description Update tenant model configuration
+5
View File
@@ -156,6 +156,11 @@ func Init(configPath string) error {
return fmt.Errorf("parse default models config error: %w", err)
}
err = globalConfig.ParseOAuthConfig(v)
if err != nil {
return fmt.Errorf("parse OAuth config error: %w", err)
}
return nil
}
+14 -1
View File
@@ -30,7 +30,8 @@ const (
)
type AuthenticationConfig struct {
EnableRegister bool `mapstructure:"enable_register"`
DisablePasswordLogin bool `mapstructure:"disable_password_login"`
EnableRegister bool `mapstructure:"enable_register"`
}
type APIServerConfig struct {
@@ -93,6 +94,7 @@ func (c *Config) ParseAPIServerConfig(v *viper.Viper) error {
func (c *Config) parseAuthenticationConfig(v *viper.Viper) {
apiServerConfig := &c.apiServer
apiServerConfig.Authentication.DisablePasswordLogin = false
apiServerConfig.Authentication.EnableRegister = true
if !v.IsSet("authentication") {
@@ -103,11 +105,22 @@ func (c *Config) parseAuthenticationConfig(v *viper.Viper) {
return
}
if sub.IsSet("disable_password_login") {
apiServerConfig.Authentication.DisablePasswordLogin = sub.GetBool("disable_password_login")
}
if sub.IsSet("enable_register") {
apiServerConfig.Authentication.EnableRegister = sub.GetBool("enable_register")
}
}
func (c *Config) DisablePasswordLogin() bool {
if c.environments.DisablePasswordLogin != nil {
return *c.environments.DisablePasswordLogin
}
return c.apiServer.Authentication.DisablePasswordLogin
}
func (c *Config) EnableRegister() bool {
if c.environments.EnableRegister != nil {
return *c.environments.EnableRegister
+1
View File
@@ -43,4 +43,5 @@ type Config struct {
// For EE
defaultModels DefaultModelsConfig
billing BillingConfig
oAuth OAuthConfig
}
+18 -1
View File
@@ -30,7 +30,8 @@ type Environments struct {
SecretKey string `mapstructure:"secret_key"`
EnableRegister *bool `mapstructure:"enable_register"`
EnableRegister *bool `mapstructure:"enable_register"`
DisablePasswordLogin *bool `mapstructure:"disable_password_login"`
DocumentEngineType string `mapstructure:"document_engine_type"`
DatabaseType string `mapstructure:"database_type"`
@@ -143,6 +144,18 @@ func (c *Config) GetEnvironments() error {
}
}
if envVal := common.GetEnv(common.EnvDisablePasswordLogin); envVal != "" {
if c.environments.DisablePasswordLogin == nil {
c.environments.DisablePasswordLogin = new(bool)
}
str := envVal
if str == "true" || str == "1" || str == "yes" {
*c.environments.DisablePasswordLogin = true
} else {
*c.environments.DisablePasswordLogin = false
}
}
// Doc engine
docEngine := common.GetEnvSmall(common.EnvDocEngine)
if docEngine != "" {
@@ -587,6 +600,10 @@ func (c *Config) GetEnvEnableRegister() bool {
return *c.environments.EnableRegister
}
func (c *Config) GetEnvDisablePasswordLogin() bool {
return *c.environments.DisablePasswordLogin
}
func (c *Config) GetEnvDocumentEngineType() string {
return c.environments.DocumentEngineType
}
+8
View File
@@ -82,3 +82,11 @@ func (c *Config) GetDefaultOCRModel() ModelConfig { return c.defaultModels.OCRMo
// GetDefaultTTSModel returns the deployment TTS model, empty in the OSS build.
func (c *Config) GetDefaultTTSModel() ModelConfig { return c.defaultModels.TTSModel }
// OAuthConfig carries the OAuth/OIDC login provider settings. The OSS build
// has no OAuth login.
type OAuthConfig struct{}
// ParseOAuthConfig ignores OAuth configuration: the OSS build has no OAuth
// login.
func (c *Config) ParseOAuthConfig(v *viper.Viper) error { return nil }
+5 -5
View File
@@ -46,7 +46,7 @@ backends:
- password: ""
- password: null
password_policy: strict
enable_register: true
disable_password_login: true
max_token: 64
`)); err != nil {
t.Fatal(err)
@@ -80,10 +80,10 @@ max_token: 64
map[string]any{"password": "******"},
map[string]any{"password": "******"},
},
"credentials": wantCredentials,
"password_policy": "strict",
"enable_register": true,
"max_token": int64(64),
"credentials": wantCredentials,
"password_policy": "strict",
"disable_password_login": true,
"max_token": int64(64),
}
if !reflect.DeepEqual(settings, want) {
t.Fatalf("logged settings = %#v, want %#v", settings, want)
+13
View File
@@ -78,3 +78,16 @@ func resolveDefaultTenantIDEE(context.Context, string) (string, bool, error) {
func resolveUserAuthTenant(_ context.Context, _ *entity.User, tenantID string) (string, error) {
return tenantID, nil
}
// LoginChannel is one authentication channel advertised to the login page.
type LoginChannel struct {
Channel string `json:"channel"`
DisplayName string `json:"display_name"`
Icon string `json:"icon"`
}
// GetLoginChannels returns no channel: the community edition only offers
// password login.
func (s *UserService) GetLoginChannels() ([]*LoginChannel, common.ErrorCode, error) {
return make([]*LoginChannel, 0), common.CodeSuccess, nil
}
+4 -2
View File
@@ -41,14 +41,16 @@ func NewSystemService() *SystemService {
// ConfigResponse system configuration response
type ConfigResponse struct {
EnableRegister bool `json:"registerEnabled"`
EnableRegister bool `json:"registerEnabled"`
DisablePasswordLogin bool `json:"disablePasswordLogin"`
}
// GetConfig get system configuration
func (s *SystemService) GetConfig() (*ConfigResponse, error) {
cfg := server.GetConfig()
return &ConfigResponse{
EnableRegister: cfg.EnableRegister(),
EnableRegister: cfg.EnableRegister(),
DisablePasswordLogin: cfg.DisablePasswordLogin(),
}, nil
}
+4 -2
View File
@@ -27,8 +27,10 @@ func TestRESTSystemPublicContracts(t *testing.T) {
}
systemConfig := restOK(t, restCall(t, &config, http.MethodGet, "/system/config", nil, nil))
configData := object(t, systemConfig["data"])
if _, ok := configData["registerEnabled"]; !ok {
t.Fatalf("system config missing %q: %v", "registerEnabled", configData)
for _, key := range []string{"registerEnabled", "disablePasswordLogin"} {
if _, ok := configData[key]; !ok {
t.Fatalf("system config missing %q: %v", key, configData)
}
}
health := restCall(t, &config, http.MethodGet, "/system/healthz", nil, nil)
if health.Status != http.StatusOK && health.Status != http.StatusInternalServerError {