Compare commits

...

19 Commits

Author SHA1 Message Date
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
25 changed files with 544 additions and 265 deletions

View File

@ -13,7 +13,7 @@
<a href="https://github.com/casdoor/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
</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">
</a>
</p>

View File

@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
c.ResponseOk(organizations)
} else {
limit := util.ParseInt(limit)
count, err := object.GetOrganizationCount(owner, field, value)
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
if err != nil {
c.ResponseError(err.Error())
return
@ -138,7 +138,7 @@ func (c *ApiController) AddOrganization() {
return
}
count, err := object.GetOrganizationCount("", "", "")
count, err := object.GetOrganizationCount("", "", "", "")
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -71,6 +71,7 @@ func main() {
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))

View File

@ -31,15 +31,17 @@ type SigninMethod struct {
}
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
CustomCss string `json:"customCss"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Regex string `json:"regex"`
Rule string `json:"rule"`
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Type string `json:"type"`
CustomCss string `json:"customCss"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Options []string `json:"options"`
Regex string `json:"regex"`
Rule string `json:"rule"`
}
type SigninItem struct {
@ -78,6 +80,7 @@ type Application struct {
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`
@ -531,7 +534,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
providerItems := []*ProviderItem{}
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)
}
}

View File

@ -79,9 +79,9 @@ type Organization struct {
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, "", "")
return session.Count(&Organization{})
return session.Count(&Organization{Name: name})
}
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.CreateElement("saml:Issuer").SetText(host)
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.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
@ -184,17 +188,17 @@ type NameIDFormat struct {
}
type SingleSignOnService struct {
XMLName xml.Name
// XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
}
type Attribute struct {
// XMLName xml.Name
Xmlns string `xml:"xmlns,attr"`
Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"`
Xmlns string `xml:"xmlns,attr"`
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)
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{
Space: "samlp",
Tag: "Response",
@ -430,7 +434,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
// nameIdentifier inside subject
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
// 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 := subject.CreateElement("saml:SubjectConfirmation")
@ -439,7 +447,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
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.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)
}
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
if err != nil {
return "", "", err
}

View File

@ -950,7 +950,17 @@ func DeleteUser(user *User) (bool, error) {
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) {

View File

@ -271,113 +271,213 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
if oldUser.Owner != newUser.Owner {
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 {
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 {
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 {
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 {
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 {
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
if oldUser.Password != newUser.Password && newUser.Password != "***" {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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)
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
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 {
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 {
@ -390,7 +490,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
item := GetAccountItemByName("Groups", organization)
itemsChanged = append(itemsChanged, item)
if item == nil {
newUser.Groups = oldUser.Groups
} else {
itemsChanged = append(itemsChanged, item)
}
}
if oldUser.Address == nil {
@ -404,65 +508,117 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
newUserAddressJson, _ := json.Marshal(newUser.Address)
if string(oldUserAddressJson) != string(newUserAddressJson) {
item := GetAccountItemByName("Address", organization)
itemsChanged = append(itemsChanged, item)
if item == nil {
newUser.Address = oldUser.Address
} else {
itemsChanged = append(itemsChanged, item)
}
}
if newUser.FaceIds != nil {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
item := GetAccountItemByName("Hash", organization)
itemsChanged = append(itemsChanged, item)
if item == nil {
newUser.Hash = oldUser.Hash
} else {
itemsChanged = append(itemsChanged, item)
}
}
for _, accountItem := range itemsChanged {

View File

@ -48,6 +48,10 @@ func CorsFilter(ctx *context.Context) {
originHostname := getHostname(origin)
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") {
setCorsHeaders(ctx, origin)
return

View File

@ -43,6 +43,10 @@ func getWebBuildFolder() string {
return path
}
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
return frontendBaseDir
}
path = filepath.Join(frontendBaseDir, "web/build")
return path
}

View File

@ -5,10 +5,6 @@
"dependencies": {
"@ant-design/cssinjs": "^1.10.1",
"@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",
"@crowdin/cli": "^3.7.10",
"@ctrl/tinycolor": "^3.5.0",
@ -27,8 +23,7 @@
"antd": "5.2.3",
"antd-token-previewer": "^1.1.0-22",
"buffer": "^6.0.3",
"casbin": "^5.30.0",
"codemirror": "5",
"codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0",
"craco-less": "^2.0.0",

View File

@ -703,6 +703,16 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</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"}} >
<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"))} :

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 Setting from "./Setting";
import i18next from "i18next";
import {Controlled as CodeMirror} from "react-codemirror2";
import {createLinter} from "./utils/modelLinter";
require("codemirror/mode/properties/properties");
import ModelEditor from "./CasbinEditor";
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"))} :
</Col>
<Col span={22}>
<div style={{width: "100%"}} >
<CodeMirror
value={this.state.model.modelText}
options={{
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 style={{position: "relative", height: "500px"}} >
<ModelEditor
model={this.state.model}
onModelTextChange={(value) => this.updateModelField("modelText", value)}
/>
</div>
</Col>

View File

@ -434,10 +434,9 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.syncer.table}
disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
this.updateSyncerField("table", e.target.value);
}} />
<Input value={this.state.syncer.table} onChange={e => {
this.updateSyncerField("table", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >

View File

@ -1050,6 +1050,13 @@ class UserEditPage extends React.Component {
<MfaAccountTable
title={i18next.t("user:MFA accounts")}
table={this.state.user.mfaAccounts}
qrUrl={
"casdoor-app://login/into?serverUrl=" + window.location.origin +
"&clientId=" + this.state.application.clientId +
"&organizationName=" + this.state.organizationName +
"&appName=" + this.state.user.signupApplication
}
icon={this.state.user.avatar}
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
/>
</Col>

View File

@ -34,25 +34,42 @@ class CasLogout extends React.Component {
UNSAFE_componentWillMount() {
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()
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully");
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`);
}
logoutTimeOut(res.data2);
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${res.msg}`);
}
});
}
render() {

View File

@ -938,7 +938,7 @@ class LoginPage extends React.Component {
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
(
<React.Fragment>
{i18next.t("login:No account?")}
{i18next.t("login:No account?")}&nbsp;
{
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
}

View File

@ -51,3 +51,19 @@ code {
.custom-link:hover {
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 {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Image, Input, Row, Table, Tooltip} from "antd";
import {Button, Col, Image, Input, Popover, QRCode, Row, Table, Tooltip} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
@ -23,6 +23,8 @@ class MfaAccountTable extends React.Component {
super(props);
this.state = {
classes: props,
qrUrl: this.props.qrUrl,
icon: this.props.icon,
mfaAccounts: this.props.table !== null ? this.props.table.map((item, index) => {
item.key = index;
return item;
@ -158,6 +160,15 @@ class MfaAccountTable extends React.Component {
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
<Popover trigger="focus" content={
<QRCode
value={this.state.qrUrl}
icon={this.state.icon}
bordered={false}
/>
}>
<Button style={{marginLeft: "5px"}} type="primary" size="small">{i18next.t("general:QR Code")}</Button>
</Popover>
</div>
)}
/>

View File

@ -65,7 +65,7 @@ class SignupTable extends React.Component {
}
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) {
table = [];
}
@ -201,6 +201,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"),
dataIndex: "label",
@ -261,7 +280,7 @@ class SignupTable extends React.Component {
title: i18next.t("signup:Placeholder"),
dataIndex: "placeholder",
key: "placeholder",
width: "200px",
width: "110px",
render: (text, record, index) => {
if (record.name.startsWith("Text ")) {
return null;
@ -274,6 +293,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"),
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"
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":
version "3.7.1"
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"
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":
version "6.0.0"
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"
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:
version "0.7.0"
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"
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:
version "2.4.0"
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"
q "^1.1.2"
codemirror@5:
version "5.65.17"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.17.tgz#00d71f34c3518471ae4c0de23a2f8bb39a6df6ca"
integrity sha512-1zOsUx3lzAOu/gnMAZkQ9kpIHcPYOc9y1Fbm2UVk5UBPkdq380nhkelG0qUwm1f7wPvTbndu9ZYlug35EwAZRQ==
codemirror@^5.61.1:
version "5.65.13"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.13.tgz#c098a6f409db8b5a7c5722788bd9fa3bb2367f2e"
integrity sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg==
collect-v8-coverage@^1.0.0:
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"
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:
version "1.5.13"
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"
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:
version "12.15.0"
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"
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:
version "1.7.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
@ -9823,11 +9736,6 @@ jsdom@^16.6.0:
ws "^7.4.6"
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:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -10463,13 +10371,6 @@ minimatch@^5.0.1:
dependencies:
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:
version "4.1.0"
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"
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:
version "0.1.0"
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:
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:
version "2.0.0"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"