mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-04 03:50:30 +08:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d647eed22a | ||
![]() |
717c53f6e5 | ||
![]() |
097adac871 | ||
![]() |
74543b9533 | ||
![]() |
110dc04179 | ||
![]() |
6464bd10dc | ||
![]() |
db878a890e | ||
![]() |
12d6d8e6ce | ||
![]() |
8ed6e4f934 | ||
![]() |
ed9732caf9 | ||
![]() |
0de4e7da38 | ||
![]() |
a330fbc11f | ||
![]() |
ed158d4981 | ||
![]() |
8df965b98d | ||
![]() |
2c3749820e | ||
![]() |
0b17cb9746 | ||
![]() |
e2ce9ad625 | ||
![]() |
64491abc64 | ||
![]() |
934a8947c8 | ||
![]() |
943edfb48b | ||
![]() |
0d02b5e768 | ||
![]() |
ba8d0b5f46 | ||
![]() |
973a1df6c2 | ||
![]() |
05bfd3a3a3 | ||
![]() |
69aa3c8a8b | ||
![]() |
a1b010a406 | ||
![]() |
89e92cbd47 | ||
![]() |
d4c8193357 |
@@ -13,7 +13,7 @@
|
|||||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, *
|
|||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
p, *, *, GET, /.well-known/webfinger, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
|
@@ -26,6 +26,10 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return NewDefaultCaptchaProvider()
|
return NewDefaultCaptchaProvider()
|
||||||
case "reCAPTCHA":
|
case "reCAPTCHA":
|
||||||
return NewReCaptchaProvider()
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v2":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v3":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
case "Aliyun Captcha":
|
case "Aliyun Captcha":
|
||||||
return NewAliyunCaptchaProvider()
|
return NewAliyunCaptchaProvider()
|
||||||
case "hCaptcha":
|
case "hCaptcha":
|
||||||
|
@@ -200,6 +200,10 @@ func (c *ApiController) Signup() {
|
|||||||
Type: userType,
|
Type: userType,
|
||||||
Password: authForm.Password,
|
Password: authForm.Password,
|
||||||
DisplayName: authForm.Name,
|
DisplayName: authForm.Name,
|
||||||
|
Gender: authForm.Gender,
|
||||||
|
Bio: authForm.Bio,
|
||||||
|
Tag: authForm.Tag,
|
||||||
|
Education: authForm.Education,
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: authForm.Email,
|
Email: authForm.Email,
|
||||||
Phone: authForm.Phone,
|
Phone: authForm.Phone,
|
||||||
|
@@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/object"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
// GetOidcDiscovery
|
// GetOidcDiscovery
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
@@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
|||||||
c.Data["json"] = jwks
|
c.Data["json"] = jwks
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebFinger
|
||||||
|
// @Title GetWebFinger
|
||||||
|
// @Tag OIDC API
|
||||||
|
// @Param resource query string true "resource"
|
||||||
|
// @Success 200 {object} object.WebFinger
|
||||||
|
// @router /.well-known/webfinger [get]
|
||||||
|
func (c *RootController) GetWebFinger() {
|
||||||
|
resource := c.Input().Get("resource")
|
||||||
|
rels := []string{}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
for key, value := range c.Input() {
|
||||||
|
if strings.HasPrefix(key, "rel") {
|
||||||
|
rels = append(rels, value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = webfinger
|
||||||
|
c.Ctx.Output.ContentType("application/jrd+json")
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
c.ResponseOk(organizations)
|
c.ResponseOk(organizations)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
count, err := object.GetOrganizationCount(owner, field, value)
|
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -138,7 +138,7 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetOrganizationCount("", "", "")
|
count, err := object.GetOrganizationCount("", "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -410,6 +410,12 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
organization := c.Ctx.Request.Form.Get("organization")
|
organization := c.Ctx.Request.Form.Get("organization")
|
||||||
username := c.Ctx.Request.Form.Get("username")
|
username := c.Ctx.Request.Form.Get("username")
|
||||||
|
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
c.ResponseError("Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user, err := object.GetUserByFields(organization, username)
|
user, err := object.GetUserByFields(organization, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@@ -45,6 +45,15 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
|||||||
|
|
||||||
// ResponseError ...
|
// ResponseError ...
|
||||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
error = c.T("subscription:Error")
|
||||||
|
|
||||||
|
resp := &Response{Status: "error", Msg: error}
|
||||||
|
c.ResponseJsonData(resp, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
||||||
if enableErrorMask {
|
if enableErrorMask {
|
||||||
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
||||||
|
@@ -26,6 +26,10 @@ type AuthForm struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Education string `json:"education"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
1
main.go
1
main.go
@@ -71,6 +71,7 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||||
}
|
}
|
||||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||||
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
|
||||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
||||||
|
@@ -31,15 +31,17 @@ type SigninMethod struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignupItem struct {
|
type SignupItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
CustomCss string `json:"customCss"`
|
Type string `json:"type"`
|
||||||
Label string `json:"label"`
|
CustomCss string `json:"customCss"`
|
||||||
Placeholder string `json:"placeholder"`
|
Label string `json:"label"`
|
||||||
Regex string `json:"regex"`
|
Placeholder string `json:"placeholder"`
|
||||||
Rule string `json:"rule"`
|
Options []string `json:"options"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigninItem struct {
|
type SigninItem struct {
|
||||||
@@ -78,13 +80,14 @@ type Application struct {
|
|||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||||
|
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
||||||
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
@@ -531,7 +534,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
|
|
||||||
providerItems := []*ProviderItem{}
|
providerItems := []*ProviderItem{}
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
|
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha" || providerItem.Provider.Category == "SAML") {
|
||||||
providerItems = append(providerItems, providerItem)
|
providerItems = append(providerItems, providerItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,18 @@ type OidcDiscovery struct {
|
|||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebFinger struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Links []WebFingerLink `json:"links"`
|
||||||
|
Aliases *[]string `json:"aliases,omitempty"`
|
||||||
|
Properties *map[string]string `json:"properties,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebFingerLink struct {
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
func isIpAddress(host string) bool {
|
func isIpAddress(host string) bool {
|
||||||
// Attempt to split the host and port, ignoring the error
|
// Attempt to split the host and port, ignoring the error
|
||||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||||
@@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
|
|
||||||
return jwks, nil
|
return jwks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) {
|
||||||
|
wf := WebFinger{}
|
||||||
|
|
||||||
|
resourceSplit := strings.Split(resource, ":")
|
||||||
|
|
||||||
|
if len(resourceSplit) != 2 {
|
||||||
|
return wf, fmt.Errorf("invalid resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceType := resourceSplit[0]
|
||||||
|
resourceValue := resourceSplit[1]
|
||||||
|
|
||||||
|
oidcDiscovery := GetOidcDiscovery(host)
|
||||||
|
|
||||||
|
switch resourceType {
|
||||||
|
case "acct":
|
||||||
|
user, err := GetUserByEmailOnly(resourceValue)
|
||||||
|
if err != nil {
|
||||||
|
return wf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return wf, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wf.Subject = resource
|
||||||
|
|
||||||
|
for _, rel := range rels {
|
||||||
|
if rel == "http://openid.net/specs/connect/1.0/issuer" {
|
||||||
|
wf.Links = append(wf.Links, WebFingerLink{
|
||||||
|
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||||
|
Href: oidcDiscovery.Issuer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wf, nil
|
||||||
|
}
|
||||||
|
@@ -79,9 +79,9 @@ type Organization struct {
|
|||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) (int64, error) {
|
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Organization{})
|
return session.Count(&Organization{Name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||||
|
@@ -65,7 +65,11 @@ func NewSamlResponse(application *Application, user *User, host string, certific
|
|||||||
assertion.CreateAttr("IssueInstant", now)
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
assertion.CreateElement("saml:Issuer").SetText(host)
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||||
subject := assertion.CreateElement("saml:Subject")
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
subject.CreateElement("saml:NameID").SetText(user.Name)
|
nameIDValue := user.Name
|
||||||
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIDValue = user.Email
|
||||||
|
}
|
||||||
|
subject.CreateElement("saml:NameID").SetText(nameIDValue)
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||||
@@ -184,17 +188,17 @@ type NameIDFormat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SingleSignOnService struct {
|
type SingleSignOnService struct {
|
||||||
XMLName xml.Name
|
// XMLName xml.Name
|
||||||
Binding string `xml:"Binding,attr"`
|
Binding string `xml:"Binding,attr"`
|
||||||
Location string `xml:"Location,attr"`
|
Location string `xml:"Location,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
// XMLName xml.Name
|
// XMLName xml.Name
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
NameFormat string `xml:"NameFormat,attr"`
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
FriendlyName string `xml:"FriendlyName,attr"`
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Values []string `xml:"AttributeValue"`
|
Values []string `xml:"AttributeValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +390,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSamlResponse11 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, error) {
|
func NewSamlResponse11(application *Application, user *User, requestID string, host string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
@@ -430,7 +434,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
// nameIdentifier inside subject
|
// nameIdentifier inside subject
|
||||||
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
||||||
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
||||||
nameIdentifier.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifier.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifier.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// subjectConfirmation inside subject
|
// subjectConfirmation inside subject
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
@@ -439,7 +447,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
||||||
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
||||||
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
||||||
nameIdentifierInAttribute.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
@@ -281,7 +281,7 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
|
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
@@ -950,7 +950,17 @@ func DeleteUser(user *User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if organization != nil && organization.EnableSoftDeletion {
|
||||||
|
user.IsDeleted = true
|
||||||
|
user.DeletedTime = util.GetCurrentTime()
|
||||||
|
return UpdateUser(user.GetId(), user, []string{"is_deleted", "deleted_time"}, false)
|
||||||
|
} else {
|
||||||
|
return deleteUser(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
||||||
|
@@ -271,113 +271,213 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Owner != newUser.Owner {
|
if oldUser.Owner != newUser.Owner {
|
||||||
item := GetAccountItemByName("Organization", organization)
|
item := GetAccountItemByName("Organization", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Owner = oldUser.Owner
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Name != newUser.Name {
|
if oldUser.Name != newUser.Name {
|
||||||
item := GetAccountItemByName("Name", organization)
|
item := GetAccountItemByName("Name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Name = oldUser.Name
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Id != newUser.Id {
|
if oldUser.Id != newUser.Id {
|
||||||
item := GetAccountItemByName("ID", organization)
|
item := GetAccountItemByName("ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Id = oldUser.Id
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
if oldUser.DisplayName != newUser.DisplayName {
|
||||||
item := GetAccountItemByName("Display name", organization)
|
item := GetAccountItemByName("Display name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.DisplayName = oldUser.DisplayName
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
if oldUser.Avatar != newUser.Avatar {
|
||||||
item := GetAccountItemByName("Avatar", organization)
|
item := GetAccountItemByName("Avatar", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Avatar = oldUser.Avatar
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Type != newUser.Type {
|
if oldUser.Type != newUser.Type {
|
||||||
item := GetAccountItemByName("User type", organization)
|
item := GetAccountItemByName("User type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Type = oldUser.Type
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The password is *** when not modified
|
// The password is *** when not modified
|
||||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||||
item := GetAccountItemByName("Password", organization)
|
item := GetAccountItemByName("Password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Password = oldUser.Password
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Email != newUser.Email {
|
if oldUser.Email != newUser.Email {
|
||||||
item := GetAccountItemByName("Email", organization)
|
item := GetAccountItemByName("Email", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Email = oldUser.Email
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Phone != newUser.Phone {
|
if oldUser.Phone != newUser.Phone {
|
||||||
item := GetAccountItemByName("Phone", organization)
|
item := GetAccountItemByName("Phone", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Phone = oldUser.Phone
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
if oldUser.CountryCode != newUser.CountryCode {
|
||||||
item := GetAccountItemByName("Country code", organization)
|
item := GetAccountItemByName("Country code", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.CountryCode = oldUser.CountryCode
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Region != newUser.Region {
|
if oldUser.Region != newUser.Region {
|
||||||
item := GetAccountItemByName("Country/Region", organization)
|
item := GetAccountItemByName("Country/Region", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Region = oldUser.Region
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Location != newUser.Location {
|
if oldUser.Location != newUser.Location {
|
||||||
item := GetAccountItemByName("Location", organization)
|
item := GetAccountItemByName("Location", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Location = oldUser.Location
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
if oldUser.Affiliation != newUser.Affiliation {
|
||||||
item := GetAccountItemByName("Affiliation", organization)
|
item := GetAccountItemByName("Affiliation", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Affiliation = oldUser.Affiliation
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Title != newUser.Title {
|
if oldUser.Title != newUser.Title {
|
||||||
item := GetAccountItemByName("Title", organization)
|
item := GetAccountItemByName("Title", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Title = oldUser.Title
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
if oldUser.Homepage != newUser.Homepage {
|
||||||
item := GetAccountItemByName("Homepage", organization)
|
item := GetAccountItemByName("Homepage", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Homepage = oldUser.Homepage
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Bio != newUser.Bio {
|
if oldUser.Bio != newUser.Bio {
|
||||||
item := GetAccountItemByName("Bio", organization)
|
item := GetAccountItemByName("Bio", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Bio = oldUser.Bio
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Tag != newUser.Tag {
|
if oldUser.Tag != newUser.Tag {
|
||||||
item := GetAccountItemByName("Tag", organization)
|
item := GetAccountItemByName("Tag", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Tag = oldUser.Tag
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||||
item := GetAccountItemByName("Signup application", organization)
|
item := GetAccountItemByName("Signup application", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.SignupApplication = oldUser.SignupApplication
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Gender != newUser.Gender {
|
if oldUser.Gender != newUser.Gender {
|
||||||
item := GetAccountItemByName("Gender", organization)
|
item := GetAccountItemByName("Gender", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Gender = oldUser.Gender
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Birthday != newUser.Birthday {
|
if oldUser.Birthday != newUser.Birthday {
|
||||||
item := GetAccountItemByName("Birthday", organization)
|
item := GetAccountItemByName("Birthday", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Birthday = oldUser.Birthday
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Education != newUser.Education {
|
if oldUser.Education != newUser.Education {
|
||||||
item := GetAccountItemByName("Education", organization)
|
item := GetAccountItemByName("Education", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Education = oldUser.Education
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCard != newUser.IdCard {
|
if oldUser.IdCard != newUser.IdCard {
|
||||||
item := GetAccountItemByName("ID card", organization)
|
item := GetAccountItemByName("ID card", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCard = oldUser.IdCard
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCardType != newUser.IdCardType {
|
if oldUser.IdCardType != newUser.IdCardType {
|
||||||
item := GetAccountItemByName("ID card type", organization)
|
item := GetAccountItemByName("ID card type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCardType = oldUser.IdCardType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||||
item := GetAccountItemByName("Properties", organization)
|
item := GetAccountItemByName("Properties", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Properties = oldUser.Properties
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
||||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Groups == nil {
|
if oldUser.Groups == nil {
|
||||||
@@ -390,7 +490,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
||||||
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
||||||
item := GetAccountItemByName("Groups", organization)
|
item := GetAccountItemByName("Groups", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Groups = oldUser.Groups
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Address == nil {
|
if oldUser.Address == nil {
|
||||||
@@ -404,65 +508,117 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
||||||
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
||||||
item := GetAccountItemByName("Address", organization)
|
item := GetAccountItemByName("Address", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Address = oldUser.Address
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUser.FaceIds != nil {
|
if newUser.FaceIds != nil {
|
||||||
item := GetAccountItemByName("Face ID", organization)
|
item := GetAccountItemByName("Face ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.FaceIds = oldUser.FaceIds
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||||
item := GetAccountItemByName("Is admin", organization)
|
item := GetAccountItemByName("Is admin", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsAdmin = oldUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||||
item := GetAccountItemByName("Is forbidden", organization)
|
item := GetAccountItemByName("Is forbidden", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsForbidden = oldUser.IsForbidden
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||||
item := GetAccountItemByName("Is deleted", organization)
|
item := GetAccountItemByName("Is deleted", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsDeleted = oldUser.IsDeleted
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
||||||
item := GetAccountItemByName("Need update password", organization)
|
item := GetAccountItemByName("Need update password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Balance != newUser.Balance {
|
if oldUser.Balance != newUser.Balance {
|
||||||
item := GetAccountItemByName("Balance", organization)
|
item := GetAccountItemByName("Balance", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Balance = oldUser.Balance
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Score != newUser.Score {
|
if oldUser.Score != newUser.Score {
|
||||||
item := GetAccountItemByName("Score", organization)
|
item := GetAccountItemByName("Score", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Score = oldUser.Score
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Karma != newUser.Karma {
|
if oldUser.Karma != newUser.Karma {
|
||||||
item := GetAccountItemByName("Karma", organization)
|
item := GetAccountItemByName("Karma", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Karma = oldUser.Karma
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Language != newUser.Language {
|
if oldUser.Language != newUser.Language {
|
||||||
item := GetAccountItemByName("Language", organization)
|
item := GetAccountItemByName("Language", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Language = oldUser.Language
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Ranking != newUser.Ranking {
|
if oldUser.Ranking != newUser.Ranking {
|
||||||
item := GetAccountItemByName("Ranking", organization)
|
item := GetAccountItemByName("Ranking", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Ranking = oldUser.Ranking
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Currency != newUser.Currency {
|
if oldUser.Currency != newUser.Currency {
|
||||||
item := GetAccountItemByName("Currency", organization)
|
item := GetAccountItemByName("Currency", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Currency = oldUser.Currency
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Hash != newUser.Hash {
|
if oldUser.Hash != newUser.Hash {
|
||||||
item := GetAccountItemByName("Hash", organization)
|
item := GetAccountItemByName("Hash", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Hash = oldUser.Hash
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, accountItem := range itemsChanged {
|
for _, accountItem := range itemsChanged {
|
||||||
|
@@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -23,6 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AutoSigninFilter(ctx *context.Context) {
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
|
urlPath := ctx.Request.URL.Path
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth/access_token") {
|
||||||
|
return
|
||||||
|
}
|
||||||
//if getSessionUser(ctx) != "" {
|
//if getSessionUser(ctx) != "" {
|
||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
|
@@ -48,6 +48,10 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
originHostname := getHostname(origin)
|
originHostname := getHostname(origin)
|
||||||
host := removePort(ctx.Request.Host)
|
host := removePort(ctx.Request.Host)
|
||||||
|
|
||||||
|
if origin == "null" {
|
||||||
|
origin = ""
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
|
@@ -290,6 +290,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||||
|
@@ -43,6 +43,10 @@ func getWebBuildFolder() string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
|
||||||
|
return frontendBaseDir
|
||||||
|
}
|
||||||
|
|
||||||
path = filepath.Join(frontendBaseDir, "web/build")
|
path = filepath.Join(frontendBaseDir, "web/build")
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
@@ -703,6 +703,16 @@ 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:Use Email as NameID"), i18next.t("application:Use Email as NameID - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
<Switch checked={this.state.application.useEmailAsSamlNameId} onChange={checked => {
|
||||||
|
this.updateApplicationField("useEmailAsSamlNameId", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<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"))} :
|
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
||||||
|
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import "codemirror/mode/properties/properties";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import IframeEditor from "./IframeEditor";
|
||||||
|
import {Tabs} from "antd";
|
||||||
|
|
||||||
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
|
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||||
|
const [activeKey, setActiveKey] = useState("advanced");
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||||
|
|
||||||
|
const handleModelTextChange = useCallback((newModelText) => {
|
||||||
|
if (!Setting.builtInObject(model)) {
|
||||||
|
setLocalModelText(newModelText);
|
||||||
|
onModelTextChange(newModelText);
|
||||||
|
}
|
||||||
|
}, [model, onModelTextChange]);
|
||||||
|
|
||||||
|
const syncModelText = useCallback(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (activeKey === "advanced" && iframeRef.current) {
|
||||||
|
const handleSyncMessage = (event) => {
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
window.removeEventListener("message", handleSyncMessage);
|
||||||
|
handleModelTextChange(event.data.modelText);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("message", handleSyncMessage);
|
||||||
|
iframeRef.current.getModelText();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [activeKey, handleModelTextChange]);
|
||||||
|
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
syncModelText().then(() => {
|
||||||
|
setActiveKey(key);
|
||||||
|
if (key === "advanced" && iframeRef.current) {
|
||||||
|
iframeRef.current.updateModelText(localModelText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalModelText(model.modelText);
|
||||||
|
}, [model.modelText]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
|
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||||
|
<TabPane tab="Basic Editor" key="basic" />
|
||||||
|
<TabPane tab="Advanced Editor" key="advanced" />
|
||||||
|
</Tabs>
|
||||||
|
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||||
|
{activeKey === "advanced" ? (
|
||||||
|
<IframeEditor
|
||||||
|
ref={iframeRef}
|
||||||
|
initialModelText={localModelText}
|
||||||
|
onModelTextChange={handleModelTextChange}
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CodeMirror
|
||||||
|
value={localModelText}
|
||||||
|
className="full-height-editor no-horizontal-scroll-editor"
|
||||||
|
options={{mode: "properties", theme: "default"}}
|
||||||
|
onBeforeChange={(editor, data, value) => {
|
||||||
|
handleModelTextChange(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CasbinEditor;
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||||
|
|
||||||
|
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [iframeReady, setIframeReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event) => {
|
||||||
|
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||||
|
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
onModelTextChange(event.data.modelText);
|
||||||
|
} else if (event.data.type === "iframeReady") {
|
||||||
|
setIframeReady(true);
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "initializeModel",
|
||||||
|
modelText: initialModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
|
}, [onModelTextChange, initialModelText]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getModelText: () => {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||||
|
},
|
||||||
|
updateModelText: (newModelText) => {
|
||||||
|
if (iframeReady) {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "updateModelText",
|
||||||
|
modelText: newModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
src="https://editor.casbin.org/model-editor"
|
||||||
|
frameBorder="0"
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
title="Casbin Model Editor"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IframeEditor;
|
@@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import ModelEditor from "./CasbinEditor";
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
|
||||||
import "codemirror/lib/codemirror.css";
|
|
||||||
|
|
||||||
require("codemirror/mode/properties/properties");
|
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<div style={{width: "100%"}} >
|
<div style={{position: "relative", height: "500px"}} >
|
||||||
<CodeMirror
|
<ModelEditor
|
||||||
value={this.state.model.modelText}
|
model={this.state.model}
|
||||||
options={{mode: "properties", theme: "default"}}
|
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||||
onBeforeChange={(editor, data, value) => {
|
|
||||||
if (Setting.builtInObject(this.state.model)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateModelField("modelText", value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
@@ -287,6 +287,14 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
url: "https://www.google.com/recaptcha",
|
url: "https://www.google.com/recaptcha",
|
||||||
},
|
},
|
||||||
|
"reCAPTCHA v2": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA v3": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
"hCaptcha": {
|
"hCaptcha": {
|
||||||
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
url: "https://www.hcaptcha.com",
|
url: "https://www.hcaptcha.com",
|
||||||
@@ -1088,7 +1096,8 @@ export function getProviderTypeOptions(category) {
|
|||||||
} else if (category === "Captcha") {
|
} else if (category === "Captcha") {
|
||||||
return ([
|
return ([
|
||||||
{id: "Default", name: "Default"},
|
{id: "Default", name: "Default"},
|
||||||
{id: "reCAPTCHA", name: "reCAPTCHA"},
|
{id: "reCAPTCHA v2", name: "reCAPTCHA v2"},
|
||||||
|
{id: "reCAPTCHA v3", name: "reCAPTCHA v3"},
|
||||||
{id: "hCaptcha", name: "hCaptcha"},
|
{id: "hCaptcha", name: "hCaptcha"},
|
||||||
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
||||||
{id: "GEETEST", name: "GEETEST"},
|
{id: "GEETEST", name: "GEETEST"},
|
||||||
|
@@ -434,10 +434,9 @@ class SyncerEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.syncer.table}
|
<Input value={this.state.syncer.table} onChange={e => {
|
||||||
disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
|
this.updateSyncerField("table", e.target.value);
|
||||||
this.updateSyncerField("table", e.target.value);
|
}} />
|
||||||
}} />
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
@@ -1050,6 +1050,8 @@ class UserEditPage extends React.Component {
|
|||||||
<MfaAccountTable
|
<MfaAccountTable
|
||||||
title={i18next.t("user:MFA accounts")}
|
title={i18next.t("user:MFA accounts")}
|
||||||
table={this.state.user.mfaAccounts}
|
table={this.state.user.mfaAccounts}
|
||||||
|
accessToken={this.props.account?.accessToken}
|
||||||
|
icon={this.state.user.avatar}
|
||||||
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
|
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@@ -34,25 +34,42 @@ class CasLogout extends React.Component {
|
|||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
const params = new URLSearchParams(this.props.location.search);
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
const logoutInterval = 100;
|
||||||
|
|
||||||
|
const logoutTimeOut = (redirectUri) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
AuthBackend.getAccount().then((accountRes) => {
|
||||||
|
if (accountRes.status === "ok") {
|
||||||
|
AuthBackend.logout().then((logoutRes) => {
|
||||||
|
if (logoutRes.status === "ok") {
|
||||||
|
logoutTimeOut(logoutRes.data2);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${logoutRes.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||||
|
this.props.onUpdateAccount(null);
|
||||||
|
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||||
|
Setting.goToLink(redirectUri);
|
||||||
|
} else if (params.has("service")) {
|
||||||
|
Setting.goToLink(params.get("service"));
|
||||||
|
} else {
|
||||||
|
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, logoutInterval);
|
||||||
|
};
|
||||||
|
|
||||||
AuthBackend.logout()
|
AuthBackend.logout()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Logged out successfully");
|
logoutTimeOut(res.data2);
|
||||||
this.props.onUpdateAccount(null);
|
|
||||||
const redirectUri = res.data2;
|
|
||||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
|
||||||
Setting.goToLink(redirectUri);
|
|
||||||
} else if (params.has("service")) {
|
|
||||||
Setting.goToLink(params.get("service"));
|
|
||||||
} else {
|
|
||||||
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@@ -938,7 +938,7 @@ class LoginPage extends React.Component {
|
|||||||
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
||||||
(
|
(
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{i18next.t("login:No account?")}
|
{i18next.t("login:No account?")}
|
||||||
{
|
{
|
||||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Form, Input, Radio, Result, Row, message} from "antd";
|
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as ProviderButton from "./ProviderButton";
|
import * as ProviderButton from "./ProviderButton";
|
||||||
@@ -50,6 +50,38 @@ const formItemLayout = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderFormItem = (signupItem) => {
|
||||||
|
const commonProps = {
|
||||||
|
name: signupItem.name.toLowerCase(),
|
||||||
|
label: signupItem.label || signupItem.name,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: signupItem.required,
|
||||||
|
message: i18next.t(`signup:Please input your ${signupItem.label || signupItem.name}!`),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!signupItem.type || signupItem.type === "Input") {
|
||||||
|
return (
|
||||||
|
<Form.Item {...commonProps}>
|
||||||
|
<Input placeholder={signupItem.placeholder} />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else if (signupItem.type === "Single Choice" || signupItem.type === "Multiple Choices") {
|
||||||
|
return (
|
||||||
|
<Form.Item {...commonProps}>
|
||||||
|
<Select
|
||||||
|
mode={signupItem.type === "Multiple Choices" ? "multiple" : "single"}
|
||||||
|
placeholder={signupItem.placeholder}
|
||||||
|
showSearch={false}
|
||||||
|
options={signupItem.options.map(option => ({label: option, value: option}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const tailFormItemLayout = {
|
export const tailFormItemLayout = {
|
||||||
wrapperCol: {
|
wrapperCol: {
|
||||||
xs: {
|
xs: {
|
||||||
@@ -198,6 +230,22 @@ class SignupPage extends React.Component {
|
|||||||
onFinish(values) {
|
onFinish(values) {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
|
|
||||||
|
if (Array.isArray(values.gender)) {
|
||||||
|
values.gender = values.gender.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.bio)) {
|
||||||
|
values.bio = values.bio.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.tag)) {
|
||||||
|
values.tag = values.tag.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.education)) {
|
||||||
|
values.education = values.education.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
values.plan = params.get("plan");
|
values.plan = params.get("plan");
|
||||||
values.pricing = params.get("pricing");
|
values.pricing = params.get("pricing");
|
||||||
@@ -238,6 +286,7 @@ class SignupPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFormItem(application, signupItem) {
|
renderFormItem(application, signupItem) {
|
||||||
|
const validItems = ["Gender", "Bio", "Tag", "Education"];
|
||||||
if (!signupItem.visible) {
|
if (!signupItem.visible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -366,7 +415,9 @@ class SignupPage extends React.Component {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<RegionSelect className="signup-region-select" onChange={(value) => {this.setState({region: value});}} />
|
<RegionSelect className="signup-region-select" onChange={(value) => {
|
||||||
|
this.setState({region: value});
|
||||||
|
}} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
|
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
|
||||||
@@ -669,8 +720,9 @@ class SignupPage extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
);
|
);
|
||||||
|
} else if (validItems.includes(signupItem.name)) {
|
||||||
|
return renderFormItem(signupItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,7 +27,8 @@ export const CaptchaWidget = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (captchaType) {
|
switch (captchaType) {
|
||||||
case "reCAPTCHA": {
|
case "reCAPTCHA" :
|
||||||
|
case "reCAPTCHA v2": {
|
||||||
const reTimer = setInterval(() => {
|
const reTimer = setInterval(() => {
|
||||||
if (!window.grecaptcha) {
|
if (!window.grecaptcha) {
|
||||||
loadScript("https://recaptcha.net/recaptcha/api.js");
|
loadScript("https://recaptcha.net/recaptcha/api.js");
|
||||||
@@ -42,6 +43,32 @@ export const CaptchaWidget = (props) => {
|
|||||||
}, 300);
|
}, 300);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "reCAPTCHA v3": {
|
||||||
|
const reTimer = setInterval(() => {
|
||||||
|
if (!window.grecaptcha) {
|
||||||
|
loadScript(`https://recaptcha.net/recaptcha/api.js?render=${siteKey}`);
|
||||||
|
}
|
||||||
|
if (window.grecaptcha && window.grecaptcha.render) {
|
||||||
|
const clientId = window.grecaptcha.render("captcha", {
|
||||||
|
"sitekey": siteKey,
|
||||||
|
"badge": "inline",
|
||||||
|
"size": "invisible",
|
||||||
|
"callback": onChange,
|
||||||
|
"error-callback": function() {
|
||||||
|
const logoWidth = `${document.getElementById("captcha").offsetWidth + 40}px`;
|
||||||
|
document.getElementsByClassName("grecaptcha-logo")[0].firstChild.style.width = logoWidth;
|
||||||
|
document.getElementsByClassName("grecaptcha-badge")[0].style.width = logoWidth;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.grecaptcha.ready(function() {
|
||||||
|
window.grecaptcha.execute(clientId, {action: "submit"});
|
||||||
|
});
|
||||||
|
clearInterval(reTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "hCaptcha": {
|
case "hCaptcha": {
|
||||||
const hTimer = setInterval(() => {
|
const hTimer = setInterval(() => {
|
||||||
if (!window.hcaptcha) {
|
if (!window.hcaptcha) {
|
||||||
|
@@ -115,7 +115,7 @@ export const CaptchaModal = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
<Row>
|
<Row justify={"center"}>
|
||||||
<CaptchaWidget
|
<CaptchaWidget
|
||||||
captchaType={captchaType}
|
captchaType={captchaType}
|
||||||
subType={subType}
|
subType={subType}
|
||||||
|
@@ -51,3 +51,19 @@ code {
|
|||||||
.custom-link:hover {
|
.custom-link:hover {
|
||||||
color: rgb(64 64 64) !important;
|
color: rgb(64 64 64) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-height-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-height-editor [class*="CodeMirror"] {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-horizontal-scroll-editor [class*="CodeMirror-hscrollbar"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-horizontal-scroll-editor [class*="CodeMirror-scroll"] {
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||||
import {Button, Col, Image, Input, Row, Table, Tooltip} from "antd";
|
import {Alert, Button, Col, Image, Input, Popover, QRCode, Row, Table, Tooltip} from "antd";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ class MfaAccountTable extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
classes: props,
|
classes: props,
|
||||||
|
icon: this.props.icon,
|
||||||
mfaAccounts: this.props.table !== null ? this.props.table.map((item, index) => {
|
mfaAccounts: this.props.table !== null ? this.props.table.map((item, index) => {
|
||||||
item.key = index;
|
item.key = index;
|
||||||
return item;
|
return item;
|
||||||
@@ -75,6 +76,42 @@ class MfaAccountTable extends React.Component {
|
|||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getQrUrl() {
|
||||||
|
const {accessToken} = this.props;
|
||||||
|
let qrUrl = `casdoor-app://login?serverUrl=${window.location.origin}&accessToken=${accessToken}`;
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
qrUrl = "";
|
||||||
|
error = i18next.t("general:Access token is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qrUrl.length >= 2000) {
|
||||||
|
qrUrl = "";
|
||||||
|
error = i18next.t("general:QR code is too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {qrUrl, error};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQrCode() {
|
||||||
|
const {qrUrl, error} = this.getQrUrl();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert message={error} type="error" showIcon />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<QRCode
|
||||||
|
value={qrUrl}
|
||||||
|
icon={this.state.icon}
|
||||||
|
errorLevel="M"
|
||||||
|
size={230}
|
||||||
|
bordered={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderTable(table) {
|
renderTable(table) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -157,7 +194,11 @@ class MfaAccountTable extends React.Component {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "10px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
|
<Popover trigger="focus" overlayInnerStyle={{padding: 0}}
|
||||||
|
content={this.renderQrCode()}>
|
||||||
|
<Button style={{marginLeft: "5px"}} type="primary" size="small">{i18next.t("general:QR Code")}</Button>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@@ -65,7 +65,7 @@ class SignupTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRow(table) {
|
addRow(table) {
|
||||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a signup item"), visible: true, required: true, rule: "None", customCss: ""};
|
const row = {name: Setting.getNewRowNameForTable(table, "Please select a signup item"), visible: true, required: true, options: [], rule: "None", customCss: ""};
|
||||||
if (table === undefined) {
|
if (table === undefined) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
@@ -100,6 +100,10 @@ class SignupTable extends React.Component {
|
|||||||
{name: "ID", displayName: i18next.t("general:ID")},
|
{name: "ID", displayName: i18next.t("general:ID")},
|
||||||
{name: "Display name", displayName: i18next.t("general:Display name")},
|
{name: "Display name", displayName: i18next.t("general:Display name")},
|
||||||
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||||
|
{name: "Gender", displayName: i18next.t("user:Gender")},
|
||||||
|
{name: "Bio", displayName: i18next.t("user:Bio")},
|
||||||
|
{name: "Tag", displayName: i18next.t("user:Tag")},
|
||||||
|
{name: "Education", displayName: i18next.t("user:Education")},
|
||||||
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||||
{name: "ID card", displayName: i18next.t("user:ID card")},
|
{name: "ID card", displayName: i18next.t("user:ID card")},
|
||||||
{name: "Password", displayName: i18next.t("general:Password")},
|
{name: "Password", displayName: i18next.t("general:Password")},
|
||||||
@@ -201,6 +205,25 @@ class SignupTable extends React.Component {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Type"),
|
||||||
|
dataIndex: "type",
|
||||||
|
key: "type",
|
||||||
|
width: "160px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
const options = [
|
||||||
|
{id: "Input", name: i18next.t("application:Input")},
|
||||||
|
{id: "Single Choice", name: i18next.t("application:Single Choice")},
|
||||||
|
{id: "Multiple Choices", name: i18next.t("application:Multiple Choices")},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
|
||||||
|
this.updateField(table, index, "type", value);
|
||||||
|
})} options={options.map(item => Setting.getOption(item.name, item.id))} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("signup:Label"),
|
title: i18next.t("signup:Label"),
|
||||||
dataIndex: "label",
|
dataIndex: "label",
|
||||||
@@ -261,7 +284,7 @@ class SignupTable extends React.Component {
|
|||||||
title: i18next.t("signup:Placeholder"),
|
title: i18next.t("signup:Placeholder"),
|
||||||
dataIndex: "placeholder",
|
dataIndex: "placeholder",
|
||||||
key: "placeholder",
|
key: "placeholder",
|
||||||
width: "200px",
|
width: "110px",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
if (record.name.startsWith("Text ")) {
|
if (record.name.startsWith("Text ")) {
|
||||||
return null;
|
return null;
|
||||||
@@ -274,6 +297,26 @@ class SignupTable extends React.Component {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("signup:Options"),
|
||||||
|
dataIndex: "options",
|
||||||
|
key: "options",
|
||||||
|
width: "180px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
if (record.type !== "Single Choice" && record.type !== "Multiple Choices") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select virtual={false} mode="tags" style={{width: "100%"}} value={text}
|
||||||
|
onChange={(value => {
|
||||||
|
this.updateField(table, index, "options", value);
|
||||||
|
})}
|
||||||
|
options={text?.map((option) => Setting.getOption(option, option))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("signup:Regex"),
|
title: i18next.t("signup:Regex"),
|
||||||
dataIndex: "regex",
|
dataIndex: "regex",
|
||||||
|
Reference in New Issue
Block a user