mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: support SAML POST binding (#2661)
* fix: support saml http post binding * fix: support saml http post binding * fix: support saml post binding sp
This commit is contained in:
parent
c4096788b2
commit
ce0d45a70b
@ -80,6 +80,7 @@ p, *, *, *, /.well-known/jwks, *, *
|
|||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
p, *, *, GET, /api/saml/metadata, *, *
|
p, *, *, GET, /api/saml/metadata, *, *
|
||||||
|
p, *, *, *, /api/saml/redirect, *, *
|
||||||
p, *, *, *, /cas, *, *
|
p, *, *, *, /cas, *, *
|
||||||
p, *, *, *, /scim, *, *
|
p, *, *, *, /scim, *, *
|
||||||
p, *, *, *, /api/webauthn, *, *
|
p, *, *, *, /api/webauthn, *, *
|
||||||
|
@ -912,7 +912,7 @@ func (c *ApiController) HandleSamlLogin() {
|
|||||||
samlResponse = url.QueryEscape(samlResponse)
|
samlResponse = url.QueryEscape(samlResponse)
|
||||||
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
|
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
|
||||||
slice[4], relayState, samlResponse)
|
slice[4], relayState, samlResponse)
|
||||||
c.Redirect(targetUrl, 303)
|
c.Redirect(targetUrl, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleOfficialAccountEvent ...
|
// HandleOfficialAccountEvent ...
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
)
|
)
|
||||||
@ -34,7 +35,13 @@ func (c *ApiController) GetSamlMeta() {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -43,3 +50,17 @@ func (c *ApiController) GetSamlMeta() {
|
|||||||
c.Data["xml"] = metadata
|
c.Data["xml"] = metadata
|
||||||
c.ServeXML()
|
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)
|
||||||
|
}
|
||||||
|
@ -52,31 +52,32 @@ type Application struct {
|
|||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
EnablePassword bool `json:"enablePassword"`
|
EnablePassword bool `json:"enablePassword"`
|
||||||
EnableSignUp bool `json:"enableSignUp"`
|
EnableSignUp bool `json:"enableSignUp"`
|
||||||
EnableSigninSession bool `json:"enableSigninSession"`
|
EnableSigninSession bool `json:"enableSigninSession"`
|
||||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
|
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
|
BIN
object/cert.go~
BIN
object/cert.go~
Binary file not shown.
@ -198,7 +198,7 @@ type Attribute struct {
|
|||||||
Values []string `xml:"AttributeValue"`
|
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)
|
cert, err := getCertByApplication(application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -217,6 +217,13 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
|||||||
|
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
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{
|
d := IdpEntityDescriptor{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Local: "md:EntityDescriptor",
|
Local: "md:EntityDescriptor",
|
||||||
@ -248,7 +255,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
|||||||
},
|
},
|
||||||
SingleSignOnService: SingleSignOnService{
|
SingleSignOnService: SingleSignOnService{
|
||||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
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",
|
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
|
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)
|
||||||
|
}
|
||||||
|
@ -164,6 +164,10 @@ func getUrlPath(urlPath string) string {
|
|||||||
return "/api/webauthn"
|
return "/api/webauthn"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(urlPath, "/api/saml/redirect") {
|
||||||
|
return "/api/saml/redirect"
|
||||||
|
}
|
||||||
|
|
||||||
return urlPath
|
return urlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||||
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
|
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/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
|
||||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||||
|
@ -5592,6 +5592,9 @@
|
|||||||
"enableSamlCompress": {
|
"enableSamlCompress": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"enableSamlPostBinding": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"enableSignUp": {
|
"enableSignUp": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
@ -116,7 +116,6 @@ class ApplicationEditPage extends React.Component {
|
|||||||
this.getApplication();
|
this.getApplication();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getProviders();
|
this.getProviders();
|
||||||
this.getSamlMetadata();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
@ -146,6 +145,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.getCerts(application.organization);
|
this.getCerts(application.organization);
|
||||||
|
|
||||||
|
this.getSamlMetadata(application.enableSamlPostBinding);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +187,8 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSamlMetadata() {
|
getSamlMetadata(checked) {
|
||||||
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName)
|
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName, checked)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
samlMetadata: data,
|
samlMetadata: data,
|
||||||
@ -663,6 +664,17 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} :
|
||||||
@ -688,7 +700,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
<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"));
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -89,8 +89,8 @@ export function deleteApplication(application) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSamlMetadata(owner, name) {
|
export function getSamlMetadata(owner, name, enablePostBinding) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/saml/metadata?application=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/saml/metadata?application=${owner}/${encodeURIComponent(name)}&enablePostBinding=${enablePostBinding}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
"Enable SAML C14N10 - Tooltip": "Use C14N10 instead of C14N11 in SAML",
|
"Enable SAML C14N10 - Tooltip": "Use C14N10 instead of C14N11 in SAML",
|
||||||
"Enable SAML compression": "Enable SAML compression",
|
"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 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 side panel": "Enable side panel",
|
||||||
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
|
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
|
||||||
"Enable signup": "Enable signup",
|
"Enable signup": "Enable signup",
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
"Enable SAML C14N10 - Tooltip": "在SAML协议里使用C14N10,而不是C14N11",
|
"Enable SAML C14N10 - Tooltip": "在SAML协议里使用C14N10,而不是C14N11",
|
||||||
"Enable SAML compression": "压缩SAML响应",
|
"Enable SAML compression": "压缩SAML响应",
|
||||||
"Enable SAML compression - Tooltip": "Casdoor作为SAML IdP时,是否压缩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 side panel": "启用侧面板",
|
||||||
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
|
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
|
||||||
"Enable signup": "启用注册",
|
"Enable signup": "启用注册",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user