Compare commits

...

25 Commits

Author SHA1 Message Date
d647eed22a feat: add OIDC WebFinger support (#3245)
* feat: add WebFinger support

* lint: used gofumpt

* oidc: ensure webfinger rel is checked
2024-09-26 13:06:36 +08:00
717c53f6e5 feat: support enableErrorMask2 config 2024-09-25 19:37:14 +08:00
097adac871 feat: support single-choice and multi-choices in signup page (#3234)
* feat: add custom signup field

* feat: support more field in signup page

* feat: support more field in signup page

* feat: support more field in signup page

* feat: Reduce code duplication in form item rendering

* feat: Simplify gender and info checks using includes

* feat: update translate

* Revert "feat: update translate"

This reverts commit 669334c716.

* feat: address feedback from hsluoyz
2024-09-25 12:48:37 +08:00
74543b9533 feat: improve QR code for casdoor-app (#3226)
* feat: simplify login url for casdoor-app

* feat: add token check

* fix: improve logic
2024-09-23 22:27:58 +08:00
110dc04179 feat: Revert "feat: fix permission problem in standard image" (#3231)
This reverts commit 6464bd10dc.
2024-09-23 22:19:27 +08:00
6464bd10dc feat: fix permission problem in standard image (#3228) 2024-09-23 18:40:39 +08:00
db878a890e feat: add type and options to signup items 2024-09-21 23:40:29 +08:00
12d6d8e6ce feat: fix cookie expire time too short bug 2024-09-21 22:45:13 +08:00
8ed6e4f934 feat: improve UI for "No account?" 2024-09-21 07:35:33 +08:00
ed9732caf9 feat: add condition for getWebBuildFolder function (#3219) 2024-09-20 23:59:13 +08:00
0de4e7da38 feat: fix organization pagination count error (#3215)
* fix(organization): ensure count includes shared organizations

Adjust the `GetOrganizationCount` function to account for shared organizations by adding
an additional parameter and modifying the count query accordingly. This change ensures that
the organization count correctly reflects shared organizations within the system.

* ```fix(organization): optimize GetOrganizationCount query

Refactor the GetOrganizationCount function to use a more efficient search
method by leveraging the 'is_shared' field directly in the query condition.
This change improves the performance for counting organizations by avoiding
unnecessary iteration over potentially large result sets.
```

---------

Co-authored-by: CuiJing <cuijing@tul.com.cn>
2024-09-20 23:58:46 +08:00
a330fbc11f docs: fix Docker link 2024-09-17 20:45:32 +08:00
ed158d4981 feat: support advanced editor in model edit page (#3176)
* feat: integrate external model editor and handle message events for model updates

* feat: add CasbinEditor and IframeEditor components for model editing

* feat: add tabbed editor interface for CasbinEditor

* fix: Synchronize content between basic and advanced editors

* refactor: simplify CasbinEditor and ModelEditPage components

* refactor: Refactor CasbinEditor for improved iframe initialization and model synchronization

* refactor: update default state of CasbinEditor active tab to "advanced

* chore: add Apache License header to CasbinEditor.js and IframeEditor.js files

* refactor: update CasbinEditor class names for consistency
2024-09-16 22:25:25 +08:00
8df965b98d feat: improve SAML XML's xmlns to fix SAML support for some clouds (#3207) 2024-09-16 08:01:28 +08:00
2c3749820e feat: add application.UseEmailAsSamlNameId field for SAML (#3203)
* feat: Add option to use email as SAML NameID based on application config

- Updated NewSamlResponse11 to accept an application parameter.
- Conditionally set SAML NameIdentifier to user's email or username based on application.UseEmailAsNameId.

* refactor: Update GetValidationBySaml to pass application to NewSamlResponse11

- Modified GetValidationBySaml function to include application parameter in NewSamlResponse11 call.

* feat: Rename field and update logic for using Email as SAML NameID

- Renamed the `UseEmailAsNameId` field to `UseEmailAsSamlNameId` in the `Application` struct.
- Updated `NewSamlResponse` and `NewSamlResponse11` functions to use `UseEmailAsSamlNameId` for setting the NameID value.
- Modified `ApplicationEditPage.js` to reflect the field name change and update the corresponding logic.
2024-09-15 23:00:50 +08:00
0b17cb9746 feat: make Organization.EnableSoftDeletion and User.IsDeleted work (#3205)
* feat: make Organization.EnableSoftDeletion and User.IsDeleted work

* fix: add handling of the situation where organization is nil
2024-09-15 14:35:44 +08:00
e2ce9ad625 feat: handle null account item issue in CheckPermissionForUpdateUser() (#3202)
* feat: improve the logic of the permission check code for users to modify account items

* fix: add skip operation for deleted account items in update-user API

* fix: add the function of removing deleted account item
2024-09-14 15:00:10 +08:00
64491abc64 feat: fix CORS issue of /api/acs for SAML IdP (#3200)
* fix: fix CORS problem of /api/acs when login with saml idp

* fix: fix origin get null when receive post with http protocol
2024-09-14 12:48:51 +08:00
934a8947c8 feat: fix CAS logout failure caused by Beego session update problem (#3194)
* feat: fix the cas logout failure caused by beego session update problem

* fix: simplify the implementation of logout timer

* fix: change the location of the login success code

* fix: add i18n to CasLogout.js
2024-09-10 21:31:37 +08:00
943edfb48b feat: support QR login for casdoor app (#3190)
* feat: add MFA devices QR code to UserEditPage

* chore: remove mfa devices
2024-09-08 22:38:13 +08:00
0d02b5e768 feat: remove disabled state in syncer.table 2024-09-07 21:08:21 +08:00
ba8d0b5f46 feat: Revert "feat: Users added through LDAP cannot log in using the set password" (#3186)
This reverts commit 973a1df6c2.
2024-09-07 20:55:14 +08:00
973a1df6c2 feat: Users added through LDAP cannot log in using the set password (#3175)
* fix: login will prioritize the use of password set in casdoor and use ldap when use LDAP option in login form or user never change their password in casdoor after sync

* fix: promote if statement
2024-09-06 10:31:34 +08:00
05bfd3a3a3 feat: fix bug that custom SAML providers are removed by GetMaskedApplication() (#3165) 2024-09-05 20:08:56 +08:00
69aa3c8a8b feat: Revert "feat: add Casbin editor's checking in model editor" (#3167)
This reverts commit a1b010a406.
2024-09-03 21:59:06 +08:00
34 changed files with 740 additions and 271 deletions

View File

@ -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>

View File

@ -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, *, *

View File

@ -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,

View File

@ -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()
}

View File

@ -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

View File

@ -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())

View File

@ -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, "不存在") {

View File

@ -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"`

View File

@ -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"))

View File

@ -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)
} }
} }

View File

@ -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
}

View File

@ -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) {

View File

@ -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")

View File

@ -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
} }

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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")

View File

@ -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
} }

View File

@ -5,10 +5,6 @@
"dependencies": { "dependencies": {
"@ant-design/cssinjs": "^1.10.1", "@ant-design/cssinjs": "^1.10.1",
"@ant-design/icons": "^4.7.0", "@ant-design/icons": "^4.7.0",
"@codemirror/language": "^6.10.2",
"@codemirror/lint": "^6.8.1",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.33.0",
"@craco/craco": "^6.4.5", "@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.7.10", "@crowdin/cli": "^3.7.10",
"@ctrl/tinycolor": "^3.5.0", "@ctrl/tinycolor": "^3.5.0",
@ -27,8 +23,7 @@
"antd": "5.2.3", "antd": "5.2.3",
"antd-token-previewer": "^1.1.0-22", "antd-token-previewer": "^1.1.0-22",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"casbin": "^5.30.0", "codemirror": "^5.61.1",
"codemirror": "5",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0", "core-js": "^3.25.0",
"craco-less": "^2.0.0", "craco-less": "^2.0.0",

View File

@ -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
View 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
View 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;

View File

@ -18,10 +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 {Controlled as CodeMirror} from "react-codemirror2"; import ModelEditor from "./CasbinEditor";
import {createLinter} from "./utils/modelLinter";
require("codemirror/mode/properties/properties");
const {Option} = Select; const {Option} = Select;
@ -146,24 +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={{ onModelTextChange={(value) => this.updateModelField("modelText", value)}
mode: "properties",
theme: "default",
lineNumbers: true,
lint: true,
}}
onBeforeChange={(editor, data, value) => {
if (Setting.builtInObject(this.state.model)) {
return;
}
this.updateModelField("modelText", value);
}}
editorDidMount={(editor, value, cb) => {
createLinter(editor.constructor);
}}
/> />
</div> </div>
</Col> </Col>

View File

@ -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"}} >

View File

@ -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>

View File

@ -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() {

View File

@ -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?")}&nbsp;
{ {
Setting.renderSignupLink(application, i18next.t("login:sign up now")) Setting.renderSignupLink(application, i18next.t("login:sign up now"))
} }

View File

@ -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);
} }
} }

View File

@ -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;
}

View File

@ -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}&nbsp;&nbsp;&nbsp;&nbsp; {this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<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>
)} )}
/> />

View File

@ -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",

View File

@ -1,42 +0,0 @@
import {newModel} from "casbin";
import "codemirror/lib/codemirror.css";
import "codemirror/addon/lint/lint.css";
import "codemirror/addon/lint/lint";
export const checkModelSyntax = (modelText) => {
try {
const model = newModel(modelText);
if (!model.model.get("r") || !model.model.get("p") || !model.model.get("e")) {
throw new Error("Model is missing one or more required sections (r, p, or e)");
}
return null;
} catch (e) {
return e.message;
}
};
export const createLinter = (CodeMirror) => {
CodeMirror.registerHelper("lint", "properties", (text) => {
const error = checkModelSyntax(text);
if (error) {
const lineMatch = error.match(/line (\d+)/);
if (lineMatch) {
const lineNumber = parseInt(lineMatch[1], 10) - 1;
return [{
from: CodeMirror.Pos(lineNumber, 0),
to: CodeMirror.Pos(lineNumber, text.split("\n")[lineNumber].length),
message: error,
severity: "error",
}];
} else {
return [{
from: CodeMirror.Pos(0, 0),
to: CodeMirror.Pos(text.split("\n").length - 1),
message: error,
severity: "error",
}];
}
}
return [];
});
};

View File

@ -1442,41 +1442,6 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@codemirror/language@^6.10.2":
version "6.10.2"
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.2.tgz#4056dc219619627ffe995832eeb09cea6060be61"
integrity sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.23.0"
"@lezer/common" "^1.1.0"
"@lezer/highlight" "^1.0.0"
"@lezer/lr" "^1.0.0"
style-mod "^4.0.0"
"@codemirror/lint@^6.8.1":
version "6.8.1"
resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.8.1.tgz#6427848815baaf68c08e98c7673b804d3d8c0e7f"
integrity sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1":
version "6.4.1"
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b"
integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==
"@codemirror/view@^6.0.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.33.0":
version "6.33.0"
resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.33.0.tgz#51e270410fc3af92a6e38798e80ebf8add7dc3ec"
integrity sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==
dependencies:
"@codemirror/state" "^6.4.0"
style-mod "^4.1.0"
w3c-keyname "^2.2.4"
"@coinbase/wallet-sdk@^3.7.1": "@coinbase/wallet-sdk@^3.7.1":
version "3.7.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.7.1.tgz#44b3b7a925ff5cc974e4cbf7a44199ffdcf03541" resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.7.1.tgz#44b3b7a925ff5cc974e4cbf7a44199ffdcf03541"
@ -3334,25 +3299,6 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049"
integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==
"@lezer/highlight@^1.0.0":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.1.tgz#596fa8f9aeb58a608be0a563e960c373cbf23f8b"
integrity sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==
dependencies:
"@lezer/common" "^1.0.0"
"@lezer/lr@^1.0.0":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.2.tgz#931ea3dea8e9de84e90781001dae30dea9ff1727"
integrity sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==
dependencies:
"@lezer/common" "^1.0.0"
"@metamask/eth-sig-util@^6.0.0": "@metamask/eth-sig-util@^6.0.0":
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-6.0.0.tgz#083321dc7285a9aa6e066db7c49be6e94c5e03a3" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-6.0.0.tgz#083321dc7285a9aa6e066db7c49be6e94c5e03a3"
@ -5174,11 +5120,6 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
await-lock@^2.0.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
aws-sign2@~0.7.0: aws-sign2@~0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -5682,17 +5623,6 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz#fae53f6286e7564783eadea9b447819410a59534" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz#fae53f6286e7564783eadea9b447819410a59534"
integrity sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A== integrity sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A==
casbin@^5.30.0:
version "5.30.0"
resolved "https://registry.yarnpkg.com/casbin/-/casbin-5.30.0.tgz#819019348ead1cfd923fbab93772ad188da342cd"
integrity sha512-GDc8sImStd+ddBVBfLpe5fJPBWRjeEaz7fkiAGuw0+LTHF2TVvVsMALIMOx+ofzQhm+EHCH7mfiJsrS1Kgef2w==
dependencies:
await-lock "^2.0.1"
buffer "^6.0.3"
csv-parse "^5.3.5"
expression-eval "^5.0.0"
minimatch "^7.4.2"
case-sensitive-paths-webpack-plugin@^2.4.0: case-sensitive-paths-webpack-plugin@^2.4.0:
version "2.4.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
@ -5881,10 +5811,10 @@ coa@^2.0.2:
chalk "^2.4.1" chalk "^2.4.1"
q "^1.1.2" q "^1.1.2"
codemirror@5: codemirror@^5.61.1:
version "5.65.17" version "5.65.13"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.17.tgz#00d71f34c3518471ae4c0de23a2f8bb39a6df6ca" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.13.tgz#c098a6f409db8b5a7c5722788bd9fa3bb2367f2e"
integrity sha512-1zOsUx3lzAOu/gnMAZkQ9kpIHcPYOc9y1Fbm2UVk5UBPkdq380nhkelG0qUwm1f7wPvTbndu9ZYlug35EwAZRQ== integrity sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==
collect-v8-coverage@^1.0.0: collect-v8-coverage@^1.0.0:
version "1.0.1" version "1.0.1"
@ -6138,11 +6068,6 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
crelt@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
cropperjs@^1.5.13: cropperjs@^1.5.13:
version "1.5.13" version "1.5.13"
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8" resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.13.tgz#eb1682f01d17c70ed5244317091d745c9a249ef8"
@ -6405,11 +6330,6 @@ csstype@^3.0.10, csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
csv-parse@^5.3.5:
version "5.5.6"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.6.tgz#0d726d58a60416361358eec291a9f93abe0b6b1a"
integrity sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==
cypress@^12.5.1: cypress@^12.5.1:
version "12.15.0" version "12.15.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.15.0.tgz#06103529583c41f39712c6cfa6d9d09a01731760" resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.15.0.tgz#06103529583c41f39712c6cfa6d9d09a01731760"
@ -7781,13 +7701,6 @@ express@^4.17.3:
utils-merge "1.0.1" utils-merge "1.0.1"
vary "~1.1.2" vary "~1.1.2"
expression-eval@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/expression-eval/-/expression-eval-5.0.1.tgz#845758fa9ba64d9edc7b6804ae404934a6cfee6b"
integrity sha512-7SL4miKp19lI834/F6y156xlNg+i9Q41tteuGNCq9C06S78f1bm3BXuvf0+QpQxv369Pv/P2R7Hb17hzxLpbDA==
dependencies:
jsep "^0.3.0"
ext@^1.1.2: ext@^1.1.2:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
@ -9823,11 +9736,6 @@ jsdom@^16.6.0:
ws "^7.4.6" ws "^7.4.6"
xml-name-validator "^3.0.0" xml-name-validator "^3.0.0"
jsep@^0.3.0:
version "0.3.5"
resolved "https://registry.yarnpkg.com/jsep/-/jsep-0.3.5.tgz#3fd79ebd92f6f434e4857d5272aaeef7d948264d"
integrity sha512-AoRLBDc6JNnKjNcmonituEABS5bcfqDhQAWWXNTFrqu6nVXBpBAGfcoTGZMFlIrh9FjmE1CQyX9CTNwZrXMMDA==
jsesc@^2.5.1: jsesc@^2.5.1:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -10463,13 +10371,6 @@ minimatch@^5.0.1:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimatch@^7.4.2:
version "7.4.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb"
integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==
dependencies:
brace-expansion "^2.0.1"
minimist-options@4.1.0: minimist-options@4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
@ -13778,11 +13679,6 @@ style-loader@^3.3.1:
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff"
integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw== integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==
style-mod@^4.0.0, style-mod@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.2.tgz#ca238a1ad4786520f7515a8539d5a63691d7bf67"
integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==
style-search@^0.1.0: style-search@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
@ -14690,11 +14586,6 @@ w3c-hr-time@^1.0.2:
dependencies: dependencies:
browser-process-hrtime "^1.0.0" browser-process-hrtime "^1.0.0"
w3c-keyname@^2.2.4:
version "2.2.8"
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
w3c-xmlserializer@^2.0.0: w3c-xmlserializer@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"