diff --git a/object/application.go b/object/application.go
index 49826c5f7..30016cd24 100644
--- a/object/application.go
+++ b/object/application.go
@@ -46,6 +46,7 @@ type Application struct {
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
+ EnableSamlCompress bool `json:"enableSamlCompress"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
diff --git a/object/saml_idp.go b/object/saml_idp.go
index b7b0fe34e..10fbf443f 100644
--- a/object/saml_idp.go
+++ b/object/saml_idp.go
@@ -36,7 +36,7 @@ import (
)
//returns a saml2 response
-func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, redirectUri []string) (*etree.Element, error) {
+func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
@@ -51,7 +51,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
samlResponse.CreateAttr("Version", "2.0")
samlResponse.CreateAttr("IssueInstant", now)
samlResponse.CreateAttr("Destination", destination)
- samlResponse.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
+ samlResponse.CreateAttr("InResponseTo", requestId)
samlResponse.CreateElement("saml:Issuer").SetText(host)
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
@@ -68,7 +68,7 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
- subjectConfirmationData.CreateAttr("InResponseTo", fmt.Sprintf("_%s", arId))
+ subjectConfirmationData.CreateAttr("InResponseTo", requestId)
subjectConfirmationData.CreateAttr("Recipient", destination)
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
condition := assertion.CreateElement("saml:Conditions")
@@ -225,14 +225,15 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
return &d, nil
}
-//GenerateSamlResponse generates a SAML2.0 response
-//parameter samlRequest is saml request in base64 format
+// GetSamlResponse generates a SAML2.0 response
+// parameter samlRequest is saml request in base64 format
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) {
- //decode samlRequest
+ // base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
+ // decompress
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
io.Copy(&buffer, rdr)
@@ -241,20 +242,21 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
- //verify samlRequest
+
+ // verify samlRequest
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
return "", "", fmt.Errorf("err: invalid issuer url")
}
- //get publickey string
+ // get public key string
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host)
- //build signedResponse
- samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, application.RedirectUris)
+ // build signedResponse
+ samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
@@ -270,15 +272,28 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
doc := etree.NewDocument()
doc.SetRoot(samlResponse)
- xmlStr, err := doc.WriteToString()
+ xmlBytes, err := doc.WriteToBytes()
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
- res := base64.StdEncoding.EncodeToString([]byte(xmlStr))
+
+ // compress
+ if application.EnableSamlCompress {
+ flated := bytes.NewBuffer(nil)
+ writer, err := flate.NewWriter(flated, flate.DefaultCompression)
+ if err != nil {
+ return "", "", fmt.Errorf("err: %s", err.Error())
+ }
+ writer.Write(xmlBytes)
+ writer.Close()
+ xmlBytes = flated.Bytes()
+ }
+ // base64 encode
+ res := base64.StdEncoding.EncodeToString(xmlBytes)
return res, authnRequest.AssertionConsumerServiceURL, nil
}
-//return a saml1.1 response(not 2.0)
+// NewSamlResponse11 return a saml1.1 response(not 2.0)
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
samlResponse := &etree.Element{
Space: "samlp",
diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js
index a306ae075..48f09fed7 100644
--- a/web/src/ApplicationEditPage.js
+++ b/web/src/ApplicationEditPage.js
@@ -474,6 +474,16 @@ class ApplicationEditPage extends React.Component {
+
+
+ {Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
+
+
+ {
+ this.updateApplicationField('enableSamlCompress', checked);
+ }} />
+
+
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
@@ -484,6 +494,14 @@ class ApplicationEditPage extends React.Component {
options={{mode: 'xml', theme: 'default'}}
onBeforeChange={(editor, data, value) => {}}
/>
+
+ } onClick={() => {
+ copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
+ Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully"));
+ }}
+ >
+ {i18next.t("application:Copy SAML metadata URL")}
+
diff --git a/web/src/ApplicationListPage.js b/web/src/ApplicationListPage.js
index f6f13b719..d547c20bf 100644
--- a/web/src/ApplicationListPage.js
+++ b/web/src/ApplicationListPage.js
@@ -35,6 +35,7 @@ class ApplicationListPage extends BaseListPage {
enableSignUp: true,
enableSigninSession: false,
enableCodeSignin: false,
+ enableSamlCompress: false,
providers: [
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
],
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index 9afd7a902..7f8a32384 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -7,11 +7,14 @@
},
"application": {
"Copy prompt page URL": "复制提醒页面URL",
+ "Copy SAML metadata URL": "复制SAML元数据URL",
"Copy signin page URL": "复制登录页面URL",
"Copy signup page URL": "复制注册页面URL",
"Edit Application": "编辑应用",
"Enable code signin": "启用验证码登录",
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
+ "Enable SAML compress": "压缩SAML响应",
+ "Enable SAML compress - Tooltip": "Casdoor作为SAML idp时,是否压缩SAML响应信息",
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
"Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册",
@@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Refresh Token过期时间",
"SAML metadata": "SAML元数据",
"SAML metadata - Tooltip": "SAML协议的元数据(Metadata)信息",
+ "SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "登录页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Signin session": "保持登录会话",
"Signup items": "注册项",