Compare commits

..

23 Commits

Author SHA1 Message Date
OutOfEastGate
ee5c3f3f39 feat: fix display name null error during 3rd-party binding (#1747) 2023-04-17 15:39:33 +08:00
Yang Luo
714f69be7b Use HTTP for IP host in getOriginFromHost() 2023-04-17 00:55:40 +08:00
Yang Luo
0d12972e92 Fix "auto single OAuth signin doesn't work" bug 2023-04-17 00:38:48 +08:00
Yang Luo
78b62c28ab Fix the wrong order of g policy in enforce() API 2023-04-16 22:26:22 +08:00
wht
5c26335fd6 feat: add rule option for phone in application's signup page (#1745) 2023-04-16 20:34:06 +08:00
Yang Luo
7edaeafea5 Call refreshAvatar() in addUser() 2023-04-16 01:00:02 +08:00
Yang Luo
336f3f7a7b Add user.refreshAvatar() 2023-04-16 01:00:02 +08:00
Yaodong Yu
47dc3715f9 feat: handle error when parsing samlResponse (#1744)
* fix: handle err from parse samlResponse

* fix: lint
2023-04-16 00:36:25 +08:00
Yang Luo
7503e05a4a Improve menu style 2023-04-15 18:08:21 +08:00
Yang Luo
b89cf1de07 Add karma to account items 2023-04-15 16:05:33 +08:00
Yang Luo
be87078c25 Fix vi i18n 2023-04-15 14:16:49 +08:00
Yang Luo
faf352acc5 Fix i18n 2023-04-15 11:17:31 +08:00
Yang Luo
0db61dd658 Add empty list item and expand menu by default 2023-04-15 10:54:56 +08:00
Yang Luo
ebe8ad8669 Improve UI effect 2023-04-15 10:54:56 +08:00
Yang Luo
2e01f0d10e Add input box 2023-04-15 10:54:55 +08:00
Yang Luo
754fa1e745 Add chat box 2023-04-15 10:54:55 +08:00
Yang Luo
8b9e0ba96b Add chat page 2023-04-15 10:54:55 +08:00
Yang Luo
b0656aca36 Add chat and message pages 2023-04-15 10:54:54 +08:00
erguotou
623b4fee17 feat: pre-ensure tempFiles folder exists before uploading files (#1739)
When deployed with docker, the user `casdoor` has no permission to mkdir `tempFiles`, so let's create the folder first.
2023-04-14 19:14:59 +08:00
Yaodong Yu
1b1de1dd01 feat: add LDAP custom filter support (#1719)
* refactor: improve ldap server code

* feat: custom filter

* fix: fix displayName mapping

* feat: add custom filter search fields

* chore: add license

* chore: i18n

* chore: i18n

* chore: update init field
2023-04-13 14:12:31 +08:00
XDTD
968d8646b2 fix: update webAuthnBufferDecode to support Base64URL for WebAuthn updates (#1734) 2023-04-12 21:33:54 +08:00
imp2002
94eef7dceb feat: fix adapter set organizations invalid bug (#1729) 2023-04-11 22:38:00 +08:00
rune
fe647939ce fix: fix CAS callback url not match bug (#1728)
Co-authored-by: mfk <mfk@hengwei.com.cn>
2023-04-11 19:26:57 +08:00
72 changed files with 1927 additions and 724 deletions

View File

@@ -64,6 +64,7 @@ COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
COPY --from=FRONT /web/build ./web/build COPY --from=FRONT /web/build ./web/build
RUN mkdir tempFiles
ENTRYPOINT ["/bin/bash"] ENTRYPOINT ["/bin/bash"]
CMD ["/docker-entrypoint.sh"] CMD ["/docker-entrypoint.sh"]

View File

@@ -20,5 +20,5 @@ staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false isDemoMode = false
batchSize = 100 batchSize = 100
ldapServerPort = 389 ldapServerPort = 389
languages = en,zh,es,fr,de,id,ja,ko,ru,vn languages = en,zh,es,fr,de,id,ja,ko,ru,vi
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1} quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}

View File

@@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
} }
var checkPhone string var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" { if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" {
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode) checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage()) checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
if checkResult.Code != object.VerificationSuccess { if checkResult.Code != object.VerificationSuccess {

View File

@@ -339,7 +339,7 @@ func (c *ApiController) Login() {
userInfo := &idp.UserInfo{} userInfo := &idp.UserInfo{}
if provider.Category == "SAML" { if provider.Category == "SAML" {
// SAML // SAML
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type) userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider, c.Ctx.Request.Host)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -546,7 +546,7 @@ func (c *ApiController) Login() {
func (c *ApiController) GetSamlLogin() { func (c *ApiController) GetSamlLogin() {
providerId := c.Input().Get("id") providerId := c.Input().Get("id")
relayState := c.Input().Get("relayState") relayState := c.Input().Get("relayState")
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState, c.GetAcceptLanguage()) authURL, method, err := object.GenerateSamlRequest(providerId, relayState, c.Ctx.Request.Host, c.GetAcceptLanguage())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
} }

View File

@@ -72,6 +72,11 @@ func (c *RootController) CasProxyValidate() {
c.CasP3ServiceAndProxyValidate() c.CasP3ServiceAndProxyValidate()
} }
func queryUnescape(service string) string {
s, _ := url.QueryUnescape(service)
return s
}
func (c *RootController) CasP3ServiceAndProxyValidate() { func (c *RootController) CasP3ServiceAndProxyValidate() {
ticket := c.Input().Get("ticket") ticket := c.Input().Get("ticket")
format := c.Input().Get("format") format := c.Input().Get("format")
@@ -91,7 +96,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
// find the token // find the token
if ok { if ok {
// check whether service is the one for which we previously issued token // check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) { if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
serviceResponse.Success = response serviceResponse.Success = response
} else { } else {
// service not match // service not match

View File

@@ -65,7 +65,7 @@ func (c *ApiController) GetLdapUsers() {
// }) // })
//} //}
users, err := conn.GetLdapUsers(ldapServer.BaseDn) users, err := conn.GetLdapUsers(ldapServer)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -80,10 +80,11 @@ func (c *ApiController) GetLdapUsers() {
Cn: user.Cn, Cn: user.Cn,
GroupId: user.GidNumber, GroupId: user.GidNumber,
// GroupName: groupsMap[user.GidNumber].Cn, // GroupName: groupsMap[user.GidNumber].Cn,
Uuid: user.Uuid, Uuid: user.Uuid,
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress), DisplayName: user.DisplayName,
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber), Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress), Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
}) })
uuids = append(uuids, user.Uuid) uuids = append(uuids, user.Uuid)
} }
@@ -131,7 +132,7 @@ func (c *ApiController) AddLdap() {
return return
} }
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) { if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
c.ResponseError(c.T("general:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@@ -160,7 +161,7 @@ func (c *ApiController) AddLdap() {
func (c *ApiController) UpdateLdap() { func (c *ApiController) UpdateLdap() {
var ldap object.Ldap var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap) err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) { if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
c.ResponseError(c.T("general:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }

View File

@@ -37,8 +37,17 @@ func (c *ApiController) GetMessages() {
value := c.Input().Get("value") value := c.Input().Get("value")
sortField := c.Input().Get("sortField") sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
chat := c.Input().Get("chat")
if limit == "" || page == "" { if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedMessages(object.GetMessages(owner)) var messages []*object.Message
if chat == "" {
messages = object.GetMessages(owner)
} else {
messages = object.GetChatMessages(chat)
}
c.Data["json"] = object.GetMaskedMessages(messages)
c.ServeJSON() c.ServeJSON()
} else { } else {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)

4
go.mod
View File

@@ -17,14 +17,16 @@ require (
github.com/casdoor/xorm-adapter/v3 v3.0.4 github.com/casdoor/xorm-adapter/v3 v3.0.4
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0 github.com/denisenkom/go-mssqldb v0.9.0
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0 github.com/forestmgy/ldapserver v1.1.0
github.com/go-git/go-git/v5 v5.6.0 github.com/go-git/go-git/v5 v5.6.0
github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-mysql-org/go-mysql v1.7.0 github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/go-webauthn/webauthn v0.5.0 github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect

15
go.sum
View File

@@ -173,6 +173,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4= github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM= github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
@@ -224,8 +226,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU= github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8= github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc= github.com/go-webauthn/webauthn v0.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE= github.com/go-webauthn/webauthn v0.6.0/go.mod h1:7edMRZXwuM6JIVjN68G24Bzt+bPCvTmjiL0j+cAmXtY=
github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E= github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -234,11 +236,13 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -662,6 +666,7 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -676,6 +681,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -743,6 +749,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -835,6 +842,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -845,6 +853,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -31,7 +31,7 @@ func TestGenerateI18nFrontend(t *testing.T) {
applyToOtherLanguage("frontend", "ja", data) applyToOtherLanguage("frontend", "ja", data)
applyToOtherLanguage("frontend", "ko", data) applyToOtherLanguage("frontend", "ko", data)
applyToOtherLanguage("frontend", "ru", data) applyToOtherLanguage("frontend", "ru", data)
applyToOtherLanguage("frontend", "vn", data) applyToOtherLanguage("frontend", "vi", data)
} }
func TestGenerateI18nBackend(t *testing.T) { func TestGenerateI18nBackend(t *testing.T) {
@@ -46,5 +46,5 @@ func TestGenerateI18nBackend(t *testing.T) {
applyToOtherLanguage("backend", "ja", data) applyToOtherLanguage("backend", "ja", data)
applyToOtherLanguage("backend", "ko", data) applyToOtherLanguage("backend", "ko", data)
applyToOtherLanguage("backend", "ru", data) applyToOtherLanguage("backend", "ru", data)
applyToOtherLanguage("backend", "vn", data) applyToOtherLanguage("backend", "vi", data)
} }

View File

@@ -32,8 +32,8 @@
"Email is invalid": "E-Mail ist ungültig", "Email is invalid": "E-Mail ist ungültig",
"Empty username.": "Leerer Benutzername.", "Empty username.": "Leerer Benutzername.",
"FirstName cannot be blank": "Vorname darf nicht leer sein", "FirstName cannot be blank": "Vorname darf nicht leer sein",
"LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
"LastName cannot be blank": "Nachname darf nicht leer sein", "LastName cannot be blank": "Nachname darf nicht leer sein",
"Ldap user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
"Multiple accounts with same uid, please check your ldap server": "Mehrere Konten mit derselben uid, bitte überprüfen Sie Ihren LDAP-Server", "Multiple accounts with same uid, please check your ldap server": "Mehrere Konten mit derselben uid, bitte überprüfen Sie Ihren LDAP-Server",
"Organization does not exist": "Organisation existiert nicht", "Organization does not exist": "Organisation existiert nicht",
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten", "Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Die Telefonnummer ist ungültig", "Phone number is invalid": "Die Telefonnummer ist ungültig",
"Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden", "Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden",
"The user is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator", "The user is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.",
"Username already exists": "Benutzername existiert bereits", "Username already exists": "Benutzername existiert bereits",
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein", "Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "LastName cannot be blank": "LastName cannot be blank",
"Ldap user name or password incorrect": "Ldap user name or password incorrect",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server", "Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters", "Password must have at least 6 characters": "Password must have at least 6 characters",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again", "Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists", "Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address", "Username cannot be an email address": "Username cannot be an email address",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "El correo electrónico no es válido", "Email is invalid": "El correo electrónico no es válido",
"Empty username.": "Nombre de usuario vacío.", "Empty username.": "Nombre de usuario vacío.",
"FirstName cannot be blank": "El nombre no puede estar en blanco", "FirstName cannot be blank": "El nombre no puede estar en blanco",
"LDAP user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
"LastName cannot be blank": "El apellido no puede estar en blanco", "LastName cannot be blank": "El apellido no puede estar en blanco",
"Ldap user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
"Multiple accounts with same uid, please check your ldap server": "Cuentas múltiples con el mismo uid, por favor revise su servidor ldap", "Multiple accounts with same uid, please check your ldap server": "Cuentas múltiples con el mismo uid, por favor revise su servidor ldap",
"Organization does not exist": "La organización no existe", "Organization does not exist": "La organización no existe",
"Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres", "Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres",
@@ -42,6 +42,7 @@
"Phone number is invalid": "El número de teléfono no es válido", "Phone number is invalid": "El número de teléfono no es válido",
"Session outdated, please login again": "Sesión expirada, por favor vuelva a iniciar sesión", "Session outdated, please login again": "Sesión expirada, por favor vuelva a iniciar sesión",
"The user is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador", "The user is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.",
"Username already exists": "El nombre de usuario ya existe", "Username already exists": "El nombre de usuario ya existe",
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico", "Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "L'adresse e-mail est invalide", "Email is invalid": "L'adresse e-mail est invalide",
"Empty username.": "Nom d'utilisateur vide.", "Empty username.": "Nom d'utilisateur vide.",
"FirstName cannot be blank": "Le prénom ne peut pas être laissé vide", "FirstName cannot be blank": "Le prénom ne peut pas être laissé vide",
"LDAP user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
"LastName cannot be blank": "Le nom de famille ne peut pas être vide", "LastName cannot be blank": "Le nom de famille ne peut pas être vide",
"Ldap user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
"Multiple accounts with same uid, please check your ldap server": "Plusieurs comptes avec le même identifiant d'utilisateur, veuillez vérifier votre serveur LDAP", "Multiple accounts with same uid, please check your ldap server": "Plusieurs comptes avec le même identifiant d'utilisateur, veuillez vérifier votre serveur LDAP",
"Organization does not exist": "L'organisation n'existe pas", "Organization does not exist": "L'organisation n'existe pas",
"Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères", "Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Le numéro de téléphone est invalide", "Phone number is invalid": "Le numéro de téléphone est invalide",
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau", "Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur", "The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
"Username already exists": "Nom d'utilisateur existe déjà", "Username already exists": "Nom d'utilisateur existe déjà",
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail", "Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "Email tidak valid", "Email is invalid": "Email tidak valid",
"Empty username.": "Nama pengguna kosong.", "Empty username.": "Nama pengguna kosong.",
"FirstName cannot be blank": "Nama depan tidak boleh kosong", "FirstName cannot be blank": "Nama depan tidak boleh kosong",
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
"LastName cannot be blank": "Nama belakang tidak boleh kosong", "LastName cannot be blank": "Nama belakang tidak boleh kosong",
"Ldap user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda", "Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda",
"Organization does not exist": "Organisasi tidak ada", "Organization does not exist": "Organisasi tidak ada",
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter", "Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Nomor telepon tidak valid", "Phone number is invalid": "Nomor telepon tidak valid",
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi", "Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
"The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator", "The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
"Username already exists": "Nama pengguna sudah ada", "Username already exists": "Nama pengguna sudah ada",
"Username cannot be an email address": "Username tidak bisa menjadi alamat email", "Username cannot be an email address": "Username tidak bisa menjadi alamat email",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "電子メールは無効です", "Email is invalid": "電子メールは無効です",
"Empty username.": "空のユーザー名。", "Empty username.": "空のユーザー名。",
"FirstName cannot be blank": "ファーストネームは空白にできません", "FirstName cannot be blank": "ファーストネームは空白にできません",
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
"LastName cannot be blank": "姓は空白にできません", "LastName cannot be blank": "姓は空白にできません",
"Ldap user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください", "Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
"Organization does not exist": "組織は存在しません", "Organization does not exist": "組織は存在しません",
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です", "Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
@@ -42,6 +42,7 @@
"Phone number is invalid": "電話番号が無効です", "Phone number is invalid": "電話番号が無効です",
"Session outdated, please login again": "セッションが期限切れになりました。再度ログインしてください", "Session outdated, please login again": "セッションが期限切れになりました。再度ログインしてください",
"The user is forbidden to sign in, please contact the administrator": "ユーザーはサインインできません。管理者に連絡してください", "The user is forbidden to sign in, please contact the administrator": "ユーザーはサインインできません。管理者に連絡してください",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "ユーザー名には英数字、アンダースコア、ハイフンしか含めることができません。連続したハイフンまたはアンダースコアは不可であり、ハイフンまたはアンダースコアで始まるまたは終わることもできません。", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "ユーザー名には英数字、アンダースコア、ハイフンしか含めることができません。連続したハイフンまたはアンダースコアは不可であり、ハイフンまたはアンダースコアで始まるまたは終わることもできません。",
"Username already exists": "ユーザー名はすでに存在しています", "Username already exists": "ユーザー名はすでに存在しています",
"Username cannot be an email address": "ユーザー名には電子メールアドレスを使用できません", "Username cannot be an email address": "ユーザー名には電子メールアドレスを使用できません",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "이메일이 유효하지 않습니다", "Email is invalid": "이메일이 유효하지 않습니다",
"Empty username.": "빈 사용자 이름.", "Empty username.": "빈 사용자 이름.",
"FirstName cannot be blank": "이름은 공백일 수 없습니다", "FirstName cannot be blank": "이름은 공백일 수 없습니다",
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
"LastName cannot be blank": "성은 비어 있을 수 없습니다", "LastName cannot be blank": "성은 비어 있을 수 없습니다",
"Ldap user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요", "Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
"Organization does not exist": "조직은 존재하지 않습니다", "Organization does not exist": "조직은 존재하지 않습니다",
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다", "Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
@@ -42,6 +42,7 @@
"Phone number is invalid": "전화번호가 유효하지 않습니다", "Phone number is invalid": "전화번호가 유효하지 않습니다",
"Session outdated, please login again": "세션이 만료되었습니다. 다시 로그인해주세요", "Session outdated, please login again": "세션이 만료되었습니다. 다시 로그인해주세요",
"The user is forbidden to sign in, please contact the administrator": "사용자는 로그인이 금지되어 있습니다. 관리자에게 문의하십시오", "The user is forbidden to sign in, please contact the administrator": "사용자는 로그인이 금지되어 있습니다. 관리자에게 문의하십시오",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "사용자 이름은 알파벳, 숫자, 밑줄 또는 하이픈만 포함할 수 있으며, 연속된 하이픈 또는 밑줄을 가질 수 없으며, 하이픈 또는 밑줄로 시작하거나 끝날 수 없습니다.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "사용자 이름은 알파벳, 숫자, 밑줄 또는 하이픈만 포함할 수 있으며, 연속된 하이픈 또는 밑줄을 가질 수 없으며, 하이픈 또는 밑줄로 시작하거나 끝날 수 없습니다.",
"Username already exists": "사용자 이름이 이미 존재합니다", "Username already exists": "사용자 이름이 이미 존재합니다",
"Username cannot be an email address": "사용자 이름은 이메일 주소가 될 수 없습니다", "Username cannot be an email address": "사용자 이름은 이메일 주소가 될 수 없습니다",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "Адрес электронной почты недействительный", "Email is invalid": "Адрес электронной почты недействительный",
"Empty username.": "Пустое имя пользователя.", "Empty username.": "Пустое имя пользователя.",
"FirstName cannot be blank": "Имя не может быть пустым", "FirstName cannot be blank": "Имя не может быть пустым",
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
"LastName cannot be blank": "Фамилия не может быть пустой", "LastName cannot be blank": "Фамилия не может быть пустой",
"Ldap user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP", "Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
"Organization does not exist": "Организация не существует", "Organization does not exist": "Организация не существует",
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов", "Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Номер телефона является недействительным", "Phone number is invalid": "Номер телефона является недействительным",
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова", "Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору", "The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
"Username already exists": "Имя пользователя уже существует", "Username already exists": "Имя пользователя уже существует",
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты", "Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "Địa chỉ email không hợp lệ", "Email is invalid": "Địa chỉ email không hợp lệ",
"Empty username.": "Tên đăng nhập trống.", "Empty username.": "Tên đăng nhập trống.",
"FirstName cannot be blank": "Tên không được để trống", "FirstName cannot be blank": "Tên không được để trống",
"LDAP user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
"LastName cannot be blank": "Họ không thể để trống", "LastName cannot be blank": "Họ không thể để trống",
"Ldap user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
"Multiple accounts with same uid, please check your ldap server": "Nhiều tài khoản với cùng một uid, vui lòng kiểm tra máy chủ ldap của bạn", "Multiple accounts with same uid, please check your ldap server": "Nhiều tài khoản với cùng một uid, vui lòng kiểm tra máy chủ ldap của bạn",
"Organization does not exist": "Tổ chức không tồn tại", "Organization does not exist": "Tổ chức không tồn tại",
"Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự", "Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự",
@@ -42,6 +42,7 @@
"Phone number is invalid": "Số điện thoại không hợp lệ", "Phone number is invalid": "Số điện thoại không hợp lệ",
"Session outdated, please login again": "Phiên làm việc hết hạn, vui lòng đăng nhập lại", "Session outdated, please login again": "Phiên làm việc hết hạn, vui lòng đăng nhập lại",
"The user is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên", "The user is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.",
"Username already exists": "Tên đăng nhập đã tồn tại", "Username already exists": "Tên đăng nhập đã tồn tại",
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email", "Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",

View File

@@ -32,8 +32,8 @@
"Email is invalid": "无效邮箱", "Email is invalid": "无效邮箱",
"Empty username.": "用户名不可为空", "Empty username.": "用户名不可为空",
"FirstName cannot be blank": "名不可以为空", "FirstName cannot be blank": "名不可以为空",
"LDAP user name or password incorrect": "LDAP密码错误",
"LastName cannot be blank": "姓不可以为空", "LastName cannot be blank": "姓不可以为空",
"Ldap user name or password incorrect": "LDAP密码错误",
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid请检查您的 LDAP 服务器", "Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid请检查您的 LDAP 服务器",
"Organization does not exist": "组织不存在", "Organization does not exist": "组织不存在",
"Password must have at least 6 characters": "新密码至少为6位", "Password must have at least 6 characters": "新密码至少为6位",
@@ -42,6 +42,7 @@
"Phone number is invalid": "无效手机号", "Phone number is invalid": "无效手机号",
"Session outdated, please login again": "会话已过期,请重新登录", "Session outdated, please login again": "会话已过期,请重新登录",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员", "The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
"Username already exists": "用户名已存在", "Username already exists": "用户名已存在",
"Username cannot be an email address": "用户名不可以是邮箱地址", "Username cannot be an email address": "用户名不可以是邮箱地址",

View File

@@ -12,7 +12,7 @@
"defaultAvatar": "", "defaultAvatar": "",
"defaultApplication": "", "defaultApplication": "",
"tags": [], "tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"], "languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"],
"masterPassword": "", "masterPassword": "",
"initScore": 2000, "initScore": 2000,
"enableSoftDeletion": false, "enableSoftDeletion": false,
@@ -159,8 +159,8 @@
"serverName": "", "serverName": "",
"host": "", "host": "",
"port": 389, "port": 389,
"admin": "", "username": "",
"passwd": "", "password": "",
"baseDn": "", "baseDn": "",
"autoSync": 0, "autoSync": 0,
"lastSync": "" "lastSync": ""

View File

@@ -110,12 +110,11 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
for _, user := range users { for _, user := range users {
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject())) dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
e := ldap.NewSearchResultEntry(dn) e := ldap.NewSearchResultEntry(dn)
e.AddAttribute("cn", message.AttributeValue(user.Name))
e.AddAttribute("uid", message.AttributeValue(user.Name)) for _, attr := range r.Attributes() {
e.AddAttribute("email", message.AttributeValue(user.Email)) e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
e.AddAttribute("mobile", message.AttributeValue(user.Phone)) }
e.AddAttribute("userPassword", message.AttributeValue(getUserPasswordWithType(user)))
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
w.Write(e) w.Write(e)
} }
w.Write(res) w.Write(res)

View File

@@ -21,6 +21,7 @@ import (
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/lor00x/goldap/message"
ldap "github.com/forestmgy/ldapserver" ldap "github.com/forestmgy/ldapserver"
) )
@@ -68,6 +69,7 @@ func getUsername(filter string) string {
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) { func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
r := m.GetSearchRequest() r := m.GetSearchRequest()
name, org, code := getNameAndOrgFromFilter(string(r.BaseObject()), r.FilterString()) name, org, code := getNameAndOrgFromFilter(string(r.BaseObject()), r.FilterString())
if code != ldap.LDAPResultSuccess { if code != ldap.LDAPResultSuccess {
return nil, code return nil, code
@@ -114,3 +116,20 @@ func getUserPasswordWithType(user *object.User) string {
} }
return fmt.Sprintf("{%s}%s", prefix, user.Password) return fmt.Sprintf("{%s}%s", prefix, user.Password)
} }
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
switch attributeName {
case "cn":
return message.AttributeValue(user.Name)
case "uid":
return message.AttributeValue(user.Name)
case "email":
return message.AttributeValue(user.Email)
case "mobile":
return message.AttributeValue(user.Phone)
case "userPassword":
return message.AttributeValue(getUserPasswordWithType(user))
default:
return ""
}
}

View File

@@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
panic(err) panic(err)
} }
} }
func getPermanentAvatarUrlFromBuffer(organization string, username string, fileBuffer *bytes.Buffer, ext string, upload bool) string {
if defaultStorageProvider == nil {
return ""
}
fullFilePath := fmt.Sprintf("/avatar/%s/%s%s", organization, username, ext)
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
if upload {
_, _, err := UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer, "en")
if err != nil {
panic(err)
}
}
return uploadedFileUrl
}

View File

@@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
@@ -37,3 +38,22 @@ func TestSyncPermanentAvatars(t *testing.T) {
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar) fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
} }
} }
func TestUpdateAvatars(t *testing.T) {
InitConfig()
InitDefaultStorageProvider()
proxy.InitHttpClient()
users := GetUsers("casdoor")
for _, user := range users {
if strings.HasPrefix(user.Avatar, "http") {
continue
}
updated := user.refreshAvatar()
if updated {
user.PermanentAvatar = "*"
UpdateUser(user.GetId(), user, []string{"avatar"}, true)
}
}
}

167
object/avatar_util.go Normal file
View File

@@ -0,0 +1,167 @@
// Copyright 2023 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.
package object
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/color"
"image/png"
"io"
"net/http"
"strings"
"github.com/fogleman/gg"
)
func hasGravatar(client *http.Client, email string) (bool, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL with d=404 parameter
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
// Send a request to Gravatar
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Check if the user has a custom Gravatar image
if resp.StatusCode == http.StatusOK {
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
return false, nil
} else {
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
}
}
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
// Download the image
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return nil, "", err
}
resp, err := client.Do(req)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
}
// Get the content type and determine the file extension
contentType := resp.Header.Get("Content-Type")
fileExtension := ""
switch contentType {
case "image/jpeg":
fileExtension = ".jpg"
case "image/png":
fileExtension = ".png"
case "image/gif":
fileExtension = ".gif"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}
// Save the image to a bytes.Buffer
buffer := &bytes.Buffer{}
_, err = io.Copy(buffer, resp.Body)
if err != nil {
return nil, "", err
}
return buffer, fileExtension, nil
}
func getColor(data []byte) color.RGBA {
r := int(data[0]) % 256
g := int(data[1]) % 256
b := int(data[2]) % 256
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
username = strings.TrimSpace(strings.ToLower(username))
hash := md5.New()
io.WriteString(hash, username)
hashedUsername := hash.Sum(nil)
// Define the size of the image
const imageSize = 420
const cellSize = imageSize / 7
// Create a new image
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
// Create a context
dc := gg.NewContextForRGBA(img)
// Set a background color
dc.SetColor(color.RGBA{240, 240, 240, 255})
dc.Clear()
// Get avatar color
avatarColor := getColor(hashedUsername)
// Draw cells
for i := 0; i < 7; i++ {
for j := 0; j < 7; j++ {
if (hashedUsername[i] >> uint(j) & 1) == 1 {
dc.SetColor(avatarColor)
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
dc.Fill()
}
}
}
// Save image to a bytes.Buffer
buffer := &bytes.Buffer{}
err := png.Encode(buffer, img)
if err != nil {
return nil, "", fmt.Errorf("failed to save image: %w", err)
}
return buffer, ".png", nil
}

View File

@@ -29,6 +29,8 @@ type Chat struct {
Organization string `xorm:"varchar(100)" json:"organization"` Organization string `xorm:"varchar(100)" json:"organization"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Type string `xorm:"varchar(100)" json:"type"`
Category string `xorm:"varchar(100)" json:"category"`
User1 string `xorm:"varchar(100)" json:"user1"` User1 string `xorm:"varchar(100)" json:"user1"`
User2 string `xorm:"varchar(100)" json:"user2"` User2 string `xorm:"varchar(100)" json:"user2"`
Users []string `xorm:"varchar(100)" json:"users"` Users []string `xorm:"varchar(100)" json:"users"`

View File

@@ -188,29 +188,33 @@ func CheckPassword(user *User, password string, lang string) string {
} }
} }
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) { func checkLdapUserPassword(user *User, password string, lang string) string {
ldaps := GetLdaps(user.Owner) ldaps := GetLdaps(user.Owner)
ldapLoginSuccess := false ldapLoginSuccess := false
hit := false
for _, ldapServer := range ldaps { for _, ldapServer := range ldaps {
conn, err := ldapServer.GetLdapConn() conn, err := ldapServer.GetLdapConn()
if err != nil { if err != nil {
continue continue
} }
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false, 0, 0, false, ldapServer.buildFilterString(user), []string{}, nil)
SearchFilter, []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq) searchResult, err := conn.Conn.Search(searchReq)
if err != nil { if err != nil {
return nil, err.Error() return err.Error()
} }
if len(searchResult.Entries) == 0 { if len(searchResult.Entries) == 0 {
continue continue
} else if len(searchResult.Entries) > 1 { }
return nil, i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server") if len(searchResult.Entries) > 1 {
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
} }
hit = true
dn := searchResult.Entries[0].DN dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil { if err := conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true ldapLoginSuccess = true
@@ -219,9 +223,12 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
} }
if !ldapLoginSuccess { if !ldapLoginSuccess {
return nil, i18n.Translate(lang, "check:Ldap user name or password incorrect") if !hit {
return "user not exist"
}
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
} }
return user, "" return ""
} }
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) { func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
@@ -236,10 +243,14 @@ func CheckUserPassword(organization string, username string, password string, la
if user.Ldap != "" { if user.Ldap != "" {
// ONLY for ldap users // ONLY for ldap users
return checkLdapUserPassword(user, password, lang) if msg := checkLdapUserPassword(user, password, lang); msg != "" {
if msg == "user not exist" {
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
}
return nil, msg
}
} else { } else {
msg := CheckPassword(user, password, lang) if msg := CheckPassword(user, password, lang); msg != "" {
if msg != "" {
return nil, msg return nil, msg
} }
} }

View File

@@ -89,7 +89,7 @@ func initBuiltInOrganization() bool {
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"}, CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"}, Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"},
InitScore: 2000, InitScore: 2000,
AccountItems: getBuiltInAccountItems(), AccountItems: getBuiltInAccountItems(),
EnableSoftDeletion: false, EnableSoftDeletion: false,
@@ -219,8 +219,8 @@ func initBuiltInLdap() {
ServerName: "BuildIn LDAP Server", ServerName: "BuildIn LDAP Server",
Host: "example.com", Host: "example.com",
Port: 389, Port: 389,
Admin: "cn=buildin,dc=example,dc=com", Username: "cn=buildin,dc=example,dc=com",
Passwd: "123", Password: "123",
BaseDn: "ou=BuildIn,dc=example,dc=com", BaseDn: "ou=BuildIn,dc=example,dc=com",
AutoSync: 0, AutoSync: 0,
LastSync: "", LastSync: "",

View File

@@ -15,14 +15,7 @@
package object package object
import ( import (
"errors"
"fmt"
"strings"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
) )
type Ldap struct { type Ldap struct {
@@ -30,263 +23,20 @@ type Ldap struct {
Owner string `xorm:"varchar(100)" json:"owner"` Owner string `xorm:"varchar(100)" json:"owner"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
ServerName string `xorm:"varchar(100)" json:"serverName"` ServerName string `xorm:"varchar(100)" json:"serverName"`
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `xorm:"int" json:"port"`
EnableSsl bool `xorm:"bool" json:"enableSsl"` EnableSsl bool `xorm:"bool" json:"enableSsl"`
Admin string `xorm:"varchar(100)" json:"admin"` Username string `xorm:"varchar(100)" json:"username"`
Passwd string `xorm:"varchar(100)" json:"passwd"` Password string `xorm:"varchar(100)" json:"password"`
BaseDn string `xorm:"varchar(100)" json:"baseDn"` BaseDn string `xorm:"varchar(100)" json:"baseDn"`
Filter string `xorm:"varchar(200)" json:"filter"`
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
AutoSync int `json:"autoSync"` AutoSync int `json:"autoSync"`
LastSync string `xorm:"varchar(100)" json:"lastSync"` LastSync string `xorm:"varchar(100)" json:"lastSync"`
} }
type ldapConn struct {
Conn *goldap.Conn
IsAD bool
}
//type ldapGroup struct {
// GidNumber string
// Cn string
//}
type ldapUser struct {
UidNumber string
Uid string
Cn string
GidNumber string
// Gcn string
Uuid string
Mail string
Email string
EmailAddress string
TelephoneNumber string
Mobile string
MobileTelephoneNumber string
RegisteredAddress string
PostalAddress string
}
type LdapRespUser struct {
UidNumber string `json:"uidNumber"`
Uid string `json:"uid"`
Cn string `json:"cn"`
GroupId string `json:"groupId"`
// GroupName string `json:"groupName"`
Uuid string `json:"uuid"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
}
type ldapServerType struct {
Vendorname string
Vendorversion string
IsGlobalCatalogReady string
ForestFunctionality string
}
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
returnAnyNotEmpty := func(strs ...string) string {
for _, str := range strs {
if str != "" {
return str
}
}
return ""
}
res := make([]LdapRespUser, 0)
for _, user := range users {
res = append(res, LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.Uuid,
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
})
}
return res
}
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectClass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
searchReq := goldap.NewSearchRequest("",
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
searchResult, err := Conn.Search(searchReq)
if err != nil {
return false, err
}
if len(searchResult.Entries) == 0 {
return false, nil
}
isMicrosoft := false
var ldapServerType ldapServerType
for _, entry := range searchResult.Entries {
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "vendorname":
ldapServerType.Vendorname = attribute.Values[0]
case "vendorversion":
ldapServerType.Vendorversion = attribute.Values[0]
case "isGlobalCatalogReady":
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
case "forestFunctionality":
ldapServerType.ForestFunctionality = attribute.Values[0]
}
}
}
if ldapServerType.Vendorname == "" &&
ldapServerType.Vendorversion == "" &&
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
ldapServerType.ForestFunctionality != "" {
isMicrosoft = true
}
return isMicrosoft, err
}
func (ldap *Ldap) GetLdapConn() (c *ldapConn, err error) {
var conn *goldap.Conn
if ldap.EnableSsl {
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
} else {
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
}
if err != nil {
return nil, err
}
err = conn.Bind(ldap.Admin, ldap.Passwd)
if err != nil {
return nil, err
}
isAD, err := isMicrosoftAD(conn)
if err != nil {
return nil, err
}
return &ldapConn{Conn: conn, IsAD: isAD}, nil
}
//FIXME: The Base DN does not necessarily contain the Group
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
// SearchFilter := "(objectClass=posixGroup)"
// SearchAttributes := []string{"cn", "gidNumber"}
// groupMap := make(map[string]ldapGroup)
//
// searchReq := goldap.NewSearchRequest(baseDn,
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
// SearchFilter, SearchAttributes, nil)
// searchResult, err := l.Conn.Search(searchReq)
// if err != nil {
// return nil, err
// }
//
// if len(searchResult.Entries) == 0 {
// return nil, errors.New("no result")
// }
//
// for _, entry := range searchResult.Entries {
// var ldapGroupItem ldapGroup
// for _, attribute := range entry.Attributes {
// switch attribute.Name {
// case "gidNumber":
// ldapGroupItem.GidNumber = attribute.Values[0]
// break
// case "cn":
// ldapGroupItem.Cn = attribute.Values[0]
// break
// }
// }
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
// }
//
// return groupMap, nil
//}
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
SearchFilter := "(objectClass=posixAccount)"
SearchAttributes := []string{
"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
}
SearchFilterMsAD := "(objectClass=user)"
SearchAttributesMsAD := []string{
"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
}
var searchReq *goldap.SearchRequest
if l.IsAD {
searchReq = goldap.NewSearchRequest(baseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilterMsAD, SearchAttributesMsAD, nil)
} else {
searchReq = goldap.NewSearchRequest(baseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
}
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
if err != nil {
return nil, err
}
if len(searchResult.Entries) == 0 {
return nil, errors.New("no result")
}
var ldapUsers []ldapUser
for _, entry := range searchResult.Entries {
var ldapUserItem ldapUser
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "uidNumber":
ldapUserItem.UidNumber = attribute.Values[0]
case "uid":
ldapUserItem.Uid = attribute.Values[0]
case "sAMAccountName":
ldapUserItem.Uid = attribute.Values[0]
case "cn":
ldapUserItem.Cn = attribute.Values[0]
case "gidNumber":
ldapUserItem.GidNumber = attribute.Values[0]
case "entryUUID":
ldapUserItem.Uuid = attribute.Values[0]
case "objectGUID":
ldapUserItem.Uuid = attribute.Values[0]
case "mail":
ldapUserItem.Mail = attribute.Values[0]
case "email":
ldapUserItem.Email = attribute.Values[0]
case "emailAddress":
ldapUserItem.EmailAddress = attribute.Values[0]
case "telephoneNumber":
ldapUserItem.TelephoneNumber = attribute.Values[0]
case "mobile":
ldapUserItem.Mobile = attribute.Values[0]
case "mobileTelephoneNumber":
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
case "registeredAddress":
ldapUserItem.RegisteredAddress = attribute.Values[0]
case "postalAddress":
ldapUserItem.PostalAddress = attribute.Values[0]
}
}
ldapUsers = append(ldapUsers, ldapUserItem)
}
return ldapUsers, nil
}
func AddLdap(ldap *Ldap) bool { func AddLdap(ldap *Ldap) bool {
if len(ldap.Id) == 0 { if len(ldap.Id) == 0 {
ldap.Id = util.GenerateId() ldap.Id = util.GenerateId()
@@ -307,12 +57,12 @@ func AddLdap(ldap *Ldap) bool {
func CheckLdapExist(ldap *Ldap) bool { func CheckLdapExist(ldap *Ldap) bool {
var result []*Ldap var result []*Ldap
err := adapter.Engine.Find(&result, &Ldap{ err := adapter.Engine.Find(&result, &Ldap{
Owner: ldap.Owner, Owner: ldap.Owner,
Host: ldap.Host, Host: ldap.Host,
Port: ldap.Port, Port: ldap.Port,
Admin: ldap.Admin, Username: ldap.Username,
Passwd: ldap.Passwd, Password: ldap.Password,
BaseDn: ldap.BaseDn, BaseDn: ldap.BaseDn,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@@ -359,7 +109,7 @@ func UpdateLdap(ldap *Ldap) bool {
} }
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host", affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
"port", "enable_ssl", "admin", "passwd", "base_dn", "auto_sync").Update(ldap) "port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -375,123 +125,3 @@ func DeleteLdap(ldap *Ldap) bool {
return affected != 0 return affected != 0
} }
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
var existUsers []LdapRespUser
var failedUsers []LdapRespUser
var uuids []string
for _, user := range users {
uuids = append(uuids, user.Uuid)
}
existUuids := CheckLdapUuidExist(owner, uuids)
organization := getOrganization("admin", owner)
ldap := GetLdap(ldapId)
var dc []string
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
if strings.Contains(basedn, "dc=") {
dc = append(dc, basedn[3:])
}
}
affiliation := strings.Join(dc, ".")
var ou []string
for _, admin := range strings.Split(ldap.Admin, ",") {
if strings.Contains(admin, "ou=") {
ou = append(ou, admin[3:])
}
}
tag := strings.Join(ou, ".")
for _, user := range users {
found := false
if len(existUuids) > 0 {
for _, existUuid := range existUuids {
if user.Uuid == existUuid {
existUsers = append(existUsers, user)
found = true
}
}
}
if !found && !AddUser(&User{
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(),
DisplayName: user.Cn,
Avatar: organization.DefaultAvatar,
Email: user.Email,
Phone: user.Phone,
Address: []string{user.Address},
Affiliation: affiliation,
Tag: tag,
Score: beego.AppConfig.DefaultInt("initScore", 2000),
Ldap: user.Uuid,
}) {
failedUsers = append(failedUsers, user)
continue
}
}
return &existUsers, &failedUsers
}
func UpdateLdapSyncTime(ldapId string) {
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
if err != nil {
panic(err)
}
}
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
var existUuids []string
existUuidSet := make(map[string]struct{})
//whereStr := ""
//for i, uuid := range uuids {
// if i == 0 {
// whereStr = fmt.Sprintf("'%s'", uuid)
// } else {
// whereStr = fmt.Sprintf(",'%s'", uuid)
// }
//}
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
if err != nil {
panic(err)
}
if len(results) > 0 {
for _, result := range results {
existUuidSet[result.Ldap] = struct{}{}
}
}
for uuid := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids
}
func buildLdapUserName(uid, uidNum string) string {
var result User
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
if err != nil {
panic(err)
}
if has {
if result.Name == uid {
return uidWithNumber
}
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
}
return uid
}

View File

@@ -82,7 +82,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
continue continue
} }
users, err := conn.GetLdapUsers(ldap.BaseDn) users, err := conn.GetLdapUsers(ldap)
if err != nil { if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err)) logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue continue
@@ -112,3 +112,10 @@ func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
} }
} }
} }
func UpdateLdapSyncTime(ldapId string) {
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
if err != nil {
panic(err)
}
}

403
object/ldap_conn.go Normal file
View File

@@ -0,0 +1,403 @@
// Copyright 2023 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.
package object
import (
"errors"
"fmt"
"strings"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
)
type LdapConn struct {
Conn *goldap.Conn
IsAD bool
}
//type ldapGroup struct {
// GidNumber string
// Cn string
//}
type ldapUser struct {
UidNumber string
Uid string
Cn string
GidNumber string
// Gcn string
Uuid string
DisplayName string
Mail string
Email string
EmailAddress string
TelephoneNumber string
Mobile string
MobileTelephoneNumber string
RegisteredAddress string
PostalAddress string
}
type LdapRespUser struct {
UidNumber string `json:"uidNumber"`
Uid string `json:"uid"`
Cn string `json:"cn"`
GroupId string `json:"groupId"`
// GroupName string `json:"groupName"`
Uuid string `json:"uuid"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
}
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
var conn *goldap.Conn
if ldap.EnableSsl {
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
} else {
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
}
if err != nil {
return nil, err
}
err = conn.Bind(ldap.Username, ldap.Password)
if err != nil {
return nil, err
}
isAD, err := isMicrosoftAD(conn)
if err != nil {
return nil, err
}
return &LdapConn{Conn: conn, IsAD: isAD}, nil
}
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectClass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
searchReq := goldap.NewSearchRequest("",
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
searchResult, err := Conn.Search(searchReq)
if err != nil {
return false, err
}
if len(searchResult.Entries) == 0 {
return false, nil
}
isMicrosoft := false
type ldapServerType struct {
Vendorname string
Vendorversion string
IsGlobalCatalogReady string
ForestFunctionality string
}
var ldapServerTypes ldapServerType
for _, entry := range searchResult.Entries {
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "vendorname":
ldapServerTypes.Vendorname = attribute.Values[0]
case "vendorversion":
ldapServerTypes.Vendorversion = attribute.Values[0]
case "isGlobalCatalogReady":
ldapServerTypes.IsGlobalCatalogReady = attribute.Values[0]
case "forestFunctionality":
ldapServerTypes.ForestFunctionality = attribute.Values[0]
}
}
}
if ldapServerTypes.Vendorname == "" &&
ldapServerTypes.Vendorversion == "" &&
ldapServerTypes.IsGlobalCatalogReady == "TRUE" &&
ldapServerTypes.ForestFunctionality != "" {
isMicrosoft = true
}
return isMicrosoft, err
}
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
SearchAttributes := []string{
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
}
if l.IsAD {
SearchAttributes = append(SearchAttributes, "sAMAccountName")
} else {
SearchAttributes = append(SearchAttributes, "uid")
}
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
0, 0, false,
ldapServer.Filter, SearchAttributes, nil)
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
if err != nil {
return nil, err
}
if len(searchResult.Entries) == 0 {
return nil, errors.New("no result")
}
var ldapUsers []ldapUser
for _, entry := range searchResult.Entries {
var user ldapUser
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "uidNumber":
user.UidNumber = attribute.Values[0]
case "uid":
user.Uid = attribute.Values[0]
case "sAMAccountName":
user.Uid = attribute.Values[0]
case "cn":
user.Cn = attribute.Values[0]
case "gidNumber":
user.GidNumber = attribute.Values[0]
case "entryUUID":
user.Uuid = attribute.Values[0]
case "objectGUID":
user.Uuid = attribute.Values[0]
case "displayName":
user.DisplayName = attribute.Values[0]
case "mail":
user.Mail = attribute.Values[0]
case "email":
user.Email = attribute.Values[0]
case "emailAddress":
user.EmailAddress = attribute.Values[0]
case "telephoneNumber":
user.TelephoneNumber = attribute.Values[0]
case "mobile":
user.Mobile = attribute.Values[0]
case "mobileTelephoneNumber":
user.MobileTelephoneNumber = attribute.Values[0]
case "registeredAddress":
user.RegisteredAddress = attribute.Values[0]
case "postalAddress":
user.PostalAddress = attribute.Values[0]
}
}
ldapUsers = append(ldapUsers, user)
}
return ldapUsers, nil
}
// FIXME: The Base DN does not necessarily contain the Group
//
// func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
// SearchFilter := "(objectClass=posixGroup)"
// SearchAttributes := []string{"cn", "gidNumber"}
// groupMap := make(map[string]ldapGroup)
//
// searchReq := goldap.NewSearchRequest(baseDn,
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
// SearchFilter, SearchAttributes, nil)
// searchResult, err := l.Conn.Search(searchReq)
// if err != nil {
// return nil, err
// }
//
// if len(searchResult.Entries) == 0 {
// return nil, errors.New("no result")
// }
//
// for _, entry := range searchResult.Entries {
// var ldapGroupItem ldapGroup
// for _, attribute := range entry.Attributes {
// switch attribute.Name {
// case "gidNumber":
// ldapGroupItem.GidNumber = attribute.Values[0]
// break
// case "cn":
// ldapGroupItem.Cn = attribute.Values[0]
// break
// }
// }
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
// }
//
// return groupMap, nil
// }
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
res := make([]LdapRespUser, 0)
for _, user := range users {
res = append(res, LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.Uuid,
DisplayName: user.DisplayName,
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Phone: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
Address: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
})
}
return res
}
func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
var existUsers []LdapRespUser
var failedUsers []LdapRespUser
var uuids []string
for _, user := range respUsers {
uuids = append(uuids, user.Uuid)
}
existUuids := CheckLdapUuidExist(owner, uuids)
organization := getOrganization("admin", owner)
ldap := GetLdap(ldapId)
var dc []string
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
if strings.Contains(basedn, "dc=") {
dc = append(dc, basedn[3:])
}
}
affiliation := strings.Join(dc, ".")
var ou []string
for _, admin := range strings.Split(ldap.Username, ",") {
if strings.Contains(admin, "ou=") {
ou = append(ou, admin[3:])
}
}
tag := strings.Join(ou, ".")
for _, respUser := range respUsers {
found := false
if len(existUuids) > 0 {
for _, existUuid := range existUuids {
if respUser.Uuid == existUuid {
existUsers = append(existUsers, respUser)
found = true
}
}
}
if !found {
newUser := &User{
Owner: owner,
Name: respUser.buildLdapUserName(),
CreatedTime: util.GetCurrentTime(),
DisplayName: respUser.buildLdapDisplayName(),
Avatar: organization.DefaultAvatar,
Email: respUser.Email,
Phone: respUser.Phone,
Address: []string{respUser.Address},
Affiliation: affiliation,
Tag: tag,
Score: beego.AppConfig.DefaultInt("initScore", 2000),
Ldap: respUser.Uuid,
}
affected := AddUser(newUser)
if !affected {
failedUsers = append(failedUsers, respUser)
continue
}
}
}
return &existUsers, &failedUsers
}
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
var existUuids []string
existUuidSet := make(map[string]struct{})
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
if err != nil {
panic(err)
}
if len(results) > 0 {
for _, result := range results {
existUuidSet[result.Ldap] = struct{}{}
}
}
for uuid := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids
}
func (ldapUser *LdapRespUser) buildLdapUserName() string {
user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
if err != nil {
panic(err)
}
if has {
if user.Name == ldapUser.Uid {
return uidWithNumber
}
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
}
return ldapUser.Uid
}
func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
if ldapUser.DisplayName != "" {
return ldapUser.DisplayName
}
return ldapUser.Cn
}
func (ldap *Ldap) buildFilterString(user *User) string {
if len(ldap.FilterFields) == 0 {
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)
}
filter := fmt.Sprintf("(&%s(|", ldap.Filter)
for _, field := range ldap.FilterFields {
filter = fmt.Sprintf("%s(%s=%s)", filter, field, user.getFieldFromLdapAttribute(field))
}
filter = fmt.Sprintf("%s))", filter)
return filter
}
func (user *User) getFieldFromLdapAttribute(attribute string) string {
switch attribute {
case "uid":
return user.Name
case "mail":
return user.Email
case "mobile":
return user.Phone
default:
return ""
}
}

View File

@@ -27,7 +27,7 @@ type Message struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"` Organization string `xorm:"varchar(100)" json:"organization"`
Chat string `xorm:"varchar(100)" json:"chat"` Chat string `xorm:"varchar(100) index" json:"chat"`
Author string `xorm:"varchar(100)" json:"author"` Author string `xorm:"varchar(100)" json:"author"`
Text string `xorm:"mediumtext" json:"text"` Text string `xorm:"mediumtext" json:"text"`
} }
@@ -67,6 +67,16 @@ func GetMessages(owner string) []*Message {
return messages return messages
} }
func GetChatMessages(chat string) []*Message {
messages := []*Message{}
err := adapter.Engine.Desc("created_time").Find(&messages, &Message{Chat: chat})
if err != nil {
panic(err)
}
return messages
}
func GetPaginationMessages(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Message { func GetPaginationMessages(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
messages := []*Message{} messages := []*Message{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)

View File

@@ -18,6 +18,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"net"
"strings" "strings"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@@ -43,6 +44,26 @@ type OidcDiscovery struct {
EndSessionEndpoint string `json:"end_session_endpoint"` EndSessionEndpoint string `json:"end_session_endpoint"`
} }
func isIpAddress(host string) bool {
// Attempt to split the host and port, ignoring the error
hostWithoutPort, _, err := net.SplitHostPort(host)
if err != nil {
// If an error occurs, it might be because there's no port
// In that case, use the original host string
hostWithoutPort = host
}
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
ip := net.ParseIP(hostWithoutPort)
if ip != nil {
// The host is an IP address
return true
}
// The host is not an IP address
return false
}
func getOriginFromHost(host string) (string, string) { func getOriginFromHost(host string) (string, string) {
origin := conf.GetConfigString("origin") origin := conf.GetConfigString("origin")
if origin != "" { if origin != "" {
@@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
protocol := "https://" protocol := "https://"
if strings.HasPrefix(host, "localhost") { if strings.HasPrefix(host, "localhost") {
protocol = "http://" protocol = "http://"
} else if isIpAddress(host) {
protocol = "http://"
} }
if host == "localhost:8000" { if host == "localhost:8000" {

View File

@@ -130,7 +130,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, subUser := range roleObj.Users { for _, subUser := range roleObj.Users {
if domainExist { if domainExist {
for _, domain := range permission.Domains { for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId}) groupingPolicies = append(groupingPolicies, []string{subUser, role, domain, "", "", permissionId})
} }
} else { } else {
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId}) groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
@@ -140,7 +140,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, subRole := range roleObj.Roles { for _, subRole := range roleObj.Roles {
if domainExist { if domainExist {
for _, domain := range permission.Domains { for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId}) groupingPolicies = append(groupingPolicies, []string{subRole, role, domain, "", "", permissionId})
} }
} else { } else {
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId}) groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})

View File

@@ -30,7 +30,10 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123") product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay") provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay") cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
if err != nil {
panic(err)
}
paymentName := util.GenerateTimeId() paymentName := util.GenerateTimeId()
returnUrl := "" returnUrl := ""

View File

@@ -23,29 +23,32 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
saml2 "github.com/russellhaering/gosaml2" saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
) )
func ParseSamlResponse(samlResponse string, providerType string) (string, error) { func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
samlResponse, _ = url.QueryUnescape(samlResponse) samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(&Provider{Type: providerType}, samlResponse) sp, err := buildSp(provider, samlResponse, host)
if err != nil { if err != nil {
return "", err return "", err
} }
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil {
return "", err
}
return assertionInfo.NameID, err return assertionInfo.NameID, err
} }
func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method string, err error) { func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
provider := GetProvider(id) provider := GetProvider(id)
if provider.Category != "SAML" { if provider.Category != "SAML" {
return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name) return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name)
} }
sp, err := buildSp(provider, "")
sp, err := buildSp(provider, "", host)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@@ -67,35 +70,22 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method stri
return auth, method, nil return auth, method, nil
} }
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) { func buildSp(provider *Provider, samlResponse string, host string) (*saml2.SAMLServiceProvider, error) {
origin := conf.GetConfigString("origin") _, origin := getOriginFromHost(host)
certStore := dsig.MemoryX509CertificateStore{ certStore, err := buildSpCertificateStore(provider, samlResponse)
Roots: []*x509.Certificate{},
}
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
} else if provider.IdP != "" {
certEncodedData = provider.IdP
}
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
return nil, err
}
certStore.Roots = append(certStore.Roots, idpCert)
sp := &saml2.SAMLServiceProvider{ sp := &saml2.SAMLServiceProvider{
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin), ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin), AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
IDPCertificateStore: &certStore,
SignAuthnRequests: false, SignAuthnRequests: false,
IDPCertificateStore: &certStore,
SPKeyStore: dsig.RandomKeyStoreForTest(), SPKeyStore: dsig.RandomKeyStoreForTest(),
} }
if provider.Endpoint != "" { if provider.Endpoint != "" {
sp.IdentityProviderSSOURL = provider.Endpoint sp.IdentityProviderSSOURL = provider.Endpoint
sp.IdentityProviderIssuer = provider.IssuerUrl sp.IdentityProviderIssuer = provider.IssuerUrl
@@ -104,10 +94,45 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
sp.SignAuthnRequests = true sp.SignAuthnRequests = true
sp.SPKeyStore = buildSpKeyStore() sp.SPKeyStore = buildSpKeyStore()
} }
return sp, nil return sp, nil
} }
func parseSamlResponse(samlResponse string, providerType string) string { func buildSpKeyStore() dsig.X509KeyStore {
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
if err != nil {
panic(err)
}
return &dsig.TLSCertKeyStore{
PrivateKey: keyPair.PrivateKey,
Certificate: keyPair.Certificate,
}
}
func buildSpCertificateStore(provider *Provider, samlResponse string) (dsig.MemoryX509CertificateStore, error) {
certEncodedData := ""
if samlResponse != "" {
certEncodedData = getCertificateFromSamlResponse(samlResponse, provider.Type)
} else if provider.IdP != "" {
certEncodedData = provider.IdP
}
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
if err != nil {
return dsig.MemoryX509CertificateStore{}, err
}
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
return dsig.MemoryX509CertificateStore{}, err
}
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{idpCert},
}
return certStore, nil
}
func getCertificateFromSamlResponse(samlResponse string, providerType string) string {
de, err := base64.StdEncoding.DecodeString(samlResponse) de, err := base64.StdEncoding.DecodeString(samlResponse)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -122,14 +147,3 @@ func parseSamlResponse(samlResponse string, providerType string) string {
res := regexp.MustCompile(expression).FindStringSubmatch(deStr) res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1] return res[1]
} }
func buildSpKeyStore() dsig.X509KeyStore {
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
if err != nil {
panic(err)
}
return &dsig.TLSCertKeyStore{
PrivateKey: keyPair.PrivateKey,
Certificate: keyPair.Certificate,
}
}

View File

@@ -15,10 +15,12 @@
package object package object
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"github.com/xorm-io/core" "github.com/xorm-io/core"
@@ -48,6 +50,7 @@ type User struct {
EmailVerified bool `json:"emailVerified"` EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(20) index" json:"phone"` Phone string `xorm:"varchar(20) index" json:"phone"`
CountryCode string `xorm:"varchar(6)" json:"countryCode"` CountryCode string `xorm:"varchar(6)" json:"countryCode"`
Region string `xorm:"varchar(100)" json:"region"`
Location string `xorm:"varchar(100)" json:"location"` Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"` Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"` Affiliation string `xorm:"varchar(100)" json:"affiliation"`
@@ -57,7 +60,6 @@ type User struct {
Homepage string `xorm:"varchar(100)" json:"homepage"` Homepage string `xorm:"varchar(100)" json:"homepage"`
Bio string `xorm:"varchar(100)" json:"bio"` Bio string `xorm:"varchar(100)" json:"bio"`
Tag string `xorm:"varchar(100)" json:"tag"` Tag string `xorm:"varchar(100)" json:"tag"`
Region string `xorm:"varchar(100)" json:"region"`
Language string `xorm:"varchar(100)" json:"language"` Language string `xorm:"varchar(100)" json:"language"`
Gender string `xorm:"varchar(100)" json:"gender"` Gender string `xorm:"varchar(100)" json:"gender"`
Birthday string `xorm:"varchar(100)" json:"birthday"` Birthday string `xorm:"varchar(100)" json:"birthday"`
@@ -449,7 +451,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
if len(columns) == 0 { if len(columns) == 0 {
columns = []string{ columns = []string{
"owner", "display_name", "avatar", "owner", "display_name", "avatar",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application", "location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time", "signin_wrong_times", "last_signin_wrong_time",
} }
@@ -513,7 +515,10 @@ func AddUser(user *User) bool {
user.UpdateUserHash() user.UpdateUserHash()
user.PreHash = user.Hash user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false) updated := user.refreshAvatar()
if updated && user.PermanentAvatar != "*" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
}
user.Ranking = GetUserCount(user.Owner, "", "") + 1 user.Ranking = GetUserCount(user.Owner, "", "") + 1
@@ -693,3 +698,40 @@ func userChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func (user *User) refreshAvatar() bool {
var err error
var fileBuffer *bytes.Buffer
var ext string
// Gravatar + Identicon
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
client := proxy.ProxyHttpClient
has, err := hasGravatar(client, user.Email)
if err != nil {
panic(err)
}
if has {
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
if err != nil {
panic(err)
}
}
}
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
if err != nil {
panic(err)
}
}
if fileBuffer != nil {
avatarUrl := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
user.Avatar = avatarUrl
return true
}
return false
}

View File

@@ -131,6 +131,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
if user.DisplayName == "" { if user.DisplayName == "" {
user.DisplayName = userInfo.DisplayName user.DisplayName = userInfo.DisplayName
} }
} else if user.DisplayName == "" {
if userInfo.Username != "" {
user.DisplayName = userInfo.Username
} else {
user.DisplayName = userInfo.Id
}
} }
if userInfo.Email != "" { if userInfo.Email != "" {
propertyName := fmt.Sprintf("oauth_%s_email", providerType) propertyName := fmt.Sprintf("oauth_%s_email", providerType)

View File

@@ -30,3 +30,12 @@ func ContainsString(values []string, val string) bool {
sort.Strings(values) sort.Strings(values)
return sort.SearchStrings(values, val) != len(values) return sort.SearchStrings(values, val) != len(values)
} }
func ReturnAnyNotEmpty(strs ...string) string {
for _, str := range strs {
if str != "" {
return str
}
}
return ""
}

View File

@@ -112,6 +112,7 @@ class AdapterEditPage extends React.Component {
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
this.getModels(value); this.getModels(value);
this.updateAdapterField("organization", value); this.updateAdapterField("organization", value);
this.updateAdapterField("owner", value);
})}> })}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
@@ -266,7 +267,7 @@ class AdapterEditPage extends React.Component {
submitAdapterEdit(willExist) { submitAdapterEdit(willExist) {
const adapter = Setting.deepCopy(this.state.adapter); const adapter = Setting.deepCopy(this.state.adapter);
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter) AdapterBackend.updateAdapter(this.state.owner, this.state.adapterName, adapter)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved")); Setting.showMessage("success", i18next.t("general:Successfully saved"));

View File

@@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs"; import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd"; import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom"; import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage"; import OrganizationListPage from "./OrganizationListPage";
@@ -44,8 +44,9 @@ import SyncerListPage from "./SyncerListPage";
import SyncerEditPage from "./SyncerEditPage"; import SyncerEditPage from "./SyncerEditPage";
import CertListPage from "./CertListPage"; import CertListPage from "./CertListPage";
import CertEditPage from "./CertEditPage"; import CertEditPage from "./CertEditPage";
import ChatEditPage from "./ChatEditPage";
import ChatListPage from "./ChatListPage"; import ChatListPage from "./ChatListPage";
import ChatEditPage from "./ChatEditPage";
import ChatPage from "./ChatPage";
import MessageEditPage from "./MessageEditPage"; import MessageEditPage from "./MessageEditPage";
import MessageListPage from "./MessageListPage"; import MessageListPage from "./MessageListPage";
import ProductListPage from "./ProductListPage"; import ProductListPage from "./ProductListPage";
@@ -325,12 +326,17 @@ class App extends Component {
items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>, items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>,
"/account" "/account"
)); ));
items.push(Setting.getItem(<><CommentOutlined />&nbsp;&nbsp;{i18next.t("account:Chats & Messages")}</>,
"/chat"
));
items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>, items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>,
"/logout")); "/logout"));
const onClick = (e) => { const onClick = (e) => {
if (e.key === "/account") { if (e.key === "/account") {
this.props.history.push("/account"); this.props.history.push("/account");
} else if (e.key === "/chat") {
this.props.history.push("/chat");
} else if (e.key === "/logout") { } else if (e.key === "/logout") {
this.logout(); this.logout();
} }
@@ -547,6 +553,7 @@ class App extends Component {
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} /> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} /> <Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} /> <Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} /> <Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} /> <Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} /> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
@@ -622,7 +629,7 @@ class App extends Component {
} }
</Header> </Header>
<Content style={{display: "flex", flexDirection: "column"}} > <Content style={{display: "flex", flexDirection: "column"}} >
{Setting.isMobile() ? {(Setting.isMobile() || window.location.pathname === "/chat") ?
this.renderRouter() : this.renderRouter() :
<Card className="content-warp-card"> <Card className="content-warp-card">
{this.renderRouter()} {this.renderRouter()}

161
web/src/ChatBox.js Normal file
View File

@@ -0,0 +1,161 @@
// Copyright 2023 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 from "react";
import {Avatar, Input, List} from "antd";
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
import * as Setting from "./Setting";
const {TextArea} = Input;
class ChatBox extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
};
}
handleKeyDown = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
if (this.state.inputValue !== "") {
this.send(this.state.inputValue);
this.setState({inputValue: ""});
}
}
};
send = (text) => {
Setting.showMessage("success", text);
this.setState({inputValue: ""});
};
renderList() {
return (
<div style={{position: "relative"}}>
<List
style={{maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}
itemLayout="horizontal"
dataSource={this.props.messages === undefined ? undefined : [...this.props.messages, {}]}
renderItem={(item, index) => {
if (Object.keys(item).length === 0 && item.constructor === Object) {
return <List.Item style={{
height: "160px",
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
borderBottom: "1px solid rgb(229, 229, 229)",
position: "relative",
}} />;
}
return (
<List.Item style={{
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
borderBottom: "1px solid rgb(229, 229, 229)",
position: "relative",
}}>
<div style={{width: "800px", margin: "0 auto", position: "relative"}}>
<List.Item.Meta
avatar={<Avatar style={{width: "30px", height: "30px", borderRadius: "3px"}} src={item.author === `${this.props.account.owner}/${this.props.account.name}` ? this.props.account.avatar : "https://cdn.casbin.com/casdoor/resource/built-in/admin/gpt.png"} />}
title={<div style={{fontSize: "16px", fontWeight: "normal", lineHeight: "24px", marginTop: "-15px", marginLeft: "5px", marginRight: "80px"}}>{item.text}</div>}
/>
<div style={{position: "absolute", top: "0px", right: "0px"}}
>
<CopyOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
<LikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
<DislikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
</div>
</div>
</List.Item>
);
}}
/>
<div style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: "120px",
background: "linear-gradient(transparent 0%, rgba(255, 255, 255, 0.8) 50%, white 100%)",
pointerEvents: "none",
}} />
</div>
);
}
renderInput() {
return (
<div
style={{
position: "fixed",
bottom: "90px",
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
<div style={{position: "relative", width: "760px", marginLeft: "-280px"}}>
<TextArea
placeholder={"Send a message..."}
autoSize={{maxRows: 8}}
value={this.state.inputValue}
onChange={(e) => this.setState({inputValue: e.target.value})}
onKeyDown={this.handleKeyDown}
style={{
fontSize: "16px",
fontWeight: "normal",
lineHeight: "24px",
width: "770px",
height: "48px",
borderRadius: "6px",
borderColor: "rgb(229,229,229)",
boxShadow: "0 0 15px rgba(0, 0, 0, 0.1)",
paddingLeft: "17px",
paddingRight: "17px",
paddingTop: "12px",
paddingBottom: "12px",
}}
suffix={<SendOutlined style={{color: "rgb(210,210,217"}} onClick={() => this.send(this.state.inputValue)} />}
autoComplete="off"
/>
<SendOutlined
style={{
color: this.state.inputValue === "" ? "rgb(210,210,217)" : "rgb(142,142,160)",
position: "absolute",
bottom: "17px",
right: "17px",
}}
onClick={() => this.send(this.state.inputValue)}
/>
</div>
</div>
);
}
render() {
return (
<div>
{
this.renderList()
}
{
this.renderInput()
}
</div>
);
}
}
export default ChatBox;

View File

@@ -126,7 +126,33 @@ class ChatEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("general:User1 - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.type} onChange={(value => {
this.updateChatField("type", value);
})}
options={[
{value: "Single", name: i18next.t("chat:Single")},
{value: "Group", name: i18next.t("chat:Group")},
{value: "AI", name: i18next.t("chat:AI")},
].map((item) => Setting.getOption(item.name, item.value))}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.chat.category} onChange={e => {
this.updateChatField("category", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})} <Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})}
@@ -136,7 +162,7 @@ class ChatEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("chat:User2"), i18next.t("general:User2 - Tooltip"))} : {Setting.getLabel(i18next.t("chat:User2"), i18next.t("chat:User2 - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})} <Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})}
@@ -146,7 +172,7 @@ class ChatEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("chat:Sub users"), i18next.t("chat:Sub users - Tooltip"))} : {Setting.getLabel(i18next.t("general:Users"), i18next.t("chat:Users - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select mode="tags" style={{width: "100%"}} value={this.state.chat.users} <Select mode="tags" style={{width: "100%"}} value={this.state.chat.users}

View File

@@ -32,6 +32,8 @@ class ChatListPage extends BaseListPage {
updatedTime: moment().format(), updatedTime: moment().format(),
organization: this.props.account.owner, organization: this.props.account.owner,
displayName: `New Chat - ${randomName}`, displayName: `New Chat - ${randomName}`,
type: "Single",
category: "Chat Category - 1",
user1: `${this.props.account.owner}/${this.props.account.name}`, user1: `${this.props.account.owner}/${this.props.account.name}`,
user2: "", user2: "",
users: [`${this.props.account.owner}/${this.props.account.name}`], users: [`${this.props.account.owner}/${this.props.account.name}`],
@@ -134,6 +136,30 @@ class ChatListPage extends BaseListPage {
sorter: true, sorter: true,
...this.getColumnSearchProps("displayName"), ...this.getColumnSearchProps("displayName"),
}, },
{
title: i18next.t("provider:Type"),
dataIndex: "type",
key: "type",
width: "110px",
sorter: true,
filterMultiple: false,
filters: [
{text: "Single", value: "Single"},
{text: "Group", value: "Group"},
{text: "AI", value: "AI"},
],
render: (text, record, index) => {
return i18next.t(`chat:${text}`);
},
},
{
title: i18next.t("provider:Category"),
dataIndex: "category",
key: "category",
// width: '100px',
sorter: true,
...this.getColumnSearchProps("category"),
},
{ {
title: i18next.t("chat:User1"), title: i18next.t("chat:User1"),
dataIndex: "user1", dataIndex: "user1",

100
web/src/ChatMenu.js Normal file
View File

@@ -0,0 +1,100 @@
// Copyright 2023 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 from "react";
import {Menu} from "antd";
import {LayoutOutlined} from "@ant-design/icons";
class ChatMenu extends React.Component {
constructor(props) {
super(props);
const items = this.chatsToItems(this.props.chats);
const openKeys = items.map((item) => item.key);
this.state = {
openKeys: openKeys,
selectedKeys: ["0-0"],
};
}
chatsToItems(chats) {
const categories = {};
chats.forEach((chat) => {
if (!categories[chat.category]) {
categories[chat.category] = [];
}
categories[chat.category].push(chat);
});
return Object.keys(categories).map((category, index) => {
return {
key: `${index}`,
icon: <LayoutOutlined />,
label: category,
children: categories[category].map((chat, chatIndex) => {
const globalChatIndex = chats.indexOf(chat);
return {
key: `${index}-${chatIndex}`,
dataIndex: globalChatIndex,
label: chat.displayName,
};
}),
};
});
}
onSelect = (info) => {
const [categoryIndex, chatIndex] = info.selectedKeys[0].split("-").map(Number);
const selectedItem = this.chatsToItems(this.props.chats)[categoryIndex].children[chatIndex];
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
if (this.props.onSelect) {
this.props.onSelect(selectedItem.dataIndex);
}
};
getRootSubmenuKeys(items) {
return items.map((item, index) => `${index}`);
}
onOpenChange = (keys) => {
const items = this.chatsToItems(this.props.chats);
const rootSubmenuKeys = this.getRootSubmenuKeys(items);
const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
this.setState({openKeys: keys});
} else {
this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
}
};
render() {
const items = this.chatsToItems(this.props.chats);
return (
<Menu
mode="inline"
openKeys={this.state.openKeys}
selectedKeys={this.state.selectedKeys}
onOpenChange={this.onOpenChange}
onSelect={this.onSelect}
items={items}
/>
);
}
}
export default ChatMenu;

157
web/src/ChatPage.js Normal file
View File

@@ -0,0 +1,157 @@
// Copyright 2023 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 from "react";
import {Spin} from "antd";
import moment from "moment";
import ChatMenu from "./ChatMenu";
import ChatBox from "./ChatBox";
import * as Setting from "./Setting";
import * as ChatBackend from "./backend/ChatBackend";
import * as MessageBackend from "./backend/MessageBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
class ChatPage extends BaseListPage {
newChat() {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.applicationName,
name: `chat_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
organization: this.props.account.owner,
displayName: `New Chat - ${randomName}`,
type: "Single",
category: "Chat Category - 1",
user1: `${this.props.account.owner}/${this.props.account.name}`,
user2: "",
users: [`${this.props.account.owner}/${this.props.account.name}`],
messageCount: 0,
};
}
addChat() {
const newChat = this.newChat();
ChatBackend.addChat(newChat)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteChat(i) {
ChatBackend.deleteChat(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
renderTable(chats) {
return (this.state.loading) ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
(
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)"}}>
<ChatMenu chats={chats} onSelect={(i) => {
const chat = chats[i];
this.getMessages(chat.name);
}} />
</div>
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
<div style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: "url(https://cdn.casbin.org/img/casdoor-logo_1185x256.png)",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "200px auto",
backgroundBlendMode: "luminosity",
filter: "grayscale(80%) brightness(140%) contrast(90%)",
opacity: 0.5,
}}>
</div>
<ChatBox messages={this.state.messages} account={this.props.account} />
</div>
</div>
)
);
}
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.category !== undefined && params.category !== null) {
field = "category";
value = params.category;
} else if (params.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};
getMessages(chatName) {
MessageBackend.getChatMessages(chatName)
.then((messages) => {
this.setState({
messages: messages,
});
});
}
}
export default ChatPage;

View File

@@ -69,7 +69,7 @@ class EntryPage extends React.Component {
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}> <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} /> <Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Switch> <Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} /> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} /> <Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />

View File

@@ -166,13 +166,37 @@ class LdapEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}>
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("ldap:Search Filter"), i18next.t("ldap:Search Filter - Tooltip"))} :
</Col>
<Col span={21}>
<Input value={this.state.ldap.filter} onChange={e => {
this.updateLdapField("filter", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("ldap:Filter fields"), i18next.t("ldap:Filter fields - Tooltip"))} :
</Col>
<Col span={21}>
<Select value={this.state.ldap.filterFields ?? []} style={{width: "100%"}} mode={"multiple"} options={[
{value: "uid", label: "uid"},
{value: "mail", label: "Email"},
{value: "mobile", label: "mobile"},
].map((item) => Setting.getOption(item.label, item.value))} onChange={value => {
this.updateLdapField("filterFields", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}> <Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
{Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} : {Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} :
</Col> </Col>
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.admin} onChange={e => { <Input value={this.state.ldap.username} onChange={e => {
this.updateLdapField("admin", e.target.value); this.updateLdapField("username", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
@@ -182,9 +206,9 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<Input.Password <Input.Password
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.passwd} iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.password}
onChange={e => { onChange={e => {
this.updateLdapField("passwd", e.target.value); this.updateLdapField("password", e.target.value);
}} }}
/> />
</Col> </Col>

View File

@@ -1,192 +0,0 @@
// Copyright 2021 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 from "react";
import {Link} from "react-router-dom";
import {Button, Col, Row, Table} from "antd";
import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next";
import PopconfirmModal from "./PopconfirmModal";
class LdapListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
ldaps: null,
};
}
UNSAFE_componentWillMount() {
this.getLdaps();
}
getLdaps() {
LdapBackend.getLdaps("")
.then((res) => {
let ldapsData = [];
if (res.status === "ok") {
ldapsData = res.data;
} else {
Setting.showMessage("error", res.msg);
}
this.setState((prevState) => {
prevState.ldaps = ldapsData;
return prevState;
});
});
}
deleteLdap(index) {
}
renderTable(ldaps) {
const columns = [
{
title: i18next.t("ldap:Server name"),
dataIndex: "serverName",
key: "serverName",
width: "200px",
sorter: (a, b) => a.serverName.localeCompare(b.serverName),
render: (text, record, index) => {
return (
<Link to={`/ldaps/${record.id}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "140px",
sorter: (a, b) => a.owner.localeCompare(b.owner),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("ldap:Server"),
dataIndex: "host",
key: "host",
ellipsis: true,
sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => {
return `${text}:${record.port}`;
},
},
{
title: i18next.t("ldap:Base DN"),
dataIndex: "baseDn",
key: "baseDn",
ellipsis: true,
sorter: (a, b) => a.baseDn.localeCompare(b.baseDn),
},
{
title: i18next.t("ldap:Admin"),
dataIndex: "admin",
key: "admin",
ellipsis: true,
sorter: (a, b) => a.admin.localeCompare(b.admin),
},
{
title: i18next.t("ldap:Auto Sync"),
dataIndex: "autoSync",
key: "autoSync",
width: "100px",
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>);
},
},
{
title: i18next.t("ldap:Last Sync"),
dataIndex: "lastSync",
key: "lastSync",
ellipsis: true,
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => {
return text;
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "240px",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
type="primary"
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("general:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
onConfirm={() => this.deleteLdap(index)}
>
</PopconfirmModal>
</div>
);
},
},
];
return (
<div>
<Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered
pagination={{pageSize: 100}}
title={() => (
<div>
<span>{i18next.t("general:LDAPs")}</span>
<Button type="primary" size="small" style={{marginLeft: "10px"}}
onClick={() => {
this.addLdap();
}}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={ldaps === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.ldaps)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default LdapListPage;

View File

@@ -140,7 +140,7 @@ class MessageEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("message:Author"), i18next.t("general:Author - Tooltip"))} : {Setting.getLabel(i18next.t("message:Author"), i18next.t("message:Author - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.message.author} onChange={(value => {this.updateMessageField("author", value);})} <Select virtual={false} style={{width: "100%"}} value={this.state.message.author} onChange={(value => {this.updateMessageField("author", value);})}

View File

@@ -42,7 +42,7 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
{label: "日本語", key: "ja", country: "JP", alt: "日本語"}, {label: "日本語", key: "ja", country: "JP", alt: "日本語"},
{label: "한국어", key: "ko", country: "KR", alt: "한국어"}, {label: "한국어", key: "ko", country: "KR", alt: "한국어"},
{label: "Русский", key: "ru", country: "RU", alt: "Русский"}, {label: "Русский", key: "ru", country: "RU", alt: "Русский"},
{label: "TiếngViệt", key: "vn", country: "VN", alt: "TiếngViệt"}, {label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
]; ];
export function getThemeData(organization, application) { export function getThemeData(organization, application) {

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd"; import {Button, Card, Col, Input, InputNumber, Result, Row, Select, Spin, Switch} from "antd";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -110,9 +110,9 @@ class UserEditPage extends React.Component {
} }
parseUserField(key, value) { parseUserField(key, value) {
// if ([].includes(key)) { if (["score", "karma", "ranking"].includes(key)) {
// value = Setting.myParseInt(value); value = Setting.myParseInt(value);
// } }
return value; return value;
} }
@@ -360,6 +360,19 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "Address") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.address} onChange={e => {
this.updateUserField("address", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Affiliation") { } else if (accountItem.name === "Affiliation") {
return ( return (
(this.state.application === null || this.state.user === null) ? null : ( (this.state.application === null || this.state.user === null) ? null : (
@@ -379,6 +392,32 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "ID card type") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:ID card type"), i18next.t("user:ID card type - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.idCardType} onChange={e => {
this.updateUserField("idCardType", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "ID card") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:ID card"), i18next.t("user:ID card - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.idCard} onChange={e => {
this.updateUserField("idCard", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Homepage") { } else if (accountItem.name === "Homepage") {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
@@ -431,6 +470,97 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
); );
} else if (accountItem.name === "Language") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Language"), i18next.t("user:Language - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.language} onChange={e => {
this.updateUserField("language", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Gender") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Gender"), i18next.t("user:Gender - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.gender} onChange={e => {
this.updateUserField("gender", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Birthday") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Birthday"), i18next.t("user:Birthday - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.birthday} onChange={e => {
this.updateUserField("birthday", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Education") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Education"), i18next.t("user:Education - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.education} onChange={e => {
this.updateUserField("education", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Score") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Score"), i18next.t("user:Score - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.user.score} onChange={value => {
this.updateUserField("score", value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Karma") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Karma"), i18next.t("user:Karma - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.user.karma} onChange={value => {
this.updateUserField("karma", value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Ranking") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Ranking"), i18next.t("user:Ranking - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.user.ranking} onChange={value => {
this.updateUserField("ranking", value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Signup application") { } else if (accountItem.name === "Signup application") {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >

View File

@@ -787,11 +787,11 @@ class LoginPage extends React.Component {
} }
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem)); const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) { if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup")); Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return ( return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} />
</div> </div>
); );
} }

View File

@@ -68,7 +68,7 @@ class ResultPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") { if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage); Setting.goToLink(linkInStorage);
} else { } else {
Setting.redirectToLoginPage(application); Setting.redirectToLoginPage(application, this.props.history);
} }
}}> }}>
{i18next.t("login:Sign In")} {i18next.t("login:Sign In")}

View File

@@ -408,24 +408,27 @@ class SignupPage extends React.Component {
</Form.Item> </Form.Item>
</Input.Group> </Input.Group>
</Form.Item> </Form.Item>
<Form.Item {
name="phoneCode" signupItem.rule !== "No verification" &&
label={i18next.t("code:Phone code")} <Form.Item
rules={[ name="phoneCode"
{ label={i18next.t("code:Phone code")}
required: required, rules={[
message: i18next.t("code:Please input your phone verification code!"), {
}, required: required,
]} message: i18next.t("code:Please input your phone verification code!"),
> },
<SendCodeInput ]}
disabled={!this.state.validPhone} >
method={"signup"} <SendCodeInput
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]} disabled={!this.state.validPhone}
application={application} method={"signup"}
countryCode={this.form.current?.getFieldValue("countryCode")} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
/> application={application}
</Form.Item> countryCode={this.form.current?.getFieldValue("countryCode")}
/>
</Form.Item>
}
</React.Fragment> </React.Fragment>
); );
} else if (signupItem.name === "Password") { } else if (signupItem.name === "Password") {

View File

@@ -24,6 +24,16 @@ export function getMessages(owner, page = "", pageSize = "", field = "", value =
}).then(res => res.json()); }).then(res => res.json());
} }
export function getChatMessages(chat) {
return fetch(`${Setting.ServerUrl}/api/get-messages?chat=${chat}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function getMessage(owner, name) { export function getMessage(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-message?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-message?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",

View File

@@ -71,8 +71,12 @@ export function deleteUserWebAuthnCredential(credentialID) {
}).then(res => res.json()); }).then(res => res.json());
} }
// Base64 to ArrayBuffer // Base64URL to ArrayBuffer
export function webAuthnBufferDecode(value) { export function webAuthnBufferDecode(value) {
value = value.replace(/-/g, "+").replace(/_/g, "/");
while (value.length % 4) {
value += "=";
}
return Uint8Array.from(atob(value), c => c.charCodeAt(0)); return Uint8Array.from(atob(value), c => c.charCodeAt(0));
} }

View File

@@ -22,7 +22,7 @@ import id from "./locales/id/data.json";
import ja from "./locales/ja/data.json"; import ja from "./locales/ja/data.json";
import ko from "./locales/ko/data.json"; import ko from "./locales/ko/data.json";
import ru from "./locales/ru/data.json"; import ru from "./locales/ru/data.json";
import vn from "./locales/vn/data.json"; import vi from "./locales/vi/data.json";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import {initReactI18next} from "react-i18next"; import {initReactI18next} from "react-i18next";
@@ -36,7 +36,7 @@ const resources = {
ja: ja, ja: ja,
ko: ko, ko: ko,
ru: ru, ru: ru,
vn: vn, vi: vi,
}; };
function initLanguage() { function initLanguage() {
@@ -80,8 +80,8 @@ function initLanguage() {
case "ru": case "ru":
language = "ru"; language = "ru";
break; break;
case "vn": case "vi":
language = "vn"; language = "vi";
break; break;
default: default:
language = Conf.DefaultLanguage; language = Conf.DefaultLanguage;

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Abmelden", "Logout": "Abmelden",
"My Account": "Mein Konto", "My Account": "Mein Konto",
"Sign Up": "Anmelden" "Sign Up": "Anmelden"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Nutzungsszenarien des Zertifikats", "Scope - Tooltip": "Nutzungsszenarien des Zertifikats",
"Type - Tooltip": "Art des Zertifikats" "Type - Tooltip": "Art des Zertifikats"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Der Code, den Sie erhalten haben", "Code you received": "Der Code, den Sie erhalten haben",
"Email code": "E-Mail-Code", "Email code": "E-Mail-Code",
@@ -159,6 +173,7 @@
"Cert": "Zertifikat", "Cert": "Zertifikat",
"Cert - Tooltip": "Das Public-Key-Zertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss", "Cert - Tooltip": "Das Public-Key-Zertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss",
"Certs": "Zertifikate", "Certs": "Zertifikate",
"Chats": "Chats",
"Click to Upload": "Klicken Sie zum Hochladen", "Click to Upload": "Klicken Sie zum Hochladen",
"Client IP": "Client-IP", "Client IP": "Client-IP",
"Close": "Schließen", "Close": "Schließen",
@@ -203,6 +218,7 @@
"Master password": "Hauptpasswort", "Master password": "Hauptpasswort",
"Master password - Tooltip": "Kann zum Einloggen aller Benutzer unter dieser Organisation verwendet werden, was es Administratoren bequem macht, sich als dieser Benutzer einzuloggen, um technische Probleme zu lösen", "Master password - Tooltip": "Kann zum Einloggen aller Benutzer unter dieser Organisation verwendet werden, was es Administratoren bequem macht, sich als dieser Benutzer einzuloggen, um technische Probleme zu lösen",
"Menu": "Menü", "Menu": "Menü",
"Messages": "Messages",
"Method": "Methode", "Method": "Methode",
"Model": "Modell", "Model": "Modell",
"Model - Tooltip": "Casbin-Zugriffskontrollmodell", "Model - Tooltip": "Casbin-Zugriffskontrollmodell",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "URL-Link", "URL - Tooltip": "URL-Link",
"Up": "Oben", "Up": "Oben",
"Updated time": "Updated time",
"User": "Nutzer", "User": "Nutzer",
"User - Tooltip": "Stellen Sie sicher, dass der Benutzername korrekt ist", "User - Tooltip": "Stellen Sie sicher, dass der Benutzername korrekt ist",
"User containers": "Nutzerpools", "User containers": "Nutzerpools",
@@ -293,8 +310,12 @@
"Edit LDAP": "LDAP bearbeiten", "Edit LDAP": "LDAP bearbeiten",
"Enable SSL": "Aktivieren Sie SSL", "Enable SSL": "Aktivieren Sie SSL",
"Enable SSL - Tooltip": "Ob SSL aktiviert werden soll", "Enable SSL - Tooltip": "Ob SSL aktiviert werden soll",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Gruppen-ID", "Group ID": "Gruppen-ID",
"Last Sync": "Letzte Synchronisation", "Last Sync": "Letzte Synchronisation",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Server", "Server": "Server",
"Server host": "Server Host", "Server host": "Server Host",
"Server host - Tooltip": "LDAP-Server-Adresse", "Server host - Tooltip": "LDAP-Server-Adresse",
@@ -331,6 +352,16 @@
"sign up now": "Melde dich jetzt an", "sign up now": "Melde dich jetzt an",
"username, Email or phone": "Benutzername, E-Mail oder Telefon" "username, Email or phone": "Benutzername, E-Mail oder Telefon"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Modell bearbeiten", "Edit Model": "Modell bearbeiten",
"Model text": "Modelltext", "Model text": "Modelltext",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Logout", "Logout": "Logout",
"My Account": "My Account", "My Account": "My Account",
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Usage scenarios of the certificate", "Scope - Tooltip": "Usage scenarios of the certificate",
"Type - Tooltip": "Type of certificate" "Type - Tooltip": "Type of certificate"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Code you received", "Code you received": "Code you received",
"Email code": "Email code", "Email code": "Email code",
@@ -159,6 +173,7 @@
"Cert": "Cert", "Cert": "Cert",
"Cert - Tooltip": "The public key certificate that needs to be verified by the client SDK corresponding to this application", "Cert - Tooltip": "The public key certificate that needs to be verified by the client SDK corresponding to this application",
"Certs": "Certs", "Certs": "Certs",
"Chats": "Chats",
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
@@ -203,6 +218,7 @@
"Master password": "Master password", "Master password": "Master password",
"Master password - Tooltip": "Can be used to log in to all users under this organization, making it convenient for administrators to log in as this user to solve technical issues", "Master password - Tooltip": "Can be used to log in to all users under this organization, making it convenient for administrators to log in as this user to solve technical issues",
"Menu": "Menu", "Menu": "Menu",
"Messages": "Messages",
"Method": "Method", "Method": "Method",
"Model": "Model", "Model": "Model",
"Model - Tooltip": "Casbin access control model", "Model - Tooltip": "Casbin access control model",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "URL link", "URL - Tooltip": "URL link",
"Up": "Up", "Up": "Up",
"Updated time": "Updated time",
"User": "User", "User": "User",
"User - Tooltip": "Make sure the username is correct", "User - Tooltip": "Make sure the username is correct",
"User containers": "User pools", "User containers": "User pools",
@@ -293,8 +310,12 @@
"Edit LDAP": "Edit LDAP", "Edit LDAP": "Edit LDAP",
"Enable SSL": "Enable SSL", "Enable SSL": "Enable SSL",
"Enable SSL - Tooltip": "Whether to enable SSL", "Enable SSL - Tooltip": "Whether to enable SSL",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Group ID", "Group ID": "Group ID",
"Last Sync": "Last Sync", "Last Sync": "Last Sync",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Server", "Server": "Server",
"Server host": "Server host", "Server host": "Server host",
"Server host - Tooltip": "LDAP server address", "Server host - Tooltip": "LDAP server address",
@@ -331,6 +352,16 @@
"sign up now": "sign up now", "sign up now": "sign up now",
"username, Email or phone": "username, Email or phone" "username, Email or phone": "username, Email or phone"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Edit Model", "Edit Model": "Edit Model",
"Model text": "Model text", "Model text": "Model text",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Cierre de sesión", "Logout": "Cierre de sesión",
"My Account": "Mi cuenta", "My Account": "Mi cuenta",
"Sign Up": "Registrarse" "Sign Up": "Registrarse"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Escenarios de uso del certificado", "Scope - Tooltip": "Escenarios de uso del certificado",
"Type - Tooltip": "Tipo de certificado" "Type - Tooltip": "Tipo de certificado"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Código que recibió", "Code you received": "Código que recibió",
"Email code": "Código de correo electrónico", "Email code": "Código de correo electrónico",
@@ -159,6 +173,7 @@
"Cert": "ificado", "Cert": "ificado",
"Cert - Tooltip": "El certificado de clave pública que necesita ser verificado por el SDK del cliente correspondiente a esta aplicación", "Cert - Tooltip": "El certificado de clave pública que necesita ser verificado por el SDK del cliente correspondiente a esta aplicación",
"Certs": "Certificaciones", "Certs": "Certificaciones",
"Chats": "Chats",
"Click to Upload": "Haz clic para cargar", "Click to Upload": "Haz clic para cargar",
"Client IP": "Dirección IP del cliente", "Client IP": "Dirección IP del cliente",
"Close": "Cerca", "Close": "Cerca",
@@ -203,6 +218,7 @@
"Master password": "Contraseña maestra", "Master password": "Contraseña maestra",
"Master password - Tooltip": "Se puede usar para iniciar sesión en todos los usuarios de esta organización, lo que hace conveniente que los administradores inicien sesión como este usuario para resolver problemas técnicos", "Master password - Tooltip": "Se puede usar para iniciar sesión en todos los usuarios de esta organización, lo que hace conveniente que los administradores inicien sesión como este usuario para resolver problemas técnicos",
"Menu": "Menú", "Menu": "Menú",
"Messages": "Messages",
"Method": "Método", "Method": "Método",
"Model": "Modelo", "Model": "Modelo",
"Model - Tooltip": "Modelo de control de acceso Casbin", "Model - Tooltip": "Modelo de control de acceso Casbin",
@@ -269,6 +285,7 @@
"URL": "Dirección URL", "URL": "Dirección URL",
"URL - Tooltip": "Enlace de URL", "URL - Tooltip": "Enlace de URL",
"Up": "Arriba", "Up": "Arriba",
"Updated time": "Updated time",
"User": "Usuario", "User": "Usuario",
"User - Tooltip": "Asegúrate de que el nombre de usuario sea correcto", "User - Tooltip": "Asegúrate de que el nombre de usuario sea correcto",
"User containers": "Piscinas de usuarios", "User containers": "Piscinas de usuarios",
@@ -293,8 +310,12 @@
"Edit LDAP": "Editar LDAP", "Edit LDAP": "Editar LDAP",
"Enable SSL": "Habilitar SSL", "Enable SSL": "Habilitar SSL",
"Enable SSL - Tooltip": "Si se habilita SSL", "Enable SSL - Tooltip": "Si se habilita SSL",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Identificador de grupo", "Group ID": "Identificador de grupo",
"Last Sync": "Última sincronización", "Last Sync": "Última sincronización",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Servidor", "Server": "Servidor",
"Server host": "Anfitrión del servidor", "Server host": "Anfitrión del servidor",
"Server host - Tooltip": "Dirección del servidor LDAP", "Server host - Tooltip": "Dirección del servidor LDAP",
@@ -331,6 +352,16 @@
"sign up now": "Regístrate ahora", "sign up now": "Regístrate ahora",
"username, Email or phone": "Nombre de usuario, correo electrónico o teléfono" "username, Email or phone": "Nombre de usuario, correo electrónico o teléfono"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Editar modelo", "Edit Model": "Editar modelo",
"Model text": "Texto modelo", "Model text": "Texto modelo",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Déconnexion", "Logout": "Déconnexion",
"My Account": "Mon Compte", "My Account": "Mon Compte",
"Sign Up": "S'inscrire" "Sign Up": "S'inscrire"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Scénarios d'utilisation du certificat", "Scope - Tooltip": "Scénarios d'utilisation du certificat",
"Type - Tooltip": "Type de certificat" "Type - Tooltip": "Type de certificat"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Le code que vous avez reçu", "Code you received": "Le code que vous avez reçu",
"Email code": "Code email", "Email code": "Code email",
@@ -159,6 +173,7 @@
"Cert": "ainement", "Cert": "ainement",
"Cert - Tooltip": "Le certificat de clé publique qui doit être vérifié par le kit de développement client correspondant à cette application", "Cert - Tooltip": "Le certificat de clé publique qui doit être vérifié par le kit de développement client correspondant à cette application",
"Certs": "Certains", "Certs": "Certains",
"Chats": "Chats",
"Click to Upload": "Cliquez pour télécharger", "Click to Upload": "Cliquez pour télécharger",
"Client IP": "Adresse IP du client", "Client IP": "Adresse IP du client",
"Close": "Fermer", "Close": "Fermer",
@@ -203,6 +218,7 @@
"Master password": "Mot de passe principal", "Master password": "Mot de passe principal",
"Master password - Tooltip": "Peut être utilisé pour se connecter à tous les utilisateurs sous cette organisation, ce qui facilite la connexion des administrateurs en tant que cet utilisateur pour résoudre les problèmes techniques", "Master password - Tooltip": "Peut être utilisé pour se connecter à tous les utilisateurs sous cette organisation, ce qui facilite la connexion des administrateurs en tant que cet utilisateur pour résoudre les problèmes techniques",
"Menu": "Menu", "Menu": "Menu",
"Messages": "Messages",
"Method": "Méthode", "Method": "Méthode",
"Model": "Modèle", "Model": "Modèle",
"Model - Tooltip": "Modèle de contrôle d'accès Casbin", "Model - Tooltip": "Modèle de contrôle d'accès Casbin",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "Lien d'URL", "URL - Tooltip": "Lien d'URL",
"Up": "Haut", "Up": "Haut",
"Updated time": "Updated time",
"User": "Utilisateur", "User": "Utilisateur",
"User - Tooltip": "Assurez-vous que le nom d'utilisateur est correct", "User - Tooltip": "Assurez-vous que le nom d'utilisateur est correct",
"User containers": "Piscines d'utilisateurs", "User containers": "Piscines d'utilisateurs",
@@ -293,8 +310,12 @@
"Edit LDAP": "Modifier LDAP", "Edit LDAP": "Modifier LDAP",
"Enable SSL": "Activer SSL", "Enable SSL": "Activer SSL",
"Enable SSL - Tooltip": "Que ce soit pour activer SSL", "Enable SSL - Tooltip": "Que ce soit pour activer SSL",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Identifiant de groupe", "Group ID": "Identifiant de groupe",
"Last Sync": "Dernière synchronisation", "Last Sync": "Dernière synchronisation",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Serveur", "Server": "Serveur",
"Server host": "Hébergeur de serveur", "Server host": "Hébergeur de serveur",
"Server host - Tooltip": "Adresse du serveur LDAP", "Server host - Tooltip": "Adresse du serveur LDAP",
@@ -331,6 +352,16 @@
"sign up now": "Inscrivez-vous maintenant", "sign up now": "Inscrivez-vous maintenant",
"username, Email or phone": "Nom d'utilisateur, e-mail ou téléphone" "username, Email or phone": "Nom d'utilisateur, e-mail ou téléphone"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Modifier le modèle", "Edit Model": "Modifier le modèle",
"Model text": "Texte modèle", "Model text": "Texte modèle",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Keluar", "Logout": "Keluar",
"My Account": "Akun Saya", "My Account": "Akun Saya",
"Sign Up": "Mendaftar" "Sign Up": "Mendaftar"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Skema penggunaan sertifikat:", "Scope - Tooltip": "Skema penggunaan sertifikat:",
"Type - Tooltip": "Jenis sertifikat" "Type - Tooltip": "Jenis sertifikat"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Kode yang kamu terima", "Code you received": "Kode yang kamu terima",
"Email code": "Kode email", "Email code": "Kode email",
@@ -159,6 +173,7 @@
"Cert": "Sertifikat", "Cert": "Sertifikat",
"Cert - Tooltip": "Sertifikat kunci publik yang perlu diverifikasi oleh SDK klien yang sesuai dengan aplikasi ini", "Cert - Tooltip": "Sertifikat kunci publik yang perlu diverifikasi oleh SDK klien yang sesuai dengan aplikasi ini",
"Certs": "Sertifikat", "Certs": "Sertifikat",
"Chats": "Chats",
"Click to Upload": "Klik untuk Mengunggah", "Click to Upload": "Klik untuk Mengunggah",
"Client IP": "IP klien", "Client IP": "IP klien",
"Close": "Tutup", "Close": "Tutup",
@@ -203,6 +218,7 @@
"Master password": "Kata sandi utama", "Master password": "Kata sandi utama",
"Master password - Tooltip": "Dapat digunakan untuk masuk ke semua pengguna di bawah organisasi ini, sehingga memudahkan administrator untuk masuk sebagai pengguna ini untuk menyelesaikan masalah teknis", "Master password - Tooltip": "Dapat digunakan untuk masuk ke semua pengguna di bawah organisasi ini, sehingga memudahkan administrator untuk masuk sebagai pengguna ini untuk menyelesaikan masalah teknis",
"Menu": "Daftar makanan", "Menu": "Daftar makanan",
"Messages": "Messages",
"Method": "Metode", "Method": "Metode",
"Model": "Model", "Model": "Model",
"Model - Tooltip": "Model kontrol akses Casbin", "Model - Tooltip": "Model kontrol akses Casbin",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "Tautan URL", "URL - Tooltip": "Tautan URL",
"Up": "Ke atas", "Up": "Ke atas",
"Updated time": "Updated time",
"User": "Pengguna", "User": "Pengguna",
"User - Tooltip": "Pastikan username-nya benar", "User - Tooltip": "Pastikan username-nya benar",
"User containers": "User pools", "User containers": "User pools",
@@ -293,8 +310,12 @@
"Edit LDAP": "Mengedit LDAP", "Edit LDAP": "Mengedit LDAP",
"Enable SSL": "Aktifkan SSL", "Enable SSL": "Aktifkan SSL",
"Enable SSL - Tooltip": "Apakah untuk mengaktifkan SSL?", "Enable SSL - Tooltip": "Apakah untuk mengaktifkan SSL?",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "ID grup", "Group ID": "ID grup",
"Last Sync": "Terakhir Sinkronisasi", "Last Sync": "Terakhir Sinkronisasi",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Server", "Server": "Server",
"Server host": "Hewan Server", "Server host": "Hewan Server",
"Server host - Tooltip": "Alamat server LDAP", "Server host - Tooltip": "Alamat server LDAP",
@@ -331,6 +352,16 @@
"sign up now": "Daftar sekarang", "sign up now": "Daftar sekarang",
"username, Email or phone": "nama pengguna, Email atau nomor telepon" "username, Email or phone": "nama pengguna, Email atau nomor telepon"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Mengedit Model", "Edit Model": "Mengedit Model",
"Model text": "Teks Model", "Model text": "Teks Model",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "ログアウト", "Logout": "ログアウト",
"My Account": "マイアカウント", "My Account": "マイアカウント",
"Sign Up": "新規登録" "Sign Up": "新規登録"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "証明書の使用シナリオ", "Scope - Tooltip": "証明書の使用シナリオ",
"Type - Tooltip": "証明書の種類" "Type - Tooltip": "証明書の種類"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "受け取ったコード", "Code you received": "受け取ったコード",
"Email code": "メールコード", "Email code": "メールコード",
@@ -159,6 +173,7 @@
"Cert": "証明書", "Cert": "証明書",
"Cert - Tooltip": "このアプリケーションに対応するクライアントSDKによって検証する必要がある公開鍵証明書", "Cert - Tooltip": "このアプリケーションに対応するクライアントSDKによって検証する必要がある公開鍵証明書",
"Certs": "証明書", "Certs": "証明書",
"Chats": "Chats",
"Click to Upload": "アップロードするにはクリックしてください", "Click to Upload": "アップロードするにはクリックしてください",
"Client IP": "クライアントIP", "Client IP": "クライアントIP",
"Close": "閉じる", "Close": "閉じる",
@@ -203,6 +218,7 @@
"Master password": "マスターパスワード", "Master password": "マスターパスワード",
"Master password - Tooltip": "この組織のすべてのユーザーにログインするために使用でき、管理者が技術的な問題を解決するためにこのユーザーとしてログインするのに便利です", "Master password - Tooltip": "この組織のすべてのユーザーにログインするために使用でき、管理者が技術的な問題を解決するためにこのユーザーとしてログインするのに便利です",
"Menu": "メニュー", "Menu": "メニュー",
"Messages": "Messages",
"Method": "方法", "Method": "方法",
"Model": "モデル", "Model": "モデル",
"Model - Tooltip": "カスビンアクセスコントロールモデル", "Model - Tooltip": "カスビンアクセスコントロールモデル",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "URLリンク", "URL - Tooltip": "URLリンク",
"Up": "アップ", "Up": "アップ",
"Updated time": "Updated time",
"User": "ユーザー", "User": "ユーザー",
"User - Tooltip": "ユーザー名が正しいことを確認してください", "User - Tooltip": "ユーザー名が正しいことを確認してください",
"User containers": "ユーザープール", "User containers": "ユーザープール",
@@ -293,8 +310,12 @@
"Edit LDAP": "LDAPを編集", "Edit LDAP": "LDAPを編集",
"Enable SSL": "SSL を有効にする", "Enable SSL": "SSL を有効にする",
"Enable SSL - Tooltip": "SSLを有効にするかどうか", "Enable SSL - Tooltip": "SSLを有効にするかどうか",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "グループID", "Group ID": "グループID",
"Last Sync": "最後の同期", "Last Sync": "最後の同期",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "サーバー", "Server": "サーバー",
"Server host": "サーバーホスト", "Server host": "サーバーホスト",
"Server host - Tooltip": "LDAPサーバーのアドレス", "Server host - Tooltip": "LDAPサーバーのアドレス",
@@ -331,6 +352,16 @@
"sign up now": "今すぐサインアップ", "sign up now": "今すぐサインアップ",
"username, Email or phone": "ユーザー名、メールアドレス、または電話番号" "username, Email or phone": "ユーザー名、メールアドレス、または電話番号"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "編集モデル", "Edit Model": "編集モデル",
"Model text": "モデルテキスト", "Model text": "モデルテキスト",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "로그아웃", "Logout": "로그아웃",
"My Account": "내 계정", "My Account": "내 계정",
"Sign Up": "가입하기" "Sign Up": "가입하기"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "인증서의 사용 시나리오", "Scope - Tooltip": "인증서의 사용 시나리오",
"Type - Tooltip": "증명서 유형" "Type - Tooltip": "증명서 유형"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "받은 코드", "Code you received": "받은 코드",
"Email code": "이메일 코드", "Email code": "이메일 코드",
@@ -159,6 +173,7 @@
"Cert": "인증서", "Cert": "인증서",
"Cert - Tooltip": "이 응용 프로그램에 해당하는 클라이언트 SDK에서 확인해야 하는 공개 키 인증서", "Cert - Tooltip": "이 응용 프로그램에 해당하는 클라이언트 SDK에서 확인해야 하는 공개 키 인증서",
"Certs": "증명서", "Certs": "증명서",
"Chats": "Chats",
"Click to Upload": "클릭하여 업로드하세요", "Click to Upload": "클릭하여 업로드하세요",
"Client IP": "고객 IP", "Client IP": "고객 IP",
"Close": "닫다", "Close": "닫다",
@@ -203,6 +218,7 @@
"Master password": "마스터 비밀번호", "Master password": "마스터 비밀번호",
"Master password - Tooltip": "이 조직의 모든 사용자에게 로그인하는 데 사용될 수 있으며, 이 사용자로 로그인하여 기술 문제를 해결하는 관리자에게 편리합니다", "Master password - Tooltip": "이 조직의 모든 사용자에게 로그인하는 데 사용될 수 있으며, 이 사용자로 로그인하여 기술 문제를 해결하는 관리자에게 편리합니다",
"Menu": "메뉴", "Menu": "메뉴",
"Messages": "Messages",
"Method": "방법", "Method": "방법",
"Model": "모델", "Model": "모델",
"Model - Tooltip": "Casbin 접근 제어 모델", "Model - Tooltip": "Casbin 접근 제어 모델",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "URL 링크", "URL - Tooltip": "URL 링크",
"Up": "위로", "Up": "위로",
"Updated time": "Updated time",
"User": "사용자", "User": "사용자",
"User - Tooltip": "사용자 이름이 정확한지 확인하세요", "User - Tooltip": "사용자 이름이 정확한지 확인하세요",
"User containers": "사용자 풀", "User containers": "사용자 풀",
@@ -293,8 +310,12 @@
"Edit LDAP": "LDAP 수정", "Edit LDAP": "LDAP 수정",
"Enable SSL": "SSL 활성화", "Enable SSL": "SSL 활성화",
"Enable SSL - Tooltip": "SSL을 활성화할지 여부를 결정하십시오", "Enable SSL - Tooltip": "SSL을 활성화할지 여부를 결정하십시오",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "그룹 ID", "Group ID": "그룹 ID",
"Last Sync": "마지막 동기화", "Last Sync": "마지막 동기화",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "서버", "Server": "서버",
"Server host": "서버 호스트", "Server host": "서버 호스트",
"Server host - Tooltip": "LDAP 서버 주소", "Server host - Tooltip": "LDAP 서버 주소",
@@ -331,6 +352,16 @@
"sign up now": "지금 가입하세요", "sign up now": "지금 가입하세요",
"username, Email or phone": "유저명, 이메일 또는 전화번호" "username, Email or phone": "유저명, 이메일 또는 전화번호"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "편집 형태 모델", "Edit Model": "편집 형태 모델",
"Model text": "모델 텍스트", "Model text": "모델 텍스트",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Выход", "Logout": "Выход",
"My Account": "Мой аккаунт", "My Account": "Мой аккаунт",
"Sign Up": "Зарегистрироваться" "Sign Up": "Зарегистрироваться"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Сценарии использования сертификата", "Scope - Tooltip": "Сценарии использования сертификата",
"Type - Tooltip": "Тип сертификата" "Type - Tooltip": "Тип сертификата"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Код, который вы получили", "Code you received": "Код, который вы получили",
"Email code": "Электронный код письма", "Email code": "Электронный код письма",
@@ -159,6 +173,7 @@
"Cert": "Сертификат", "Cert": "Сертификат",
"Cert - Tooltip": "Сертификат открытого ключа, который требуется проверить клиентским SDK, соответствующим этому приложению", "Cert - Tooltip": "Сертификат открытого ключа, который требуется проверить клиентским SDK, соответствующим этому приложению",
"Certs": "сертификаты", "Certs": "сертификаты",
"Chats": "Chats",
"Click to Upload": "Нажмите, чтобы загрузить", "Click to Upload": "Нажмите, чтобы загрузить",
"Client IP": "Клиентский IP", "Client IP": "Клиентский IP",
"Close": "Близко", "Close": "Близко",
@@ -203,6 +218,7 @@
"Master password": "Главный пароль", "Master password": "Главный пароль",
"Master password - Tooltip": "Можно использовать для входа в учетные записи всех пользователей этой организации, что удобно для администраторов, чтобы войти в качестве этого пользователя и решить технические проблемы", "Master password - Tooltip": "Можно использовать для входа в учетные записи всех пользователей этой организации, что удобно для администраторов, чтобы войти в качестве этого пользователя и решить технические проблемы",
"Menu": "Меню", "Menu": "Меню",
"Messages": "Messages",
"Method": "Метод", "Method": "Метод",
"Model": "Модель", "Model": "Модель",
"Model - Tooltip": "Модель контроля доступа Casbin", "Model - Tooltip": "Модель контроля доступа Casbin",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "Ссылка URL", "URL - Tooltip": "Ссылка URL",
"Up": "Вверх", "Up": "Вверх",
"Updated time": "Updated time",
"User": "Пользователь", "User": "Пользователь",
"User - Tooltip": "Убедитесь, что имя пользователя правильное", "User - Tooltip": "Убедитесь, что имя пользователя правильное",
"User containers": "Пользовательские пулы", "User containers": "Пользовательские пулы",
@@ -293,8 +310,12 @@
"Edit LDAP": "Изменить LDAP", "Edit LDAP": "Изменить LDAP",
"Enable SSL": "Включить SSL", "Enable SSL": "Включить SSL",
"Enable SSL - Tooltip": "Перевод: Следует ли включать SSL", "Enable SSL - Tooltip": "Перевод: Следует ли включать SSL",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Идентификатор группы", "Group ID": "Идентификатор группы",
"Last Sync": "Последняя синхронизация", "Last Sync": "Последняя синхронизация",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Сервер", "Server": "Сервер",
"Server host": "Хост сервера", "Server host": "Хост сервера",
"Server host - Tooltip": "Адрес сервера LDAP", "Server host - Tooltip": "Адрес сервера LDAP",
@@ -331,6 +352,16 @@
"sign up now": "Зарегистрируйтесь сейчас", "sign up now": "Зарегистрируйтесь сейчас",
"username, Email or phone": "имя пользователя, электронная почта или телефон" "username, Email or phone": "имя пользователя, электронная почта или телефон"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Редактировать модель", "Edit Model": "Редактировать модель",
"Model text": "Модельный текст", "Model text": "Модельный текст",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Đăng xuất", "Logout": "Đăng xuất",
"My Account": "Tài khoản của tôi", "My Account": "Tài khoản của tôi",
"Sign Up": "Đăng ký" "Sign Up": "Đăng ký"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "Các kịch bản sử dụng của giấy chứng nhận", "Scope - Tooltip": "Các kịch bản sử dụng của giấy chứng nhận",
"Type - Tooltip": "Loại chứng chỉ" "Type - Tooltip": "Loại chứng chỉ"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "Edit Chat",
"Group": "Group",
"Message count": "Message count",
"New Chat": "New Chat",
"Single": "Single",
"User1": "User1",
"User1 - Tooltip": "User1 - Tooltip",
"User2": "User2",
"User2 - Tooltip": "User2 - Tooltip",
"Users - Tooltip": "Users - Tooltip"
},
"code": { "code": {
"Code you received": "Mã bạn nhận được", "Code you received": "Mã bạn nhận được",
"Email code": "Mã email", "Email code": "Mã email",
@@ -159,6 +173,7 @@
"Cert": "Chứng chỉ", "Cert": "Chứng chỉ",
"Cert - Tooltip": "Chứng chỉ khóa công khai cần được xác minh bởi SDK khách hàng tương ứng với ứng dụng này", "Cert - Tooltip": "Chứng chỉ khóa công khai cần được xác minh bởi SDK khách hàng tương ứng với ứng dụng này",
"Certs": "Chứng chỉ", "Certs": "Chứng chỉ",
"Chats": "Chats",
"Click to Upload": "Nhấp để tải lên", "Click to Upload": "Nhấp để tải lên",
"Client IP": "Địa chỉ IP của khách hàng", "Client IP": "Địa chỉ IP của khách hàng",
"Close": "Đóng lại", "Close": "Đóng lại",
@@ -203,6 +218,7 @@
"Master password": "Mật khẩu chính", "Master password": "Mật khẩu chính",
"Master password - Tooltip": "Có thể được sử dụng để đăng nhập vào tất cả các người dùng trong tổ chức này, giúp cho quản trị viên dễ dàng đăng nhập với tư cách người dùng này để giải quyết các vấn đề kỹ thuật", "Master password - Tooltip": "Có thể được sử dụng để đăng nhập vào tất cả các người dùng trong tổ chức này, giúp cho quản trị viên dễ dàng đăng nhập với tư cách người dùng này để giải quyết các vấn đề kỹ thuật",
"Menu": "Thực đơn", "Menu": "Thực đơn",
"Messages": "Messages",
"Method": "Phương pháp", "Method": "Phương pháp",
"Model": "Mô hình", "Model": "Mô hình",
"Model - Tooltip": "Mô hình kiểm soát truy cập Casbin", "Model - Tooltip": "Mô hình kiểm soát truy cập Casbin",
@@ -269,6 +285,7 @@
"URL": "URL", "URL": "URL",
"URL - Tooltip": "Đường dẫn URL", "URL - Tooltip": "Đường dẫn URL",
"Up": "Lên", "Up": "Lên",
"Updated time": "Updated time",
"User": "Người dùng", "User": "Người dùng",
"User - Tooltip": "Hãy đảm bảo tên đăng nhập chính xác", "User - Tooltip": "Hãy đảm bảo tên đăng nhập chính xác",
"User containers": "Nhóm người dùng", "User containers": "Nhóm người dùng",
@@ -293,8 +310,12 @@
"Edit LDAP": "Chỉnh sửa LDAP", "Edit LDAP": "Chỉnh sửa LDAP",
"Enable SSL": "Kích hoạt SSL", "Enable SSL": "Kích hoạt SSL",
"Enable SSL - Tooltip": "Có nên kích hoạt SSL hay không?", "Enable SSL - Tooltip": "Có nên kích hoạt SSL hay không?",
"Filter fields": "Filter fields",
"Filter fields - Tooltip": "Filter fields - Tooltip",
"Group ID": "Nhóm ID", "Group ID": "Nhóm ID",
"Last Sync": "Đồng bộ lần cuối", "Last Sync": "Đồng bộ lần cuối",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "Máy chủ", "Server": "Máy chủ",
"Server host": "Máy chủ chủ động", "Server host": "Máy chủ chủ động",
"Server host - Tooltip": "Địa chỉ máy chủ LDAP", "Server host - Tooltip": "Địa chỉ máy chủ LDAP",
@@ -331,6 +352,16 @@
"sign up now": "Đăng ký ngay bây giờ", "sign up now": "Đăng ký ngay bây giờ",
"username, Email or phone": "Tên đăng nhập, Email hoặc điện thoại" "username, Email or phone": "Tên đăng nhập, Email hoặc điện thoại"
}, },
"message": {
"Author": "Author",
"Author - Tooltip": "Author - Tooltip",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Tooltip",
"Edit Message": "Edit Message",
"New Message": "New Message",
"Text": "Text",
"Text - Tooltip": "Text - Tooltip"
},
"model": { "model": {
"Edit Model": "Chỉnh sửa mô hình", "Edit Model": "Chỉnh sửa mô hình",
"Model text": "Văn bản mẫu", "Model text": "Văn bản mẫu",

View File

@@ -1,5 +1,6 @@
{ {
"account": { "account": {
"Chats & Messages": "聊天 & 消息",
"Logout": "登出", "Logout": "登出",
"My Account": "我的账户", "My Account": "我的账户",
"Sign Up": "注册" "Sign Up": "注册"
@@ -117,6 +118,19 @@
"Scope - Tooltip": "公钥证书的使用场景", "Scope - Tooltip": "公钥证书的使用场景",
"Type - Tooltip": "公钥证书的类型" "Type - Tooltip": "公钥证书的类型"
}, },
"chat": {
"AI": "AI",
"Edit Chat": "编辑聊天",
"Group": "群聊",
"Message count": "消息个数",
"New Chat": "添加聊天",
"Single": "单聊",
"User1": "用户1",
"User1 - Tooltip": "当聊天类型为单聊时,该值为聊天的发起者;当聊天类型为群聊时,该值为群主",
"User2": "用户2",
"User2 - Tooltip": "当聊天类型为单聊时,该值为聊天的对象;当聊天类型为群聊时,该值为管理员",
"Users - Tooltip": "能够接收到聊天消息的所有用户"
},
"code": { "code": {
"Code you received": "验证码", "Code you received": "验证码",
"Email code": "邮箱验证码", "Email code": "邮箱验证码",
@@ -159,6 +173,7 @@
"Cert": "证书", "Cert": "证书",
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书", "Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
"Certs": "证书", "Certs": "证书",
"Chats": "聊天",
"Click to Upload": "点击上传", "Click to Upload": "点击上传",
"Client IP": "客户端IP", "Client IP": "客户端IP",
"Close": "关闭", "Close": "关闭",
@@ -203,6 +218,7 @@
"Master password": "万能密码", "Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题", "Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
"Menu": "目录", "Menu": "目录",
"Messages": "消息",
"Method": "方法", "Method": "方法",
"Model": "模型", "Model": "模型",
"Model - Tooltip": "Casbin的访问控制模型", "Model - Tooltip": "Casbin的访问控制模型",
@@ -269,6 +285,7 @@
"URL": "链接", "URL": "链接",
"URL - Tooltip": "URL链接", "URL - Tooltip": "URL链接",
"Up": "上移", "Up": "上移",
"Updated time": "更新时间",
"User": "用户", "User": "用户",
"User - Tooltip": "请确保用户名正确", "User - Tooltip": "请确保用户名正确",
"User containers": "用户池", "User containers": "用户池",
@@ -293,8 +310,12 @@
"Edit LDAP": "编辑LDAP", "Edit LDAP": "编辑LDAP",
"Enable SSL": "启用SSL", "Enable SSL": "启用SSL",
"Enable SSL - Tooltip": "是否启用SSL", "Enable SSL - Tooltip": "是否启用SSL",
"Filter fields": "过滤字段",
"Filter fields - Tooltip": "使用ldap用户登录Casdoor时, 用于搜索ldap服务器中该用户的字段 - Tooltip",
"Group ID": "组ID", "Group ID": "组ID",
"Last Sync": "最近同步", "Last Sync": "最近同步",
"Search Filter": "Search Filter",
"Search Filter - Tooltip": "Search Filter - Tooltip",
"Server": "服务器", "Server": "服务器",
"Server host": "域名", "Server host": "域名",
"Server host - Tooltip": "LDAP服务器地址", "Server host - Tooltip": "LDAP服务器地址",
@@ -331,6 +352,16 @@
"sign up now": "立即注册", "sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号" "username, Email or phone": "用户名、Email或手机号"
}, },
"message": {
"Author": "作者",
"Author - Tooltip": "发出消息的用户",
"Chat": "聊天",
"Chat - Tooltip": "消息所属的聊天ID",
"Edit Message": "编辑消息",
"New Message": "添加消息",
"Text": "内容",
"Text - Tooltip": "消息的内容"
},
"model": { "model": {
"Edit Model": "编辑模型", "Edit Model": "编辑模型",
"Model text": "模型文本", "Model text": "模型文本",

View File

@@ -81,16 +81,27 @@ class AccountTable extends React.Component {
{name: "Country code", displayName: i18next.t("user:Country code")}, {name: "Country code", displayName: i18next.t("user:Country code")},
{name: "Country/Region", displayName: i18next.t("user:Country/Region")}, {name: "Country/Region", displayName: i18next.t("user:Country/Region")},
{name: "Location", displayName: i18next.t("user:Location")}, {name: "Location", displayName: i18next.t("user:Location")},
{name: "Address", displayName: i18next.t("user:Address")},
{name: "Affiliation", displayName: i18next.t("user:Affiliation")}, {name: "Affiliation", displayName: i18next.t("user:Affiliation")},
{name: "Title", displayName: i18next.t("user:Title")}, {name: "Title", displayName: i18next.t("user:Title")},
{name: "ID card type", displayName: i18next.t("user:ID card type")},
{name: "ID card", displayName: i18next.t("user:ID card")},
{name: "Homepage", displayName: i18next.t("user:Homepage")}, {name: "Homepage", displayName: i18next.t("user:Homepage")},
{name: "Bio", displayName: i18next.t("user:Bio")}, {name: "Bio", displayName: i18next.t("user:Bio")},
{name: "Tag", displayName: i18next.t("user:Tag")}, {name: "Tag", displayName: i18next.t("user:Tag")},
{name: "Language", displayName: i18next.t("user:Language")},
{name: "Gender", displayName: i18next.t("user:Gender")},
{name: "Birthday", displayName: i18next.t("user:Birthday")},
{name: "Education", displayName: i18next.t("user:Education")},
{name: "Score", displayName: i18next.t("user:Score")},
{name: "Karma", displayName: i18next.t("user:Karma")},
{name: "Ranking", displayName: i18next.t("user:Ranking")},
{name: "Signup application", displayName: i18next.t("general:Signup application")}, {name: "Signup application", displayName: i18next.t("general:Signup application")},
{name: "Roles", displayName: i18next.t("general:Roles")}, {name: "Roles", displayName: i18next.t("general:Roles")},
{name: "Permissions", displayName: i18next.t("general:Permissions")}, {name: "Permissions", displayName: i18next.t("general:Permissions")},
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")}, {name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
{name: "Properties", displayName: i18next.t("user:Properties")}, {name: "Properties", displayName: i18next.t("user:Properties")},
{name: "Is online", displayName: i18next.t("user:Is online")},
{name: "Is admin", displayName: i18next.t("user:Is admin")}, {name: "Is admin", displayName: i18next.t("user:Is admin")},
{name: "Is global admin", displayName: i18next.t("user:Is global admin")}, {name: "Is global admin", displayName: i18next.t("user:Is global admin")},
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")}, {name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},

View File

@@ -45,8 +45,8 @@ class LdapTable extends React.Component {
serverName: "Example LDAP Server", serverName: "Example LDAP Server",
host: "example.com", host: "example.com",
port: 389, port: 389,
admin: "cn=admin,dc=example,dc=com", username: "cn=admin,dc=example,dc=com",
passwd: "123", password: "123",
baseDn: "ou=People,dc=example,dc=com", baseDn: "ou=People,dc=example,dc=com",
autosync: 0, autosync: 0,
lastSync: "", lastSync: "",

View File

@@ -186,6 +186,11 @@ class SignupTable extends React.Component {
{id: "Normal", name: i18next.t("application:Normal")}, {id: "Normal", name: i18next.t("application:Normal")},
{id: "No verification", name: i18next.t("application:No verification")}, {id: "No verification", name: i18next.t("application:No verification")},
]; ];
} else if (record.name === "Phone") {
options = [
{id: "Normal", name: i18next.t("application:Normal")},
{id: "No verification", name: i18next.t("application:No verification")},
];
} else if (record.name === "Agreement") { } else if (record.name === "Agreement") {
options = [ options = [
{id: "None", name: i18next.t("application:Only signup")}, {id: "None", name: i18next.t("application:Only signup")},