Compare commits

...

15 Commits

Author SHA1 Message Date
0d239ba1cf feat: improve the error message of GitHub OAuth provider (#3462) 2024-12-29 21:54:54 +08:00
8927e08217 feat: speed up GetDashboard() by only fetching last 30 days data (#3458)
* feat: only check 30 days data

* refactor: refactor GetDashboard to reduce code line

* refactor: refactor GetDashboard to reduce code line

* refactor: remove unused where

* fix: fix error code
2024-12-29 16:15:52 +08:00
0636069584 feat: only fetch created_time field to reduce data size in get-dashboard API (#3457) 2024-12-28 23:52:19 +08:00
4d0f73c84e feat: fix Casdoor OAuth provider doesn't use domain field bug 2024-12-28 10:01:56 +08:00
74a2478e10 feat: Make MinIO storage provider region setting configurable (#3433)
* fix: Make MinIO provider region setting configurable

* Fix: Correct the issue where modifications to MinIO's default logic caused behavioral discrepancies
2024-12-23 16:07:14 +08:00
acc6f3e887 feat: escape the avatal URL in CAS response (#3434) 2024-12-20 17:11:58 +08:00
185ab9750a feat: fix VerificationRecord.IsUsed JSON Field Mapping 2024-12-18 13:56:54 +08:00
48adc050d6 feat: can pass empty user id on user update (#3443) 2024-12-18 07:56:44 +08:00
b0e318c9db feat: add localized tab titles for Basic and Advanced Editors (#3431)
* feat: add localized tab titles for Basic and Advanced Editors

* docs: update translations for model editor labels in multiple locales
2024-12-16 08:34:13 +08:00
f9a6efc00f feat: advanced model editor should support changing UI language (#3430) 2024-12-15 15:53:29 +08:00
bd4a6775dd feat: get github user email with user/emails api (#3428)
* feat: get user email use `user/emails` api

* feat: improve code format

* feat: improve code format
2024-12-15 10:28:18 +08:00
e3a43d0062 feat: improve the advanced editor of model edit page (#3427) 2024-12-15 02:07:02 +08:00
0cf281cac0 feat: fix record's password regex bug (#3421) 2024-12-11 08:43:03 +08:00
7322f67ae0 feat: add model, adapter and enforcer to the dashboard page chart (#3413)
* [feature] Add more data (Model, Adapter, Enforcer) to the dashboard page chart #3379

* feat: add model, adapter, enforcer to dashboard
2024-12-09 16:07:39 +08:00
b927c6d7b4 feat: support LDAP's SetPassword (#3395)
* fix: Resolve the issue mentioned in #3392

* fix: Change checkLdapUserPassword to CheckLdapUserPassword.

* fix: the issue mentioned by hsluoyz.

* fix: Check if the user parameter is nil

* fix: use existing i18n message
2024-12-09 16:06:24 +08:00
42 changed files with 320 additions and 234 deletions

View File

@ -475,6 +475,16 @@ func (c *ApiController) SetPassword() {
userId := util.GetId(userOwner, userName)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first")
@ -518,7 +528,11 @@ func (c *ApiController) SetPassword() {
}
}
} else if code == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if user.Ldap == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
} else {
err = object.CheckLdapUserPassword(targetUser, oldPassword, c.GetAcceptLanguage())
}
if err != nil {
c.ResponseError(err.Error())
return
@ -563,7 +577,12 @@ func (c *ApiController) SetPassword() {
targetUser.NeedUpdatePassword = false
targetUser.LastChangePasswordTime = util.GetCurrentTime()
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
if user.Ldap == "" {
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
} else {
err = object.ResetLdapPassword(targetUser, newPassword, c.GetAcceptLanguage())
}
if err != nil {
c.ResponseError(err.Error())
return

1
go.mod
View File

@ -63,6 +63,7 @@ require (
golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.17.0
golang.org/x/text v0.14.0
google.golang.org/api v0.150.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0

View File

@ -188,10 +188,23 @@ type GitHubUserInfo struct {
} `json:"plan"`
}
type GitHubUserEmailInfo struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
Visibility string `json:"visibility"`
}
type GitHubErrorInfo struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url"`
Status string `json:"status"`
}
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
panic(err)
return nil, err
}
req.Header.Add("Authorization", "token "+token.AccessToken)
resp, err := idp.Client.Do(req)
@ -212,6 +225,41 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
if githubUserInfo.Email == "" {
reqEmail, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil)
if err != nil {
return nil, err
}
reqEmail.Header.Add("Authorization", "token "+token.AccessToken)
respEmail, err := idp.Client.Do(reqEmail)
if err != nil {
return nil, err
}
defer respEmail.Body.Close()
emailBody, err := io.ReadAll(respEmail.Body)
if err != nil {
return nil, err
}
if respEmail.StatusCode != 200 {
var errMessage GitHubErrorInfo
err = json.Unmarshal(emailBody, &errMessage)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("%s, %s", errMessage.Message, errMessage.DocumentationUrl)
}
var userEmails []GitHubUserEmailInfo
err = json.Unmarshal(emailBody, &userEmails)
if err != nil {
return nil, err
}
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
}
userInfo := UserInfo{
Id: strconv.Itoa(githubUserInfo.Id),
Username: githubUserInfo.Login,
@ -248,3 +296,27 @@ func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte,
return data, nil
}
func (idp *GithubIdProvider) getEmailFromEmailsResult(emailInfo []GitHubUserEmailInfo) string {
primaryEmail := ""
verifiedEmail := ""
for _, addr := range emailInfo {
if !addr.Verified || strings.Contains(addr.Email, "users.noreply.github.com") {
continue
}
if addr.Primary {
primaryEmail = addr.Email
break
} else if verifiedEmail == "" {
verifiedEmail = addr.Email
}
}
if primaryEmail != "" {
return primaryEmail
}
return verifiedEmail
}

View File

@ -273,7 +273,7 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password)
}
func checkLdapUserPassword(user *User, password string, lang string) error {
func CheckLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err
@ -368,7 +368,7 @@ func CheckUserPassword(organization string, username string, password string, la
}
// only for LDAP users
err = checkLdapUserPassword(user, password, lang)
err = CheckLdapUserPassword(user, password, lang)
if err != nil {
if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)

View File

@ -19,234 +19,81 @@ import (
"time"
)
type Dashboard struct {
OrganizationCounts []int `json:"organizationCounts"`
UserCounts []int `json:"userCounts"`
ProviderCounts []int `json:"providerCounts"`
ApplicationCounts []int `json:"applicationCounts"`
SubscriptionCounts []int `json:"subscriptionCounts"`
RoleCounts []int `json:"roleCounts"`
GroupCounts []int `json:"groupCounts"`
ResourceCounts []int `json:"resourceCounts"`
CertCounts []int `json:"certCounts"`
PermissionCounts []int `json:"permissionCounts"`
TransactionCounts []int `json:"transactionCounts"`
type DashboardDateItem struct {
CreatedTime string `json:"createTime"`
}
func GetDashboard(owner string) (*Dashboard, error) {
type DashboardMapItem struct {
dashboardDateItems []DashboardDateItem
itemCount int64
}
func GetDashboard(owner string) (*map[string][]int64, error) {
if owner == "All" {
owner = ""
}
dashboard := &Dashboard{
OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31),
ProviderCounts: make([]int, 31),
ApplicationCounts: make([]int, 31),
SubscriptionCounts: make([]int, 31),
RoleCounts: make([]int, 31),
GroupCounts: make([]int, 31),
ResourceCounts: make([]int, 31),
CertCounts: make([]int, 31),
PermissionCounts: make([]int, 31),
TransactionCounts: make([]int, 31),
dashboard := make(map[string][]int64)
dashboardMap := sync.Map{}
tableNames := []string{"organization", "user", "provider", "application", "subscription", "role", "group", "resource", "cert", "permission", "transaction", "model", "adapter", "enforcer"}
time30day := time.Now().AddDate(0, 0, -30)
var wg sync.WaitGroup
var err error
wg.Add(len(tableNames))
for _, tableName := range tableNames {
dashboard[tableName+"Counts"] = make([]int64, 31)
tableName := tableName
go func() {
defer wg.Done()
dashboardDateItems := []DashboardDateItem{}
var countResult int64
dbQueryBefore := ormer.Engine.Cols("created_time")
dbQueryAfter := ormer.Engine.Cols("created_time")
if owner != "" {
dbQueryAfter = dbQueryAfter.And("owner = ?", owner)
dbQueryBefore = dbQueryBefore.And("owner = ?", owner)
}
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
panic(err)
}
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
panic(err)
}
dashboardMap.Store(tableName, DashboardMapItem{
dashboardDateItems: dashboardDateItems,
itemCount: countResult,
})
}()
}
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
roles := []Role{}
groups := []Group{}
resources := []Resource{}
certs := []Cert{}
permissions := []Permission{}
transactions := []Transaction{}
var wg sync.WaitGroup
wg.Add(11)
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&roles, &Role{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&groups, &Group{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&resources, &Resource{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&certs, &Cert{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&permissions, &Permission{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&transactions, &Transaction{Owner: owner}); err != nil {
panic(err)
}
}()
wg.Wait()
nowTime := time.Now()
for i := 30; i >= 0; i-- {
cutTime := nowTime.AddDate(0, 0, -i)
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
dashboard.RoleCounts[30-i] = countCreatedBefore(roles, cutTime)
dashboard.GroupCounts[30-i] = countCreatedBefore(groups, cutTime)
dashboard.ResourceCounts[30-i] = countCreatedBefore(resources, cutTime)
dashboard.CertCounts[30-i] = countCreatedBefore(certs, cutTime)
dashboard.PermissionCounts[30-i] = countCreatedBefore(permissions, cutTime)
dashboard.TransactionCounts[30-i] = countCreatedBefore(transactions, cutTime)
for _, tableName := range tableNames {
item, exist := dashboardMap.Load(tableName)
if !exist {
continue
}
dashboard[tableName+"Counts"][30-i] = countCreatedBefore(item.(DashboardMapItem), cutTime)
}
}
return dashboard, nil
return &dashboard, nil
}
func countCreatedBefore(objects interface{}, before time.Time) int {
count := 0
switch obj := objects.(type) {
case []Organization:
for _, o := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []User:
for _, u := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Provider:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Application:
for _, a := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Subscription:
for _, s := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Role:
for _, r := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Group:
for _, g := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", g.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Resource:
for _, r := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Cert:
for _, c := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", c.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Permission:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Transaction:
for _, t := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", t.CreatedTime)
if createdTime.Before(before) {
count++
}
func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
count := dashboardMapItem.itemCount
for _, e := range dashboardMapItem.dashboardDateItems {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
if createdTime.Before(before) {
count++
}
}
return count

View File

@ -20,9 +20,11 @@ import (
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
"golang.org/x/text/encoding/unicode"
)
type LdapConn struct {
@ -371,6 +373,64 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
return existUuids, nil
}
func ResetLdapPassword(user *User, newPassword string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err
}
for _, ldapServer := range ldaps {
conn, err := ldapServer.GetLdapConn()
if err != nil {
continue
}
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
0, 0, false, ldapServer.buildAuthFilterString(user), []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
conn.Close()
return err
}
if len(searchResult.Entries) == 0 {
conn.Close()
continue
}
if len(searchResult.Entries) > 1 {
conn.Close()
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
}
userDn := searchResult.Entries[0].DN
var pwdEncoded string
modifyPasswordRequest := goldap.NewModifyRequest(userDn, nil)
if conn.IsAD {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
pwdEncoded, err := utf16.NewEncoder().String("\"" + newPassword + "\"")
if err != nil {
conn.Close()
return err
}
modifyPasswordRequest.Replace("unicodePwd", []string{pwdEncoded})
modifyPasswordRequest.Replace("userAccountControl", []string{"512"})
} else {
pwdEncoded = newPassword
modifyPasswordRequest.Replace("userPassword", []string{pwdEncoded})
}
err = conn.Conn.Modify(modifyPasswordRequest)
if err != nil {
conn.Close()
return err
}
conn.Close()
}
return nil
}
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)

View File

@ -421,7 +421,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2
}
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
} else if provider.Type == "ADFS" || provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "Casdoor" || provider.Type == "Okta" {
providerInfo.HostUrl = provider.Domain
}

View File

@ -33,7 +33,7 @@ var (
func init() {
logPostOnly = conf.GetConfigBool("logPostOnly")
passwordRegex = regexp.MustCompile("\"password\":\".+\"")
passwordRegex = regexp.MustCompile("\"password\":\"([^\"]*?)\"")
}
type Record struct {

View File

@ -22,6 +22,7 @@ import (
"encoding/xml"
"fmt"
"math/rand"
"strings"
"sync"
"time"
@ -184,6 +185,15 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
return proxyTicket
}
func escapeXMLText(input string) (string, error) {
var sb strings.Builder
err := xml.EscapeText(&sb, []byte(input))
if err != nil {
return "", err
}
return sb.String(), nil
}
func GenerateCasToken(userId string, service string) (string, error) {
user, err := GetUser(userId)
if err != nil {
@ -225,6 +235,11 @@ func GenerateCasToken(userId string, service string) (string, error) {
}
if value != "" {
if escapedValue, err := escapeXMLText(value); err != nil {
return "", err
} else {
value = escapedValue
}
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: value,

View File

@ -504,7 +504,7 @@ func GetPasswordToken(application *Application, username string, password string
}
if user.Ldap != "" {
err = checkLdapUserPassword(user, password, "en")
err = CheckLdapUserPassword(user, password, "en")
} else {
err = CheckPassword(user, password, "en")
}

View File

@ -679,6 +679,10 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
user.Password = oldUser.Password
}
if user.Id != oldUser.Id && user.Id == "" {
user.Id = oldUser.Id
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
if err != nil {

View File

@ -57,7 +57,7 @@ type VerificationRecord struct {
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
Code string `xorm:"varchar(10) notnull" json:"code"`
Time int64 `xorm:"notnull" json:"time"`
IsUsed bool
IsUsed bool `xorm:"notnull" json:"isUsed"`
}
func IsAllowSend(user *User, remoteAddr, recordType string) error {

View File

@ -23,7 +23,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "MinIO":
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint), nil
if region == "" {
region = "_"
}
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
case "Tencent Cloud COS":

View File

@ -19,6 +19,7 @@ import "codemirror/mode/properties/properties";
import * as Setting from "./Setting";
import IframeEditor from "./IframeEditor";
import {Tabs} from "antd";
import i18next from "i18next";
const {TabPane} = Tabs;
@ -68,8 +69,8 @@ const CasbinEditor = ({model, onModelTextChange}) => {
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" />
<TabPane tab={i18next.t("model:Basic Editor")} key="basic" />
<TabPane tab={i18next.t("model:Advanced Editor")} key="advanced" />
</Tabs>
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
{activeKey === "advanced" ? (

View File

@ -17,6 +17,7 @@ import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} fro
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
const iframeRef = useRef(null);
const [iframeReady, setIframeReady] = useState(false);
const currentLang = localStorage.getItem("language") || "en";
useEffect(() => {
const handleMessage = (event) => {
@ -26,24 +27,31 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
onModelTextChange(event.data.modelText);
} else if (event.data.type === "iframeReady") {
setIframeReady(true);
iframeRef.current?.contentWindow.postMessage({
type: "initializeModel",
modelText: initialModelText,
}, "*");
if (initialModelText && iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "initializeModel",
modelText: initialModelText,
lang: currentLang,
}, "*");
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [onModelTextChange, initialModelText]);
}, [onModelTextChange, initialModelText, currentLang]);
useImperativeHandle(ref, () => ({
getModelText: () => {
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "getModelText",
}, "*");
}
},
updateModelText: (newModelText) => {
if (iframeReady) {
iframeRef.current?.contentWindow.postMessage({
if (iframeReady && iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({
type: "updateModelText",
modelText: newModelText,
}, "*");
@ -54,7 +62,7 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
return (
<iframe
ref={iframeRef}
src="https://editor.casbin.org/model-editor"
src={`https://editor.casbin.org/model-editor?lang=${currentLang}`}
frameBorder="0"
width="100%"
height="500px"

View File

@ -932,7 +932,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
) : null}
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS"].includes(this.state.provider.type) ? (
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ?

View File

@ -141,6 +141,9 @@ const Dashboard = (props) => {
i18next.t("general:Certs"),
i18next.t("general:Permissions"),
i18next.t("general:Transactions"),
i18next.t("general:Models"),
i18next.t("general:Adapters"),
i18next.t("general:Enforcers"),
], top: "10%"},
grid: {left: "3%", right: "4%", bottom: "0", top: "25%", containLabel: true},
xAxis: {type: "category", boundaryGap: false, data: dateArray},
@ -157,6 +160,9 @@ const Dashboard = (props) => {
{name: i18next.t("general:Certs"), type: "line", data: dashboardData.certCounts},
{name: i18next.t("general:Permissions"), type: "line", data: dashboardData.permissionCounts},
{name: i18next.t("general:Transactions"), type: "line", data: dashboardData.transactionCounts},
{name: i18next.t("general:Models"), type: "line", data: dashboardData.modelCounts},
{name: i18next.t("general:Adapters"), type: "line", data: dashboardData.adapterCounts},
{name: i18next.t("general:Enforcers"), type: "line", data: dashboardData.enforcerCounts},
],
};
myChart.setOption(option);

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Pokročilý editor",
"Basic Editor": "Základní editor",
"Edit Model": "Upravit model",
"Model text": "Text modelu",
"Model text - Tooltip": "Casbin model řízení přístupu, včetně vestavěných modelů jako ACL, RBAC, ABAC, RESTful, atd. Můžete také vytvářet vlastní modely. Pro více informací navštivte webové stránky Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Erweiterter Editor",
"Basic Editor": "Basis-Editor",
"Edit Model": "Modell bearbeiten",
"Model text": "Modelltext",
"Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Editor avanzado",
"Basic Editor": "Editor básico",
"Edit Model": "Editar modelo",
"Model text": "Texto modelo",
"Model text - Tooltip": "Modelo de control de acceso Casbin, incluyendo modelos integrados como ACL, RBAC, ABAC, RESTful, etc. También puede crear modelos personalizados. Para obtener más información, visite el sitio web de Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "کلید مخفی"
},
"model": {
"Advanced Editor": "ویرایشگر پیشرفته",
"Basic Editor": "ویرایشگر ابتدایی",
"Edit Model": "ویرایش مدل",
"Model text": "متن مدل",
"Model text - Tooltip": "مدل کنترل دسترسی Casbin، شامل مدل‌های داخلی مانند ACL، RBAC، ABAC، RESTful و غیره. همچنین می‌توانید مدل‌های سفارشی ایجاد کنید. برای اطلاعات بیشتر، لطفاً به وب‌سایت Casbin مراجعه کنید",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Éditeur avancé",
"Basic Editor": "Éditeur de base",
"Edit Model": "Modifier le modèle",
"Model text": "Définition du modèle",
"Model text - Tooltip": "Modèle de contrôle d'accès Casbin, comprenant des modèles intégrés tels que ACL, RBAC, ABAC, RESTful, etc. Vous pouvez également créer des modèles personnalisés. Pour plus d'informations, veuillez visiter le site web de Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Editor lanjutan",
"Basic Editor": "Editor dasar",
"Edit Model": "Mengedit Model",
"Model text": "Teks Model",
"Model text - Tooltip": "Model kontrol akses Casbin, termasuk model bawaan seperti ACL, RBAC, ABAC, RESTful, dll. Anda juga dapat membuat model kustom. Untuk informasi lebih lanjut, silakan kunjungi situs web Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "編集モデル",
"Model text": "モデルテキスト",
"Model text - Tooltip": "Casbinのアクセス制御モデルには、ACL、RBAC、ABAC、RESTfulなどの組み込みモデルが含まれています。カスタムモデルも作成できます。詳細については、Casbinのウェブサイトをご覧ください",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "고급 편집기",
"Basic Editor": "기본 편집기",
"Edit Model": "편집 형태 모델",
"Model text": "모델 텍스트",
"Model text - Tooltip": "Casbin 액세스 제어 모델은 ACL, RBAC, ABAC, RESTful 등의 내장된 모델을 포함하며 사용자 정의 모델도 만들 수 있습니다. 자세한 정보는 Casbin 웹 사이트를 방문하십시오",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Editor Avançado",
"Basic Editor": "Editor Básico",
"Edit Model": "Editar Modelo",
"Model text": "Texto do Modelo",
"Model text - Tooltip": "Modelo de controle de acesso Casbin, incluindo modelos incorporados como ACL, RBAC, ABAC, RESTful, etc. Você também pode criar modelos personalizados. Para obter mais informações, visite o site do Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Расширенный редактор",
"Basic Editor": "Базовый редактор",
"Edit Model": "Редактировать модель",
"Model text": "Модельный текст",
"Model text - Tooltip": "Модель контроля доступа Casbin, включая встроенные модели, такие как ACL, RBAC, ABAC, RESTful и т. д. Вы также можете создавать свои собственные модели. Для получения дополнительной информации, пожалуйста, посетите веб-сайт Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Rozšírený editor",
"Basic Editor": "Základný editor",
"Edit Model": "Upraviť model",
"Model text": "Text modelu",
"Model text - Tooltip": "Model prístupu Casbin, vrátane vstavaných modelov ako ACL, RBAC, ABAC, RESTful, atď. Môžete tiež vytvoriť vlastné modely. Pre viac informácií navštívte web Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Edit Model",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Advanced Editor",
"Basic Editor": "Basic Editor",
"Edit Model": "Modeli Düzenle",
"Model text": "Model text",
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Розширений редактор",
"Basic Editor": "Базовий редактор",
"Edit Model": "Редагувати модель",
"Model text": "Текст моделі",
"Model text - Tooltip": "Модель контролю доступу Casbin, включаючи такі вбудовані моделі, як ACL, RBAC, ABAC, RESTful тощо. Ви також можете створювати власні моделі. ",

View File

@ -592,6 +592,8 @@
"Secret Key": "Secret Key"
},
"model": {
"Advanced Editor": "Editor nâng cao",
"Basic Editor": "Editor cơ bản",
"Edit Model": "Sửa mô hình",
"Model text": "Văn bản mẫu",
"Model text - Tooltip": "Mô hình kiểm soát truy cập Casbin, bao gồm các mô hình tích hợp như ACL, RBAC, ABAC, RESTful, v.v. Bạn cũng có thể tạo các mô hình tùy chỉnh. Để biết thêm thông tin, vui lòng truy cập trang web Casbin",

View File

@ -592,6 +592,8 @@
"Secret Key": "密钥"
},
"model": {
"Advanced Editor": "高级编辑器",
"Basic Editor": "基础编辑器",
"Edit Model": "编辑模型",
"Model text": "模型文本",
"Model text - Tooltip": "Casbin访问控制模型支持ACL、RBAC、ABAC、RESTful等内置模型也可以自定义模型具体请查看Casbin官网",