Compare commits

..

5 Commits

Author SHA1 Message Date
dacongda
e593f5be5b fix: improve code format (#2665)
* feat: replace io/ioutils pacakage with io/os package

* fix: add missing error handling
2024-02-01 23:06:12 +08:00
Dmitri Aleksandrov
0918757e85 feat: add template support for Custom HTTP SMS provider (#2662) 2024-02-01 17:50:22 +08:00
dacongda
ce0d45a70b feat: support SAML POST binding (#2661)
* fix: support saml http post binding

* fix: support saml http post binding

* fix: support saml post binding sp
2024-02-01 17:28:56 +08:00
Konstantin
c4096788b2 feat: ABAC support for /api/enforce endpoint (#2660) 2024-01-31 23:14:55 +08:00
dacongda
523186f895 feat: Support sha512 password encryption algorithm (#2657)
* add sha512 encryption support for password

* fead: add sha512 encryption support for password
2024-01-31 00:06:06 +08:00
32 changed files with 228 additions and 57 deletions

View File

@@ -80,6 +80,7 @@ p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /api/saml/redirect, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /scim, *, *
p, *, *, *, /api/webauthn, *, *

View File

@@ -19,7 +19,7 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"strconv"
@@ -912,7 +912,7 @@ func (c *ApiController) HandleSamlLogin() {
samlResponse = url.QueryEscape(samlResponse)
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
slice[4], relayState, samlResponse)
c.Redirect(targetUrl, 303)
c.Redirect(targetUrl, http.StatusSeeOther)
}
// HandleOfficialAccountEvent ...
@@ -921,7 +921,7 @@ func (c *ApiController) HandleSamlLogin() {
// @router /webhook [POST]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) HandleOfficialAccountEvent() {
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
respBytes, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -121,6 +121,10 @@ func (c *ApiController) Enforce() {
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
@@ -235,6 +239,10 @@ func (c *ApiController) BatchEnforce() {
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return

View File

@@ -16,6 +16,7 @@ package controllers
import (
"fmt"
"net/http"
"github.com/casdoor/casdoor/object"
)
@@ -34,7 +35,13 @@ func (c *ApiController) GetSamlMeta() {
return
}
metadata, err := object.GetSamlMeta(application, host)
enablePostBinding, err := c.GetBool("enablePostBinding", false)
if err != nil {
c.ResponseError(err.Error())
return
}
metadata, err := object.GetSamlMeta(application, host, enablePostBinding)
if err != nil {
c.ResponseError(err.Error())
return
@@ -43,3 +50,17 @@ func (c *ApiController) GetSamlMeta() {
c.Data["xml"] = metadata
c.ServeXML()
}
func (c *ApiController) HandleSamlRedirect() {
host := c.Ctx.Request.Host
owner := c.Ctx.Input.Param(":owner")
application := c.Ctx.Input.Param(":application")
relayState := c.Input().Get("RelayState")
samlRequest := c.Input().Get("SAMLRequest")
targetURL := object.GetSamlRedirectAddress(owner, application, relayState, samlRequest, host)
c.Redirect(targetURL, http.StatusSeeOther)
}

View File

@@ -47,6 +47,11 @@ func (c *ApiController) GetSystemInfo() {
// @router /get-version-info [get]
func (c *ApiController) GetVersionInfo() {
versionInfo, err := util.GetVersionInfo()
if err != nil {
c.ResponseError(err.Error())
return
}
if versionInfo.Version != "" {
c.ResponseOk(versionInfo)
return

View File

@@ -24,6 +24,8 @@ func GetCredManager(passwordType string) CredManager {
return NewPlainCredManager()
} else if passwordType == "salt" {
return NewSha256SaltCredManager()
} else if passwordType == "sha512-salt" {
return NewSha512SaltCredManager()
} else if passwordType == "md5-salt" {
return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" {

50
cred/sha512-salt.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2024 The Casdoor 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 cred
import (
"crypto/sha512"
"encoding/hex"
)
type Sha512SaltCredManager struct{}
func getSha512(data []byte) []byte {
hash := sha512.Sum512(data)
return hash[:]
}
func getSha512HexDigest(s string) string {
b := getSha512([]byte(s))
res := hex.EncodeToString(b)
return res
}
func NewSha512SaltCredManager() *Sha512SaltCredManager {
cm := &Sha512SaltCredManager{}
return cm
}
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
res := getSha512HexDigest(password)
if organizationSalt != "" {
res = getSha512HexDigest(res + organizationSalt)
}
return res
}
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}

View File

@@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@@ -218,7 +217,7 @@ func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
@@ -255,7 +254,7 @@ func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (strin
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

View File

@@ -37,7 +37,7 @@ type Adapter struct {
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Password string `xorm:"varchar(150)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
*xormadapter.Adapter `xorm:"-" json:"-"`

View File

@@ -52,31 +52,32 @@ type Application struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(200)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
Cert string `xorm:"varchar(100)" json:"cert"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableAutoSignin bool `json:"enableAutoSignin"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
CertPublicKey string `xorm:"-" json:"certPublicKey"`
Tags []string `xorm:"mediumtext" json:"tags"`
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(200)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
Cert string `xorm:"varchar(100)" json:"cert"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableAutoSignin bool `json:"enableAutoSignin"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
CertPublicKey string `xorm:"-" json:"certPublicKey"`
Tags []string `xorm:"mediumtext" json:"tags"`
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`

Binary file not shown.

View File

@@ -198,7 +198,7 @@ type Attribute struct {
Values []string `xml:"AttributeValue"`
}
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
func GetSamlMeta(application *Application, host string, enablePostBinding bool) (*IdpEntityDescriptor, error) {
cert, err := getCertByApplication(application)
if err != nil {
return nil, err
@@ -217,6 +217,13 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
originFrontend, originBackend := getOriginFromHost(host)
idpLocation := ""
if enablePostBinding {
idpLocation = fmt.Sprintf("%s/api/saml/redirect/%s/%s", originBackend, application.Owner, application.Name)
} else {
idpLocation = fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name)
}
d := IdpEntityDescriptor{
XMLName: xml.Name{
Local: "md:EntityDescriptor",
@@ -248,7 +255,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
},
SingleSignOnService: SingleSignOnService{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
Location: idpLocation,
},
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
},
@@ -442,3 +449,8 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
return samlResponse
}
func GetSamlRedirectAddress(owner string, application string, relayState string, samlRequest string, host string) string {
originF, _ := getOriginFromHost(host)
return fmt.Sprintf("%s/login/saml/authorize/%s/%s?relayState=%s&samlRequest=%s", originF, owner, application, relayState, samlRequest)
}

View File

@@ -27,7 +27,7 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else if provider.Type == "Custom HTTP SMS" {
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title)
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title, provider.TemplateCode)
} else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
}

View File

@@ -27,20 +27,26 @@ type HttpSmsClient struct {
endpoint string
method string
paramName string
template string
}
func newHttpSmsClient(endpoint string, method string, paramName string) (*HttpSmsClient, error) {
func newHttpSmsClient(endpoint, method, paramName, template string) (*HttpSmsClient, error) {
if template == "" {
template = "%s"
}
client := &HttpSmsClient{
endpoint: endpoint,
method: method,
paramName: paramName,
template: template,
}
return client, nil
}
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
phoneNumber := targetPhoneNumber[0]
content := param["code"]
code := param["code"]
content := fmt.Sprintf(c.template, code)
var req *http.Request
var err error

View File

@@ -43,7 +43,7 @@ type Syncer struct {
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Password string `xorm:"varchar(150)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`

View File

@@ -52,7 +52,7 @@ type UserWithoutThirdIdp struct {
Id string `xorm:"varchar(100) index" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
Password string `xorm:"varchar(150)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`

View File

@@ -54,7 +54,7 @@ type User struct {
Id string `xorm:"varchar(100) index" json:"id"`
ExternalId string `xorm:"varchar(100) index" json:"externalId"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
Password string `xorm:"varchar(150)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`

View File

@@ -164,6 +164,10 @@ func getUrlPath(urlPath string) string {
return "/api/webauthn"
}
if strings.HasPrefix(urlPath, "/api/saml/redirect") {
return "/api/saml/redirect"
}
return urlPath
}

View File

@@ -60,6 +60,7 @@ func initAPI() {
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
beego.Router("/api/saml/redirect/:owner/:application", &controllers.ApiController{}, "*:HandleSamlRedirect")
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")

View File

@@ -5592,6 +5592,9 @@
"enableSamlCompress": {
"type": "boolean"
},
"enableSamlPostBinding": {
"type": "boolean"
},
"enableSignUp": {
"type": "boolean"
},

View File

@@ -14,7 +14,10 @@
package util
import "encoding/json"
import (
"encoding/json"
"reflect"
)
func StructToJson(v interface{}) string {
data, err := json.Marshal(v)
@@ -37,3 +40,30 @@ func StructToJsonFormatted(v interface{}) string {
func JsonToStruct(data string, v interface{}) error {
return json.Unmarshal([]byte(data), v)
}
func TryJsonToAnonymousStruct(j string) (interface{}, error) {
var data map[string]interface{}
if err := json.Unmarshal([]byte(j), &data); err != nil {
return nil, err
}
// Create a slice of StructFields
fields := make([]reflect.StructField, 0, len(data))
for k, v := range data {
fields = append(fields, reflect.StructField{
Name: k,
Type: reflect.TypeOf(v),
})
}
// Create the struct type
t := reflect.StructOf(fields)
// Unmarshal again, this time to the new struct type
val := reflect.New(t)
i := val.Interface()
if err := json.Unmarshal([]byte(j), &i); err != nil {
return nil, err
}
return i, nil
}

View File

@@ -16,7 +16,6 @@ package util
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
@@ -40,7 +39,7 @@ func GetPath(path string) string {
func ListFiles(path string) []string {
res := []string{}
files, err := ioutil.ReadDir(path)
files, err := os.ReadDir(path)
if err != nil {
panic(err)
}

View File

@@ -14,10 +14,10 @@
package util
import "io/ioutil"
import "os"
func GetUploadXlsxPath(fileId string) string {
file, err := ioutil.TempFile("", fileId)
file, err := os.CreateTemp("", fileId)
if err != nil {
panic(err)
}

View File

@@ -324,9 +324,16 @@ func GetUsernameFromEmail(email string) string {
}
func StringToInterfaceArray(array []string) []interface{} {
var interfaceArray []interface{}
for _, v := range array {
interfaceArray = append(interfaceArray, v)
var (
interfaceArray []interface{}
elem interface{}
)
for _, elem = range array {
jStruct, err := TryJsonToAnonymousStruct(elem.(string))
if err == nil {
elem = jStruct
}
interfaceArray = append(interfaceArray, elem)
}
return interfaceArray
}

View File

@@ -119,6 +119,9 @@ func GetVersionInfo() (*VersionInfo, error) {
}
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
if err != nil {
return res, err
}
commitOffset := 0
version := ""

View File

@@ -70,6 +70,9 @@ func TestGetVersion(t *testing.T) {
testHash := plumbing.NewHash("f8bc87eb4e5ba3256424cf14aafe0549f812f1cf")
cIter, err := r.Log(&git.LogOptions{From: testHash})
if err != nil {
t.Log(err)
}
aheadCnt := 0
releaseVersion := ""

View File

@@ -116,7 +116,6 @@ class ApplicationEditPage extends React.Component {
this.getApplication();
this.getOrganizations();
this.getProviders();
this.getSamlMetadata();
}
getApplication() {
@@ -146,6 +145,8 @@ class ApplicationEditPage extends React.Component {
});
this.getCerts(application.organization);
this.getSamlMetadata(application.enableSamlPostBinding);
});
}
@@ -186,8 +187,8 @@ class ApplicationEditPage extends React.Component {
});
}
getSamlMetadata() {
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName)
getSamlMetadata(checked) {
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName, checked)
.then((data) => {
this.setState({
samlMetadata: data,
@@ -663,6 +664,17 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSamlPostBinding} onChange={checked => {
this.updateApplicationField("enableSamlPostBinding", checked);
this.getSamlMetadata(checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} :
@@ -688,7 +700,7 @@ class ApplicationEditPage extends React.Component {
/>
<br />
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}&post=${this.state.application.enableSamlPostBinding}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}
>

View File

@@ -184,7 +184,7 @@ class OrganizationEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
options={["plain", "salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"].map(item => Setting.getOption(item, item))}
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"].map(item => Setting.getOption(item, item))}
/>
</Col>
</Row>

View File

@@ -1041,7 +1041,7 @@ class ProviderEditPage extends React.Component {
</Row>
)
}
{["Custom HTTP SMS", "Infobip SMS"].includes(this.state.provider.type) ?
{["Infobip SMS"].includes(this.state.provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>

View File

@@ -89,8 +89,8 @@ export function deleteApplication(application) {
}).then(res => res.json());
}
export function getSamlMetadata(owner, name) {
return fetch(`${Setting.ServerUrl}/api/saml/metadata?application=${owner}/${encodeURIComponent(name)}`, {
export function getSamlMetadata(owner, name, enablePostBinding) {
return fetch(`${Setting.ServerUrl}/api/saml/metadata?application=${owner}/${encodeURIComponent(name)}&enablePostBinding=${enablePostBinding}`, {
method: "GET",
credentials: "include",
headers: {

View File

@@ -36,6 +36,8 @@
"Enable SAML C14N10 - Tooltip": "Use C14N10 instead of C14N11 in SAML",
"Enable SAML compression": "Enable SAML compression",
"Enable SAML compression - Tooltip": "Whether to compress SAML response messages when Casdoor is used as SAML idp",
"Enable SAML POST binding": "Enable SAML POST binding",
"Enable SAML POST binding - Tooltip": "The HTTP POST binding uses input fields in a HTML form to send SAML messages, Enable when your SP use it",
"Enable side panel": "Enable side panel",
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
"Enable signup": "Enable signup",

View File

@@ -36,6 +36,8 @@
"Enable SAML C14N10 - Tooltip": "在SAML协议里使用C14N10而不是C14N11",
"Enable SAML compression": "压缩SAML响应",
"Enable SAML compression - Tooltip": "Casdoor作为SAML IdP时是否压缩SAML响应信息",
"Enable SAML POST binding": "启用SAML POST Binding",
"Enable SAML POST binding - Tooltip": "HTTP POST绑定使用HTML表单中的输入字段发送SAML消息当SP使用它时启用",
"Enable side panel": "启用侧面板",
"Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话",
"Enable signup": "启用注册",