blob: 0d790055b193984b4769fa32aff42398f6f3002b [file]
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// 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 object
import (
"strconv"
"github.com/apache/casbin-gateway/conf"
"github.com/apache/casbin-gateway/util"
"github.com/xorm-io/core"
)
// BuiltInSettingId is the one row this table holds. Settings are global, so
// there is nothing to add or delete: the row is created on first start from
// conf/app.conf and edited from the Settings page afterwards.
const BuiltInSettingId = "admin/setting-built-in"
type Setting struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
GatewayEnabled bool `xorm:"bool" json:"gatewayEnabled"`
GatewayHttpPort int `xorm:"int" json:"gatewayHttpPort"`
GatewayHttpsPort int `xorm:"int" json:"gatewayHttpsPort"`
LlmRecordMode string `xorm:"varchar(100)" json:"llmRecordMode"`
LlmRecordQueueCapacity int `xorm:"int" json:"llmRecordQueueCapacity"`
LlmRecordRetentionDays int `xorm:"int" json:"llmRecordRetentionDays"`
LlmRecordMaxRecords int `xorm:"int" json:"llmRecordMaxRecords"`
LlmRecordMaxPayloadBytes int `xorm:"int" json:"llmRecordMaxPayloadBytes"`
LlmPricingFile string `xorm:"varchar(500)" json:"llmPricingFile"`
AgentPatchStateDir string `xorm:"varchar(500)" json:"agentPatchStateDir"`
AgentRecordCapacity int `xorm:"int" json:"agentRecordCapacity"`
AgentMonitorPollSeconds int `xorm:"int" json:"agentMonitorPollSeconds"`
CasdoorEndpoint string `xorm:"varchar(500)" json:"casdoorEndpoint"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(200)" json:"clientSecret"`
CasdoorOrganization string `xorm:"varchar(100)" json:"casdoorOrganization"`
CasdoorApplication string `xorm:"varchar(100)" json:"casdoorApplication"`
ApiKeyEncryptionKey string `xorm:"varchar(200)" json:"apiKeyEncryptionKey"`
// RelayToken is what an agent sends to the local relay. It is generated on
// first start; clearing it on the Settings page issues a new one.
RelayToken string `xorm:"varchar(200)" json:"relayToken"`
HttpProxy string `xorm:"varchar(200)" json:"httpProxy"`
AcmeEmail string `xorm:"varchar(200)" json:"acmeEmail"`
AcmePrivateKey string `xorm:"mediumtext" json:"acmePrivateKey"`
AppDir string `xorm:"varchar(500)" json:"appDir"`
Language string `xorm:"varchar(100)" json:"language"`
AppMap string `xorm:"varchar(1000)" json:"appMap"`
ClientIdPrefix string `xorm:"varchar(100)" json:"clientIdPrefix"`
ClientSecretPrefix string `xorm:"varchar(100)" json:"clientSecretPrefix"`
DbRegionId string `xorm:"varchar(100)" json:"dbRegionId"`
DbAccessKeyId string `xorm:"varchar(200)" json:"dbAccessKeyId"`
DbAccessKeySecret string `xorm:"varchar(200)" json:"dbAccessKeySecret"`
DbInstanceId string `xorm:"varchar(100)" json:"dbInstanceId"`
DbHost string `xorm:"varchar(200)" json:"dbHost"`
DbUser string `xorm:"varchar(100)" json:"dbUser"`
DbPass string `xorm:"varchar(200)" json:"dbPass"`
}
// SyncSettingToConf makes the row the answer conf.GetConfigString() gives, so
// every existing caller of conf keeps working and none of them has to know that
// the value now comes from the database. Keys not listed here stay in
// conf/app.conf: the database connection and the management port are read
// before this row can be, and "isDemoMode" turns the API read-only, which would
// take the Settings page that turns it off down with it.
func SyncSettingToConf(setting *Setting) {
conf.SetSettingOverrides(map[string]string{
"gatewayEnabled": strconv.FormatBool(setting.GatewayEnabled),
"gatewayHttpPort": strconv.Itoa(setting.GatewayHttpPort),
"gatewayHttpsPort": strconv.Itoa(setting.GatewayHttpsPort),
"llmRecordMode": setting.LlmRecordMode,
"llmRecordQueueCapacity": strconv.Itoa(setting.LlmRecordQueueCapacity),
"llmRecordRetentionDays": strconv.Itoa(setting.LlmRecordRetentionDays),
"llmRecordMaxRecords": strconv.Itoa(setting.LlmRecordMaxRecords),
"llmRecordMaxPayloadBytes": strconv.Itoa(setting.LlmRecordMaxPayloadBytes),
"llmPricingFile": setting.LlmPricingFile,
"agentPatchStateDir": setting.AgentPatchStateDir,
"agentRecordCapacity": strconv.Itoa(setting.AgentRecordCapacity),
"agentMonitorPollSeconds": strconv.Itoa(setting.AgentMonitorPollSeconds),
"casdoorEndpoint": setting.CasdoorEndpoint,
"clientId": setting.ClientId,
"clientSecret": setting.ClientSecret,
"casdoorOrganization": setting.CasdoorOrganization,
"casdoorApplication": setting.CasdoorApplication,
"apiKeyEncryptionKey": setting.ApiKeyEncryptionKey,
"relayToken": setting.RelayToken,
"httpProxy": setting.HttpProxy,
"acmeEmail": setting.AcmeEmail,
"acmePrivateKey": setting.AcmePrivateKey,
"appDir": setting.AppDir,
"language": setting.Language,
"appMap": setting.AppMap,
"clientIdPrefix": setting.ClientIdPrefix,
"clientSecretPrefix": setting.ClientSecretPrefix,
"dbRegionId": setting.DbRegionId,
"dbAccessKeyId": setting.DbAccessKeyId,
"dbAccessKeySecret": setting.DbAccessKeySecret,
"dbInstanceId": setting.DbInstanceId,
"dbHost": setting.DbHost,
"dbUser": setting.DbUser,
"dbPass": setting.DbPass,
})
}
// relayTokenLength is long enough that the token cannot be guessed and short
// enough to stay readable in a shell snippet.
const relayTokenLength = 32
func newRelayToken() string {
return "cg-" + util.GenerateToken(relayTokenLength)
}
func getSetting(owner string, name string) (*Setting, error) {
setting := Setting{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&setting)
if err != nil {
return nil, err
}
if existed {
return &setting, nil
}
return nil, nil
}
func GetSetting(id string) (*Setting, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getSetting(owner, name)
}
func GetBuiltInSetting() (*Setting, error) {
return GetSetting(BuiltInSettingId)
}
func UpdateSetting(id string, setting *Setting) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if s, err := getSetting(owner, name); err != nil {
return false, err
} else if s == nil {
return false, nil
}
setting.Owner, setting.Name = owner, name
// An empty token would leave the relay open, so clearing the field is taken
// as "issue a new one" rather than "turn the check off".
if setting.RelayToken == "" {
setting.RelayToken = newRelayToken()
}
_, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(setting)
if err != nil {
return false, err
}
SyncSettingToConf(setting)
return true, nil
}
// InitBuiltInSetting creates the row on first start, seeded from conf/app.conf
// so an existing installation keeps the settings it already had, and loads it
// into conf. It runs before anything else reads conf, because from here on the
// row is what conf answers with.
func InitBuiltInSetting() {
setting, err := GetBuiltInSetting()
if err != nil {
panic(err)
}
if setting == nil {
setting = newSettingFromConf()
if _, err = ormer.Engine.Insert(setting); err != nil {
panic(err)
}
}
// An installation that predates the relay token has an empty column, so the
// token is issued here rather than only when the row is created.
if setting.RelayToken == "" {
setting.RelayToken = newRelayToken()
if _, err = ormer.Engine.ID(core.PK{setting.Owner, setting.Name}).Cols("relay_token").Update(setting); err != nil {
panic(err)
}
}
SyncSettingToConf(setting)
}
// newSettingFromConf reads the defaults through conf's own getters, so a key
// that conf/app.conf never mentioned is stored as the default the code would
// have used anyway rather than as an empty value.
func newSettingFromConf() *Setting {
return &Setting{
Owner: "admin",
Name: "setting-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Setting",
GatewayEnabled: conf.IsGatewayEnabled(),
GatewayHttpPort: conf.GetGatewayHttpPort(),
GatewayHttpsPort: conf.GetGatewayHttpsPort(),
LlmRecordMode: conf.GetLlmRecordMode(),
LlmRecordQueueCapacity: conf.GetLlmRecordQueueCapacity(),
LlmRecordRetentionDays: conf.GetLlmRecordRetentionDays(),
LlmRecordMaxRecords: conf.GetLlmRecordMaxRecords(),
LlmRecordMaxPayloadBytes: conf.GetLlmRecordMaxPayloadBytes(),
LlmPricingFile: conf.GetLlmPricingFile(),
AgentPatchStateDir: conf.GetAgentPatchStateDir(),
AgentRecordCapacity: conf.GetAgentRecordCapacity(),
AgentMonitorPollSeconds: conf.GetAgentMonitorPollSeconds(),
CasdoorEndpoint: conf.GetConfigStringUnquoted("casdoorEndpoint"),
ClientId: conf.GetConfigStringUnquoted("clientId"),
ClientSecret: conf.GetConfigStringUnquoted("clientSecret"),
CasdoorOrganization: conf.GetConfigStringUnquoted("casdoorOrganization"),
CasdoorApplication: conf.GetConfigStringUnquoted("casdoorApplication"),
ApiKeyEncryptionKey: conf.GetConfigStringUnquoted("apiKeyEncryptionKey"),
RelayToken: newRelayToken(),
HttpProxy: conf.GetConfigStringUnquoted("httpProxy"),
AcmeEmail: conf.GetConfigStringUnquoted("acmeEmail"),
AcmePrivateKey: conf.GetConfigStringUnquoted("acmePrivateKey"),
AppDir: conf.GetConfigStringUnquoted("appDir"),
Language: conf.GetConfigStringUnquoted("language"),
AppMap: conf.GetConfigStringUnquoted("appMap"),
ClientIdPrefix: conf.GetConfigStringUnquoted("clientIdPrefix"),
ClientSecretPrefix: conf.GetConfigStringUnquoted("clientSecretPrefix"),
DbRegionId: conf.GetConfigStringUnquoted("dbRegionId"),
DbAccessKeyId: conf.GetConfigStringUnquoted("dbAccessKeyId"),
DbAccessKeySecret: conf.GetConfigStringUnquoted("dbAccessKeySecret"),
DbInstanceId: conf.GetConfigStringUnquoted("dbInstanceId"),
DbHost: conf.GetConfigStringUnquoted("dbHost"),
DbUser: conf.GetConfigStringUnquoted("dbUser"),
DbPass: conf.GetConfigStringUnquoted("dbPass"),
}
}