diff --git a/controllers/auth.go b/controllers/auth.go
index 5bf4fcb9..1f45d78a 100644
--- a/controllers/auth.go
+++ b/controllers/auth.go
@@ -477,11 +477,10 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
return
}
-
userInfo := &idp.UserInfo{}
if provider.Category == "SAML" {
// SAML
- userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
+ userInfo, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
@@ -524,7 +523,8 @@ func (c *ApiController) Login() {
if authForm.Method == "signup" {
user := &object.User{}
if provider.Category == "SAML" {
- user, err = object.GetUser(util.GetId(application.Organization, userInfo.Id))
+ // The userInfo.Id is the NameID in SAML response, it could be name / email / phone
+ user, err = object.GetUserByFields(application.Organization, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@@ -679,6 +679,7 @@ func (c *ApiController) Login() {
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" {
+ // TODO: since we get the user info from SAML response, we can try to create the user
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
}
// resp = &Response{Status: "ok", Msg: "", Data: res}
diff --git a/object/saml_sp.go b/object/saml_sp.go
index b9c3c622..cdca0f2a 100644
--- a/object/saml_sp.go
+++ b/object/saml_sp.go
@@ -23,23 +23,49 @@ import (
"regexp"
"strings"
+ "github.com/casdoor/casdoor/idp"
+ "github.com/mitchellh/mapstructure"
+
"github.com/casdoor/casdoor/i18n"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
-func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
+func ParseSamlResponse(samlResponse string, provider *Provider, host string) (*idp.UserInfo, error) {
samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(provider, samlResponse, host)
if err != nil {
- return "", err
+ return nil, err
}
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil {
- return "", err
+ return nil, err
}
- return assertionInfo.NameID, err
+
+ userInfoMap := make(map[string]string)
+ for spAttr, idpAttr := range provider.UserMapping {
+ for _, attr := range assertionInfo.Values {
+ if attr.Name == idpAttr {
+ userInfoMap[spAttr] = attr.Values[0].Value
+ }
+ }
+ }
+ userInfoMap["id"] = assertionInfo.NameID
+
+ customUserInfo := &idp.CustomUserInfo{}
+ err = mapstructure.Decode(userInfoMap, customUserInfo)
+ if err != nil {
+ return nil, err
+ }
+ userInfo := &idp.UserInfo{
+ Id: customUserInfo.Id,
+ Username: customUserInfo.Username,
+ DisplayName: customUserInfo.DisplayName,
+ Email: customUserInfo.Email,
+ AvatarUrl: customUserInfo.AvatarUrl,
+ }
+ return userInfo, err
}
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
@@ -146,14 +172,24 @@ func getCertificateFromSamlResponse(samlResponse string, providerType string) (s
if err != nil {
return "", err
}
-
- deStr := strings.Replace(string(de), "\n", "", -1)
- tagMap := map[string]string{
- "Aliyun IDaaS": "ds",
- "Keycloak": "dsig",
- }
+ var (
+ expression string
+ deStr = strings.Replace(string(de), "\n", "", -1)
+ tagMap = map[string]string{
+ "Aliyun IDaaS": "ds",
+ "Keycloak": "dsig",
+ }
+ )
tag := tagMap[providerType]
- expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)%s:X509Certificate>", tag, tag)
+ if tag == "" {
+ // ...
+ // ...
+ // ...
+ // ...
+ expression = "<[^>]*:?X509Certificate>([\\s\\S]*?)<[^>]*:?X509Certificate>"
+ } else {
+ expression = fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)%s:X509Certificate>", tag, tag)
+ }
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1], nil
}
diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js
index 9f03ad93..86b3cf1c 100644
--- a/web/src/ProviderEditPage.js
+++ b/web/src/ProviderEditPage.js
@@ -379,10 +379,11 @@ class ProviderEditPage extends React.Component {
loadSamlConfiguration() {
const parser = new DOMParser();
- const xmlDoc = parser.parseFromString(this.state.provider.metadata, "text/xml");
- const cert = xmlDoc.getElementsByTagName("ds:X509Certificate")[0].childNodes[0].nodeValue;
- const endpoint = xmlDoc.getElementsByTagName("md:SingleSignOnService")[0].getAttribute("Location");
- const issuerUrl = xmlDoc.getElementsByTagName("md:EntityDescriptor")[0].getAttribute("entityID");
+ const rawXml = this.state.provider.metadata.replace("\n", "");
+ const xmlDoc = parser.parseFromString(rawXml, "text/xml");
+ const cert = xmlDoc.querySelector("X509Certificate").childNodes[0].nodeValue.replace(" ", "");
+ const endpoint = xmlDoc.querySelector("SingleSignOnService").getAttribute("Location");
+ const issuerUrl = xmlDoc.querySelector("EntityDescriptor").getAttribute("entityID");
this.updateProviderField("idP", cert);
this.updateProviderField("endpoint", endpoint);
this.updateProviderField("issuerUrl", issuerUrl);
@@ -491,7 +492,7 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("type", value);
if (value === "Local File System") {
this.updateProviderField("domain", Setting.getFullServerUrl());
- } else if (value === "Custom") {
+ } else if (value === "Custom" && this.state.provider.category === "OAuth") {
this.updateProviderField("customAuthUrl", "https://door.casdoor.com/login/oauth/authorize");
this.updateProviderField("scopes", "openid profile email");
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
@@ -553,48 +554,54 @@ class ProviderEditPage extends React.Component {
)
}
{
- this.state.provider.type !== "Custom" ? null : (
+ this.state.provider.type === "Custom" ? (
-
-
- {Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
-
-
- {
- this.updateProviderField("customAuthUrl", e.target.value);
- }} />
-
-
-
-
- {Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
-
-
- {
- this.updateProviderField("customTokenUrl", e.target.value);
- }} />
-
-
-
-
- {Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
-
-
- {
- this.updateProviderField("scopes", e.target.value);
- }} />
-
-
-
-
- {Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
-
-
- {
- this.updateProviderField("customUserInfoUrl", e.target.value);
- }} />
-
-
+ {
+ this.state.provider.category === "OAuth" ? (
+
+
+
+ {Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
+
+
+ {
+ this.updateProviderField("customAuthUrl", e.target.value);
+ }} />
+
+
+
+
+ {Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
+
+
+ {
+ this.updateProviderField("customTokenUrl", e.target.value);
+ }} />
+
+
+
+
+ {Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
+
+
+ {
+ this.updateProviderField("scopes", e.target.value);
+ }} />
+
+
+
+
+ {Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
+
+
+ {
+ this.updateProviderField("customUserInfoUrl", e.target.value);
+ }} />
+
+
+
+ ) : null
+ }
{Setting.getLabel(i18next.t("provider:User mapping"), i18next.t("provider:User mapping - Tooltip"))} :
@@ -631,7 +638,7 @@ class ProviderEditPage extends React.Component {
- )
+ ) : null
}
{
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
diff --git a/web/src/Setting.js b/web/src/Setting.js
index b91be627..7b78792e 100644
--- a/web/src/Setting.js
+++ b/web/src/Setting.js
@@ -209,6 +209,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/",
},
+ "Custom": {
+ logo: `${StaticBaseUrl}/img/social_custom.png`,
+ url: "https://door.casdoor.com/",
+ },
},
Payment: {
"Dummy": {
@@ -866,10 +870,10 @@ export function getClickable(text) {
}
export function getProviderLogoURL(provider) {
+ if (provider.type === "Custom" && provider.customLogo) {
+ return provider.customLogo;
+ }
if (provider.category === "OAuth") {
- if (provider.type === "Custom" && provider.customLogo) {
- return provider.customLogo;
- }
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else {
const info = OtherProviderInfo[provider.category][provider.type];
@@ -1014,6 +1018,7 @@ export function getProviderTypeOptions(category) {
return ([
{id: "Aliyun IDaaS", name: "Aliyun IDaaS"},
{id: "Keycloak", name: "Keycloak"},
+ {id: "Custom", name: "Custom"},
]);
} else if (category === "Payment") {
return ([