diff --git a/authz/authz.go b/authz/authz.go index c437954c..e2ae4144 100644 --- a/authz/authz.go +++ b/authz/authz.go @@ -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, *, * diff --git a/controllers/auth.go b/controllers/auth.go index e5541f77..50a04e8d 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -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 ... diff --git a/controllers/saml.go b/controllers/saml.go index 8d7eeb26..070bc2f8 100644 --- a/controllers/saml.go +++ b/controllers/saml.go @@ -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) +} diff --git a/object/application.go b/object/application.go index 1c80d785..d85d5974 100644 --- a/object/application.go +++ b/object/application.go @@ -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"` diff --git a/object/cert.go~ b/object/cert.go~ deleted file mode 100644 index c38896e8..00000000 Binary files a/object/cert.go~ and /dev/null differ diff --git a/object/saml_idp.go b/object/saml_idp.go index f908d9bb..b318a96b 100644 --- a/object/saml_idp.go +++ b/object/saml_idp.go @@ -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) +} diff --git a/routers/authz_filter.go b/routers/authz_filter.go index 32994537..3ee30050 100644 --- a/routers/authz_filter.go +++ b/routers/authz_filter.go @@ -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 } diff --git a/routers/router.go b/routers/router.go index 7c97f7c3..dbf8658c 100644 --- a/routers/router.go +++ b/routers/router.go @@ -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") diff --git a/swagger/swagger.json b/swagger/swagger.json index 1f9d9c1d..b54d5f8f 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -5592,6 +5592,9 @@ "enableSamlCompress": { "type": "boolean" }, + "enableSamlPostBinding": { + "type": "boolean" + }, "enableSignUp": { "type": "boolean" }, diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js index 9c47dcdb..ad92e4f8 100644 --- a/web/src/ApplicationEditPage.js +++ b/web/src/ApplicationEditPage.js @@ -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 { }} /> + + + {Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} : + + + { + this.updateApplicationField("enableSamlPostBinding", checked); + this.getSamlMetadata(checked); + }} /> + + {Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} : @@ -688,7 +700,7 @@ class ApplicationEditPage extends React.Component { />