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) => {}} /> +
+
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": "注册项",