mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 04:03:23 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
445d3c9d0e | |||
dbebd1846f | |||
2fcc8f5bfe | |||
4b65320a96 | |||
5e8897e41b | |||
ba1646a0c3 | |||
c1cd187558 | |||
519fd655cf | |||
377ac05928 | |||
4f124ff140 | |||
d5f802ec7d |
@ -38,6 +38,7 @@ type RequestForm struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
IdCard string `json:"idCard"`
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
|
||||||
Application string `json:"application"`
|
Application string `json:"application"`
|
||||||
@ -61,6 +62,7 @@ type Response struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
@ -151,6 +153,7 @@ func (c *ApiController) Signup() {
|
|||||||
Phone: form.Phone,
|
Phone: form.Phone,
|
||||||
Address: []string{},
|
Address: []string{},
|
||||||
Affiliation: form.Affiliation,
|
Affiliation: form.Affiliation,
|
||||||
|
IdCard: form.IdCard,
|
||||||
Region: form.Region,
|
Region: form.Region,
|
||||||
Score: getInitScore(),
|
Score: getInitScore(),
|
||||||
IsAdmin: false,
|
IsAdmin: false,
|
||||||
@ -220,6 +223,7 @@ func (c *ApiController) GetAccount() {
|
|||||||
resp := Response{
|
resp := Response{
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
Sub: user.Id,
|
Sub: user.Id,
|
||||||
|
Name: user.Name,
|
||||||
Data: user,
|
Data: user,
|
||||||
Data2: organization,
|
Data2: organization,
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
// @Success 200 {array} object.Application The Response object
|
// @Success 200 {array} object.Application The Response object
|
||||||
// @router /get-applications [get]
|
// @router /get-applications [get]
|
||||||
func (c *ApiController) GetApplications() {
|
func (c *ApiController) GetApplications() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
page := c.Input().Get("p")
|
page := c.Input().Get("p")
|
||||||
@ -38,12 +39,12 @@ func (c *ApiController) GetApplications() {
|
|||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetApplications(owner)
|
c.Data["json"] = object.GetMaskedApplications(object.GetApplications(owner), userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
||||||
applications := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,9 +57,10 @@ func (c *ApiController) GetApplications() {
|
|||||||
// @Success 200 {object} object.Application The Response object
|
// @Success 200 {object} object.Application The Response object
|
||||||
// @router /get-application [get]
|
// @router /get-application [get]
|
||||||
func (c *ApiController) GetApplication() {
|
func (c *ApiController) GetApplication() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetApplication(id)
|
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +72,7 @@ func (c *ApiController) GetApplication() {
|
|||||||
// @Success 200 {object} object.Application The Response object
|
// @Success 200 {object} object.Application The Response object
|
||||||
// @router /get-user-application [get]
|
// @router /get-user-application [get]
|
||||||
func (c *ApiController) GetUserApplication() {
|
func (c *ApiController) GetUserApplication() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
user := object.GetUser(id)
|
user := object.GetUser(id)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@ -77,7 +80,7 @@ func (c *ApiController) GetUserApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetApplicationByUser(user)
|
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
116
controllers/cert.go
Normal file
116
controllers/cert.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 The casbin 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
|
"github.com/casbin/casdoor/object"
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCerts
|
||||||
|
// @Title GetCerts
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description get certs
|
||||||
|
// @Param owner query string true "The owner of certs"
|
||||||
|
// @Success 200 {array} object.Cert The Response object
|
||||||
|
// @router /get-certs [get]
|
||||||
|
func (c *ApiController) GetCerts() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetMaskedCerts(object.GetCerts(owner))
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
|
||||||
|
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||||
|
c.ResponseOk(certs, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetCert
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description get cert
|
||||||
|
// @Param id query string true "The id of the cert"
|
||||||
|
// @Success 200 {object} object.Cert The Response object
|
||||||
|
// @router /get-cert [get]
|
||||||
|
func (c *ApiController) GetCert() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateCert
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description update cert
|
||||||
|
// @Param id query string true "The id of the cert"
|
||||||
|
// @Param body body object.Cert true "The details of the cert"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-cert [post]
|
||||||
|
func (c *ApiController) UpdateCert() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var cert object.Cert
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddCert
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description add cert
|
||||||
|
// @Param body body object.Cert true "The details of the cert"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-cert [post]
|
||||||
|
func (c *ApiController) AddCert() {
|
||||||
|
var cert object.Cert
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteCert
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description delete cert
|
||||||
|
// @Param body body object.Cert true "The details of the cert"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-cert [post]
|
||||||
|
func (c *ApiController) DeleteCert() {
|
||||||
|
var cert object.Cert
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -28,7 +28,7 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
// @router /api/certs [get]
|
// @router /api/certs [get]
|
||||||
func (c *RootController) GetOidcCert() {
|
func (c *RootController) GetOidcCert() {
|
||||||
jwks, err := object.GetJSONWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -226,11 +226,6 @@ func (c *ApiController) SetPassword() {
|
|||||||
c.ResponseError("Please login first.")
|
c.ResponseError("Please login first.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestUser := object.GetUser(requestUserId)
|
|
||||||
if requestUser == nil {
|
|
||||||
c.ResponseError("Session outdated. Please login again.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||||
targetUser := object.GetUser(userId)
|
targetUser := object.GetUser(userId)
|
||||||
@ -240,7 +235,14 @@ func (c *ApiController) SetPassword() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPermission := false
|
hasPermission := false
|
||||||
|
if strings.HasPrefix(requestUserId, "app/") {
|
||||||
|
hasPermission = true
|
||||||
|
} else {
|
||||||
|
requestUser := object.GetUser(requestUserId)
|
||||||
|
if requestUser == nil {
|
||||||
|
c.ResponseError("Session outdated. Please login again.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if requestUser.IsGlobalAdmin {
|
if requestUser.IsGlobalAdmin {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
} else if requestUserId == userId {
|
} else if requestUserId == userId {
|
||||||
@ -248,7 +250,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
c.ResponseError("You don't have the permission to do this.")
|
c.ResponseError("You don't have the permission to do this.")
|
||||||
return
|
return
|
||||||
@ -272,8 +274,6 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetSessionUsername("")
|
|
||||||
|
|
||||||
targetUser.Password = newPassword
|
targetUser.Password = newPassword
|
||||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||||
c.Data["json"] = Response{Status: "ok"}
|
c.Data["json"] = Response{Status: "ok"}
|
||||||
|
60
controllers/user_upload.go
Normal file
60
controllers/user_upload.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2021 The casbin 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casbin/casdoor/object"
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func saveFile(path string, file *multipart.File) {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, *file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UploadUsers() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
util.EnsureFileFolderExists(path)
|
||||||
|
saveFile(path, &file)
|
||||||
|
|
||||||
|
affected := object.UploadUsers(owner, fileId)
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError("Failed to import users")
|
||||||
|
}
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -25,6 +25,7 @@ require (
|
|||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.1.1
|
||||||
github.com/satori/go.uuid v1.2.0 // indirect
|
github.com/satori/go.uuid v1.2.0 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
|
2
go.sum
2
go.sum
@ -349,6 +349,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
|
|||||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
|
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||||
|
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||||
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||||
|
@ -145,6 +145,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Ldap))
|
err = a.Engine.Sync2(new(Ldap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -31,6 +31,7 @@ type Application struct {
|
|||||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
EnablePassword bool `json:"enablePassword"`
|
EnablePassword bool `json:"enablePassword"`
|
||||||
EnableSignUp bool `json:"enableSignUp"`
|
EnableSignUp bool `json:"enableSignUp"`
|
||||||
EnableSigninSession bool `json:"enableSigninSession"`
|
EnableSigninSession bool `json:"enableSigninSession"`
|
||||||
@ -200,24 +201,37 @@ func GetApplicationByClientId(clientId string) *Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApplicationByClientIdAndSecret(clientId, clientSecret string) *Application {
|
|
||||||
if util.IsStrsEmpty(clientId, clientSecret) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
app := GetApplicationByClientId(clientId)
|
|
||||||
if app == nil || app.ClientSecret != clientSecret {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetApplication(id string) *Application {
|
func GetApplication(id string) *Application {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
return getApplication(owner, name)
|
return getApplication(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMaskedApplication(application *Application, userId string) *Application {
|
||||||
|
if isUserIdGlobalAdmin(userId) {
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.ClientSecret != "" {
|
||||||
|
application.ClientSecret = "***"
|
||||||
|
}
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMaskedApplications(applications []*Application, userId string) []*Application {
|
||||||
|
if isUserIdGlobalAdmin(userId) {
|
||||||
|
return applications
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, application := range applications {
|
||||||
|
application = GetMaskedApplication(application, userId)
|
||||||
|
}
|
||||||
|
return applications
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateApplication(id string, application *Application) bool {
|
func UpdateApplication(id string, application *Application) bool {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if getApplication(owner, name) == nil {
|
if getApplication(owner, name) == nil {
|
||||||
|
164
object/cert.go
Normal file
164
object/cert.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Copyright 2021 The casbin 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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cert struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||||
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
|
CryptoAlgorithm string `xorm:"varchar(100)" json:"cryptoAlgorithm"`
|
||||||
|
BitSize int `json:"bitSize"`
|
||||||
|
ExpireInYears int `json:"expireInYears"`
|
||||||
|
|
||||||
|
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||||
|
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMaskedCert(cert *Cert) *Cert {
|
||||||
|
if cert == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMaskedCerts(certs []*Cert) []*Cert {
|
||||||
|
for _, cert := range certs {
|
||||||
|
cert = GetMaskedCert(cert)
|
||||||
|
}
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCertCount(owner, field, value string) int {
|
||||||
|
session := adapter.Engine.Where("owner=?", owner)
|
||||||
|
if field != "" && value != "" {
|
||||||
|
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||||
|
}
|
||||||
|
count, err := session.Count(&Cert{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCerts(owner string) []*Cert {
|
||||||
|
certs := []*Cert{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
|
||||||
|
certs := []*Cert{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&certs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCert(owner string, name string) *Cert {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := Cert{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &cert
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCert(id string) *Cert {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getCert(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCert(id string, cert *Cert) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getCert(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddCert(cert *Cert) bool {
|
||||||
|
if cert.PublicKey == "" || cert.PrivateKey == "" {
|
||||||
|
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
||||||
|
cert.PublicKey = publicKey
|
||||||
|
cert.PrivateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.Insert(cert)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteCert(cert *Cert) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{cert.Owner, cert.Name}).Delete(&Cert{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Cert) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertByApplication(application *Application) *Cert {
|
||||||
|
if application.Cert != "" {
|
||||||
|
return getCert("admin", application.Cert)
|
||||||
|
} else {
|
||||||
|
return GetDefaultCert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultCert() *Cert {
|
||||||
|
return getCert("admin", "cert-built-in")
|
||||||
|
}
|
@ -14,12 +14,23 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "github.com/casbin/casdoor/util"
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed token_jwt_key.pem
|
||||||
|
var tokenJwtPublicKey string
|
||||||
|
|
||||||
|
//go:embed token_jwt_key.key
|
||||||
|
var tokenJwtPrivateKey string
|
||||||
|
|
||||||
func InitDb() {
|
func InitDb() {
|
||||||
initBuiltInOrganization()
|
initBuiltInOrganization()
|
||||||
initBuiltInUser()
|
initBuiltInUser()
|
||||||
initBuiltInApplication()
|
initBuiltInApplication()
|
||||||
|
initBuiltInCert()
|
||||||
initBuiltInLdap()
|
initBuiltInLdap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +101,7 @@ func initBuiltInApplication() {
|
|||||||
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||||
HomepageUrl: "https://casdoor.org",
|
HomepageUrl: "https://casdoor.org",
|
||||||
Organization: "built-in",
|
Organization: "built-in",
|
||||||
|
Cert: "cert-built-in",
|
||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{},
|
Providers: []*ProviderItem{},
|
||||||
@ -109,6 +121,28 @@ func initBuiltInApplication() {
|
|||||||
AddApplication(application)
|
AddApplication(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initBuiltInCert() {
|
||||||
|
cert := getCert("admin", "cert-built-in")
|
||||||
|
if cert != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cert = &Cert{
|
||||||
|
Owner: "admin",
|
||||||
|
Name: "cert-built-in",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: "Built-in Cert",
|
||||||
|
Scope: "JWT",
|
||||||
|
Type: "x509",
|
||||||
|
CryptoAlgorithm: "RSA",
|
||||||
|
BitSize: 4096,
|
||||||
|
ExpireInYears: 20,
|
||||||
|
PublicKey: tokenJwtPublicKey,
|
||||||
|
PrivateKey: tokenJwtPrivateKey,
|
||||||
|
}
|
||||||
|
AddCert(cert)
|
||||||
|
}
|
||||||
|
|
||||||
func initBuiltInLdap() {
|
func initBuiltInLdap() {
|
||||||
ldap := GetLdap("ldap-built-in")
|
ldap := GetLdap("ldap-built-in")
|
||||||
if ldap != nil {
|
if ldap != nil {
|
||||||
|
@ -72,13 +72,15 @@ func GetOidcDiscovery() OidcDiscovery {
|
|||||||
return oidcDiscovery
|
return oidcDiscovery
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetJSONWebKeySet() (jose.JSONWebKeySet, error) {
|
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||||
|
cert := GetDefaultCert()
|
||||||
|
|
||||||
//follows the protocol rfc 7517(draft)
|
//follows the protocol rfc 7517(draft)
|
||||||
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
||||||
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
||||||
certPEMBlock := []byte(tokenJwtPublicKey)
|
certPemBlock := []byte(cert.PublicKey)
|
||||||
certDERBlock, _ := pem.Decode(certPEMBlock)
|
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||||
x509Cert, _ := x509.ParseCertificate(certDERBlock.Bytes)
|
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
||||||
|
|
||||||
var jwk jose.JSONWebKey
|
var jwk jose.JSONWebKey
|
||||||
jwk.Key = x509Cert.PublicKey
|
jwk.Key = x509Cert.PublicKey
|
||||||
|
@ -48,6 +48,7 @@ type Token struct {
|
|||||||
type TokenWrapper struct {
|
type TokenWrapper struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
IdToken string `json:"id_token"`
|
IdToken string `json:"id_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
@ -190,6 +191,12 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
Code: "",
|
Code: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if user.IsForbidden {
|
||||||
|
return &Code{
|
||||||
|
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||||
|
Code: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
@ -286,6 +293,7 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
tokenWrapper := &TokenWrapper{
|
tokenWrapper := &TokenWrapper{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IdToken: token.AccessToken,
|
IdToken: token.AccessToken,
|
||||||
|
RefreshToken: token.RefreshToken,
|
||||||
TokenType: token.TokenType,
|
TokenType: token.TokenType,
|
||||||
ExpiresIn: token.ExpiresIn,
|
ExpiresIn: token.ExpiresIn,
|
||||||
Scope: token.Scope,
|
Scope: token.Scope,
|
||||||
@ -324,7 +332,9 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
Code: "",
|
Code: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
claims, err := ParseJwtToken(refreshToken)
|
|
||||||
|
cert := getCertByApplication(application)
|
||||||
|
claims, err := ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Code{
|
return &Code{
|
||||||
Message: "error: invalid refresh_token",
|
Message: "error: invalid refresh_token",
|
||||||
@ -339,6 +349,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}
|
}
|
||||||
// generate a new token
|
// generate a new token
|
||||||
user := getUser(application.Owner, token.User)
|
user := getUser(application.Owner, token.User)
|
||||||
|
if user.IsForbidden {
|
||||||
|
return &Code{
|
||||||
|
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||||
|
Code: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
|
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -23,12 +23,6 @@ import (
|
|||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed token_jwt_key.pem
|
|
||||||
var tokenJwtPublicKey string
|
|
||||||
|
|
||||||
//go:embed token_jwt_key.key
|
|
||||||
var tokenJwtPrivateKey string
|
|
||||||
|
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
*User
|
*User
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
@ -68,9 +62,10 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
refreshToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||||
|
|
||||||
// Use "token_jwt_key.key" as RSA private key
|
cert := getCertByApplication(application)
|
||||||
privateKey := tokenJwtPrivateKey
|
|
||||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
|
// RSA private key
|
||||||
|
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@ -84,14 +79,14 @@ func generateJwtToken(application *Application, user *User, nonce string) (strin
|
|||||||
return tokenString, refreshTokenString, err
|
return tokenString, refreshTokenString, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseJwtToken(token string) (*Claims, error) {
|
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use "token_jwt_key.pem" as RSA public key
|
// RSA public key
|
||||||
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(tokenJwtPublicKey))
|
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,14 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateRsaKeys(fileId string) {
|
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) {
|
||||||
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
|
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
|
||||||
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
||||||
|
|
||||||
bitSize := 4096
|
|
||||||
|
|
||||||
// Generate RSA key.
|
// Generate RSA key.
|
||||||
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -50,12 +45,12 @@ func generateRsaKeys(fileId string) {
|
|||||||
tml := x509.Certificate{
|
tml := x509.Certificate{
|
||||||
// you can add any attr that you need
|
// you can add any attr that you need
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().AddDate(20, 0, 0),
|
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
|
||||||
// you have to generate a different serial number each execution
|
// you have to generate a different serial number each execution
|
||||||
SerialNumber: big.NewInt(123456),
|
SerialNumber: big.NewInt(123456),
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: "Casdoor Cert",
|
CommonName: commonName,
|
||||||
Organization: []string{"Casdoor Organization"},
|
Organization: []string{organization},
|
||||||
},
|
},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
@ -70,9 +65,5 @@ func generateRsaKeys(fileId string) {
|
|||||||
Bytes: cert,
|
Bytes: cert,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Write private key to file.
|
return string(certPem), string(privateKeyPem)
|
||||||
util.WriteBytesToPath(privateKeyPem, fmt.Sprintf("%s.key", fileId))
|
|
||||||
|
|
||||||
// Write certificate (aka public key) to file.
|
|
||||||
util.WriteBytesToPath(certPem, fmt.Sprintf("%s.pem", fileId))
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,20 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
func TestGenerateRsaKeys(t *testing.T) {
|
func TestGenerateRsaKeys(t *testing.T) {
|
||||||
fileId := "token_jwt_key"
|
fileId := "token_jwt_key"
|
||||||
generateRsaKeys(fileId)
|
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
|
||||||
|
|
||||||
|
// Write certificate (aka public key) to file.
|
||||||
|
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId))
|
||||||
|
|
||||||
|
// Write private key to file.
|
||||||
|
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casdoor/util"
|
"github.com/casbin/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -407,3 +408,7 @@ func LinkUserAccount(user *User, field string, value string) bool {
|
|||||||
func (user *User) GetId() string {
|
func (user *User) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUserIdGlobalAdmin(userId string) bool {
|
||||||
|
return strings.HasPrefix(userId, "built-in/")
|
||||||
|
}
|
||||||
|
114
object/user_upload.go
Normal file
114
object/user_upload.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2021 The casbin 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 (
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
"github.com/casbin/casdoor/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUserMap(owner string) map[string]*User {
|
||||||
|
m := map[string]*User{}
|
||||||
|
|
||||||
|
users := GetUsers(owner)
|
||||||
|
for _, user := range users {
|
||||||
|
m[user.GetId()] = user
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLineItem(line *[]string, i int) string {
|
||||||
|
if i >= len(*line) {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return (*line)[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLineItemInt(line *[]string, i int) int {
|
||||||
|
s := parseLineItem(line, i)
|
||||||
|
return util.ParseInt(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLineItemBool(line *[]string, i int) bool {
|
||||||
|
return parseLineItemInt(line, i) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadUsers(owner string, fileId string) bool {
|
||||||
|
table := xlsx.ReadXlsxFile(fileId)
|
||||||
|
|
||||||
|
oldUserMap := getUserMap(owner)
|
||||||
|
newUsers := []*User{}
|
||||||
|
for _, line := range table {
|
||||||
|
if parseLineItem(&line, 0) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &User{
|
||||||
|
Owner: parseLineItem(&line, 0),
|
||||||
|
Name: parseLineItem(&line, 1),
|
||||||
|
CreatedTime: parseLineItem(&line, 2),
|
||||||
|
UpdatedTime: parseLineItem(&line, 3),
|
||||||
|
Id: parseLineItem(&line, 4),
|
||||||
|
Type: parseLineItem(&line, 5),
|
||||||
|
Password: parseLineItem(&line, 6),
|
||||||
|
PasswordSalt: parseLineItem(&line, 7),
|
||||||
|
DisplayName: parseLineItem(&line, 8),
|
||||||
|
Avatar: parseLineItem(&line, 9),
|
||||||
|
PermanentAvatar: "",
|
||||||
|
Email: parseLineItem(&line, 10),
|
||||||
|
Phone: parseLineItem(&line, 11),
|
||||||
|
Location: parseLineItem(&line, 12),
|
||||||
|
Address: []string{parseLineItem(&line, 13)},
|
||||||
|
Affiliation: parseLineItem(&line, 14),
|
||||||
|
Title: parseLineItem(&line, 15),
|
||||||
|
IdCardType: parseLineItem(&line, 16),
|
||||||
|
IdCard: parseLineItem(&line, 17),
|
||||||
|
Homepage: parseLineItem(&line, 18),
|
||||||
|
Bio: parseLineItem(&line, 19),
|
||||||
|
Tag: parseLineItem(&line, 20),
|
||||||
|
Region: parseLineItem(&line, 21),
|
||||||
|
Language: parseLineItem(&line, 22),
|
||||||
|
Gender: parseLineItem(&line, 23),
|
||||||
|
Birthday: parseLineItem(&line, 24),
|
||||||
|
Education: parseLineItem(&line, 25),
|
||||||
|
Score: parseLineItemInt(&line, 26),
|
||||||
|
Ranking: parseLineItemInt(&line, 27),
|
||||||
|
IsDefaultAvatar: false,
|
||||||
|
IsOnline: parseLineItemBool(&line, 28),
|
||||||
|
IsAdmin: parseLineItemBool(&line, 29),
|
||||||
|
IsGlobalAdmin: parseLineItemBool(&line, 30),
|
||||||
|
IsForbidden: parseLineItemBool(&line, 31),
|
||||||
|
IsDeleted: parseLineItemBool(&line, 32),
|
||||||
|
SignupApplication: parseLineItem(&line, 33),
|
||||||
|
Hash: "",
|
||||||
|
PreHash: "",
|
||||||
|
CreatedIp: parseLineItem(&line, 34),
|
||||||
|
LastSigninTime: parseLineItem(&line, 35),
|
||||||
|
LastSigninIp: parseLineItem(&line, 36),
|
||||||
|
Properties: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := oldUserMap[user.GetId()]; !ok {
|
||||||
|
newUsers = append(newUsers, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newUsers) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return AddUsersInBatch(newUsers)
|
||||||
|
}
|
@ -31,7 +31,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
// "/page?access_token=123"
|
// "/page?access_token=123"
|
||||||
accessToken := ctx.Input.Query("accessToken")
|
accessToken := ctx.Input.Query("accessToken")
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
claims, err := object.ParseJwtToken(accessToken)
|
cert := object.GetDefaultCert()
|
||||||
|
claims, err := object.ParseJwtToken(accessToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseError(ctx, "invalid JWT token")
|
responseError(ctx, "invalid JWT token")
|
||||||
return
|
return
|
||||||
@ -71,7 +72,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
// Authorization: Bearer bearerToken
|
// Authorization: Bearer bearerToken
|
||||||
bearerToken := parseBearerToken(ctx)
|
bearerToken := parseBearerToken(ctx)
|
||||||
if bearerToken != "" {
|
if bearerToken != "" {
|
||||||
claims, err := object.ParseJwtToken(bearerToken)
|
cert := object.GetDefaultCert()
|
||||||
|
claims, err := object.ParseJwtToken(bearerToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseError(ctx, err.Error())
|
responseError(ctx, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -68,6 +68,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||||
|
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||||
|
|
||||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
@ -128,6 +129,12 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||||
|
|
||||||
|
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||||
|
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||||
|
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||||
|
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||||
|
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||||
|
|
||||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
|
|
||||||
|
19
util/path.go
19
util/path.go
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +29,24 @@ func FileExist(path string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetPath(path string) string {
|
||||||
|
return filepath.Dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnsureFileFolderExists(path string) {
|
||||||
|
p := GetPath(path)
|
||||||
|
if !FileExist(p) {
|
||||||
|
err := os.MkdirAll(p, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveExt(filename string) string {
|
||||||
|
return filename[:len(filename)-len(filepath.Ext(filename))]
|
||||||
|
}
|
||||||
|
|
||||||
func UrlJoin(base string, path string) string {
|
func UrlJoin(base string, path string) string {
|
||||||
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
|
res := fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(path, "/"))
|
||||||
return res
|
return res
|
||||||
|
21
util/setting.go
Normal file
21
util/setting.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2021 The casbin 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 util
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func GetUploadXlsxPath(fileId string) string {
|
||||||
|
return fmt.Sprintf("tmpFiles/%s.xlsx", fileId)
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
"codemirror": "^5.61.1",
|
"codemirror": "^5.61.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"craco-less": "^1.17.1",
|
"craco-less": "^1.17.1",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"i18n-iso-countries": "^7.0.0",
|
"i18n-iso-countries": "^7.0.0",
|
||||||
"i18next": "^19.8.9",
|
"i18next": "^19.8.9",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
|
@ -28,7 +28,6 @@ import ProviderEditPage from "./ProviderEditPage";
|
|||||||
import ApplicationListPage from "./ApplicationListPage";
|
import ApplicationListPage from "./ApplicationListPage";
|
||||||
import ApplicationEditPage from "./ApplicationEditPage";
|
import ApplicationEditPage from "./ApplicationEditPage";
|
||||||
import ResourceListPage from "./ResourceListPage";
|
import ResourceListPage from "./ResourceListPage";
|
||||||
// import ResourceEditPage from "./ResourceEditPage";
|
|
||||||
import LdapEditPage from "./LdapEditPage";
|
import LdapEditPage from "./LdapEditPage";
|
||||||
import LdapSyncPage from "./LdapSyncPage";
|
import LdapSyncPage from "./LdapSyncPage";
|
||||||
import TokenListPage from "./TokenListPage";
|
import TokenListPage from "./TokenListPage";
|
||||||
@ -38,6 +37,8 @@ import WebhookListPage from "./WebhookListPage";
|
|||||||
import WebhookEditPage from "./WebhookEditPage";
|
import WebhookEditPage from "./WebhookEditPage";
|
||||||
import SyncerListPage from "./SyncerListPage";
|
import SyncerListPage from "./SyncerListPage";
|
||||||
import SyncerEditPage from "./SyncerEditPage";
|
import SyncerEditPage from "./SyncerEditPage";
|
||||||
|
import CertListPage from "./CertListPage";
|
||||||
|
import CertEditPage from "./CertEditPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./CustomGithubCorner";
|
||||||
@ -115,6 +116,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/webhooks' });
|
this.setState({ selectedMenuKey: '/webhooks' });
|
||||||
} else if (uri.includes('/syncers')) {
|
} else if (uri.includes('/syncers')) {
|
||||||
this.setState({ selectedMenuKey: '/syncers' });
|
this.setState({ selectedMenuKey: '/syncers' });
|
||||||
|
} else if (uri.includes('/certs')) {
|
||||||
|
this.setState({ selectedMenuKey: '/certs' });
|
||||||
} else if (uri.includes('/signup')) {
|
} else if (uri.includes('/signup')) {
|
||||||
this.setState({ selectedMenuKey: '/signup' });
|
this.setState({ selectedMenuKey: '/signup' });
|
||||||
} else if (uri.includes('/login')) {
|
} else if (uri.includes('/login')) {
|
||||||
@ -376,6 +379,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/certs">
|
||||||
|
<Link to="/certs">
|
||||||
|
{i18next.t("general:Certs")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/swagger">
|
<Menu.Item key="/swagger">
|
||||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||||
@ -440,6 +450,8 @@ class App extends Component {
|
|||||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
|
@ -21,7 +21,6 @@ import * as Setting from "./Setting";
|
|||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class ApplicationListPage extends BaseListPage {
|
class ApplicationListPage extends BaseListPage {
|
||||||
|
|
||||||
|
254
web/src/CertEditPage.js
Normal file
254
web/src/CertEditPage.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
// Copyright 2021 The casbin 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 {Button, Card, Col, Input, InputNumber, Row, Select} from 'antd';
|
||||||
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
class CertEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
certName: props.match.params.certName,
|
||||||
|
cert: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCert() {
|
||||||
|
CertBackend.getCert("admin", this.state.certName)
|
||||||
|
.then((cert) => {
|
||||||
|
this.setState({
|
||||||
|
cert: cert,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseCertField(key, value) {
|
||||||
|
if (["port"].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCertField(key, value) {
|
||||||
|
value = this.parseCertField(key, value);
|
||||||
|
|
||||||
|
let cert = this.state.cert;
|
||||||
|
cert[key] = value;
|
||||||
|
this.setState({
|
||||||
|
cert: cert,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCert() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{i18next.t("cert:Edit Cert")}
|
||||||
|
<Button onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
|
||||||
|
<Row style={{marginTop: '10px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.cert.name} onChange={e => {
|
||||||
|
this.updateCertField('name', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.cert.displayName} onChange={e => {
|
||||||
|
this.updateCertField('displayName', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.scope} onChange={(value => {
|
||||||
|
this.updateCertField('scope', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'JWT', name: 'JWT'},
|
||||||
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Type"), i18next.t("cert:Type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.type} onChange={(value => {
|
||||||
|
this.updateCertField('type', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'x509', name: 'x509'},
|
||||||
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
||||||
|
this.updateCertField('cryptoAlgorithm', value);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'RSA', name: 'RSA'},
|
||||||
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.cert.bitSize} onChange={value => {
|
||||||
|
this.updateCertField('bitSize', value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.cert.expireInYears} onChange={value => {
|
||||||
|
this.updateCertField('expireInYears', value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={9} >
|
||||||
|
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||||
|
copy(this.state.cert.publicKey);
|
||||||
|
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("cert:Copy public key")}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"});
|
||||||
|
FileSaver.saveAs(blob, "token_jwt_key.pem");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("cert:Download public key")}
|
||||||
|
</Button>
|
||||||
|
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => {
|
||||||
|
this.updateCertField('publicKey', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
<Col span={1} />
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={9} >
|
||||||
|
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
|
||||||
|
copy(this.state.cert.privateKey);
|
||||||
|
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("cert:Copy private key")}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
|
||||||
|
FileSaver.saveAs(blob, "token_jwt_key.key");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18next.t("cert:Download private key")}
|
||||||
|
</Button>
|
||||||
|
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.privateKey} onChange={e => {
|
||||||
|
this.updateCertField('privateKey', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitCertEdit(willExist) {
|
||||||
|
let cert = Setting.deepCopy(this.state.cert);
|
||||||
|
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
certName: this.state.cert.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/certs`);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/certs/${this.state.cert.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateCertField('name', this.state.certName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.cert !== null ? this.renderCert() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button size="large" onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CertEditPage;
|
230
web/src/CertListPage.js
Normal file
230
web/src/CertListPage.js
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// Copyright 2021 The casbin 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, Popconfirm, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class CertListPage extends BaseListPage {
|
||||||
|
|
||||||
|
newCert() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.certname,
|
||||||
|
name: `cert_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
displayName: `New Cert - ${randomName}`,
|
||||||
|
scope: "JWT",
|
||||||
|
type: "x509",
|
||||||
|
cryptoAlgorithm: "RSA",
|
||||||
|
bitSize: 4096,
|
||||||
|
expireInYears: 20,
|
||||||
|
publicKey: "",
|
||||||
|
privateKey: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCert() {
|
||||||
|
const newCert = this.newCert();
|
||||||
|
CertBackend.addCert(newCert)
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Cert added successfully`);
|
||||||
|
this.props.history.push(`/certs/${newCert.name}`);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Cert failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCert(i) {
|
||||||
|
CertBackend.deleteCert(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Cert deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Cert failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(certs) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '120px',
|
||||||
|
fixed: 'left',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/certs/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '180px',
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Display name"),
|
||||||
|
dataIndex: 'displayName',
|
||||||
|
key: 'displayName',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('displayName'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("cert:Scope"),
|
||||||
|
dataIndex: 'scope',
|
||||||
|
key: 'scope',
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: 'JWT', value: 'JWT'},
|
||||||
|
],
|
||||||
|
width: '110px',
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("cert:Type"),
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: 'x509', value: 'x509'},
|
||||||
|
],
|
||||||
|
width: '110px',
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("cert:Crypto algorithm"),
|
||||||
|
dataIndex: 'cryptoAlgorithm',
|
||||||
|
key: 'cryptoAlgorithm',
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: 'RSA', value: 'RSA'},
|
||||||
|
],
|
||||||
|
width: '190px',
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("cert:Bit size"),
|
||||||
|
dataIndex: 'bitSize',
|
||||||
|
key: 'bitSize',
|
||||||
|
width: '130px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('bitSize'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("cert:Expire in years"),
|
||||||
|
dataIndex: 'expireInYears',
|
||||||
|
key: 'expireInYears',
|
||||||
|
width: '170px',
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps('expireInYears'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: '',
|
||||||
|
key: 'op',
|
||||||
|
width: '170px',
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete cert: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteCert(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.pagination.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={certs} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Certs")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addCert.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
let 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 });
|
||||||
|
CertBackend.getCerts("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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CertListPage;
|
@ -119,14 +119,14 @@ class ProviderListPage extends BaseListPage {
|
|||||||
{text: 'Storage', value: 'Storage'},
|
{text: 'Storage', value: 'Storage'},
|
||||||
{text: 'SAML', value: 'SAML'},
|
{text: 'SAML', value: 'SAML'},
|
||||||
],
|
],
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Type"),
|
title: i18next.t("provider:Type"),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
width: '80px',
|
width: '110px',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
@ -152,13 +152,6 @@ class ProviderListPage extends BaseListPage {
|
|||||||
return Setting.getShortText(text);
|
return Setting.getShortText(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: 'Client secret',
|
|
||||||
// dataIndex: 'clientSecret',
|
|
||||||
// key: 'clientSecret',
|
|
||||||
// width: '150px',
|
|
||||||
// sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret),
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: i18next.t("provider:Provider URL"),
|
title: i18next.t("provider:Provider URL"),
|
||||||
dataIndex: 'providerUrl',
|
dataIndex: 'providerUrl',
|
||||||
|
@ -20,7 +20,6 @@ import * as RecordBackend from "./backend/RecordBackend";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class RecordListPage extends BaseListPage {
|
class RecordListPage extends BaseListPage {
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Organization"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: 'organization',
|
dataIndex: 'organization',
|
||||||
key: 'organization',
|
key: 'organization',
|
||||||
width: '80px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('organization'),
|
...this.getColumnSearchProps('organization'),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
@ -122,7 +121,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Method"),
|
title: i18next.t("general:Method"),
|
||||||
dataIndex: 'method',
|
dataIndex: 'method',
|
||||||
key: 'method',
|
key: 'method',
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
|
@ -21,7 +21,6 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class ResourceListPage extends BaseListPage {
|
class ResourceListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -74,6 +74,7 @@ class SignupTable extends React.Component {
|
|||||||
{id: 'Display name', name: 'Display name'},
|
{id: 'Display name', name: 'Display name'},
|
||||||
{id: 'Affiliation', name: 'Affiliation'},
|
{id: 'Affiliation', name: 'Affiliation'},
|
||||||
{id: 'Country/Region', name: 'Country/Region'},
|
{id: 'Country/Region', name: 'Country/Region'},
|
||||||
|
{id: 'ID card', name: 'ID card'},
|
||||||
{id: 'Email', name: 'Email'},
|
{id: 'Email', name: 'Email'},
|
||||||
{id: 'Password', name: 'Password'},
|
{id: 'Password', name: 'Password'},
|
||||||
{id: 'Confirm password', name: 'Confirm password'},
|
{id: 'Confirm password', name: 'Confirm password'},
|
||||||
|
@ -20,7 +20,6 @@ import * as Setting from "./Setting";
|
|||||||
import * as SyncerBackend from "./backend/SyncerBackend";
|
import * as SyncerBackend from "./backend/SyncerBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class SyncerListPage extends BaseListPage {
|
class SyncerListPage extends BaseListPage {
|
||||||
|
|
||||||
@ -112,7 +111,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Created time"),
|
title: i18next.t("general:Created time"),
|
||||||
dataIndex: 'createdTime',
|
dataIndex: 'createdTime',
|
||||||
key: 'createdTime',
|
key: 'createdTime',
|
||||||
width: '180px',
|
width: '160px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return Setting.getFormattedDate(text);
|
return Setting.getFormattedDate(text);
|
||||||
@ -187,7 +186,7 @@ class SyncerListPage extends BaseListPage {
|
|||||||
title: i18next.t("syncer:Sync interval"),
|
title: i18next.t("syncer:Sync interval"),
|
||||||
dataIndex: 'syncInterval',
|
dataIndex: 'syncInterval',
|
||||||
key: 'syncInterval',
|
key: 'syncInterval',
|
||||||
width: '120px',
|
width: '130px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('syncInterval'),
|
...this.getColumnSearchProps('syncInterval'),
|
||||||
},
|
},
|
||||||
|
@ -176,7 +176,7 @@ class TokenListPage extends BaseListPage {
|
|||||||
title: i18next.t("token:Scope"),
|
title: i18next.t("token:Scope"),
|
||||||
dataIndex: 'scope',
|
dataIndex: 'scope',
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('scope'),
|
...this.getColumnSearchProps('scope'),
|
||||||
},
|
},
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from 'antd';
|
import {Button, Popconfirm, Switch, Table, Upload} from 'antd';
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
|
||||||
|
|
||||||
class UserListPage extends BaseListPage {
|
class UserListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -93,6 +93,43 @@ class UserListPage extends BaseListPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadFile(info) {
|
||||||
|
const { status, response: res } = info.file;
|
||||||
|
if (status === 'done') {
|
||||||
|
if (res.status === 'ok') {
|
||||||
|
Setting.showMessage("success", `Users uploaded successfully, refreshing the page`);
|
||||||
|
|
||||||
|
const { pagination } = this.state;
|
||||||
|
this.fetch({ pagination });
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
|
||||||
|
}
|
||||||
|
} else if (status === 'error') {
|
||||||
|
Setting.showMessage("error", `File failed to upload`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUpload() {
|
||||||
|
const props = {
|
||||||
|
name: 'file',
|
||||||
|
accept: '.xlsx',
|
||||||
|
method: 'post',
|
||||||
|
action: `${Setting.ServerUrl}/api/upload-users`,
|
||||||
|
withCredentials: true,
|
||||||
|
onChange: (info) => {
|
||||||
|
this.uploadFile(info);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Upload {...props}>
|
||||||
|
<Button type="primary" size="small">
|
||||||
|
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
renderTable(users) {
|
renderTable(users) {
|
||||||
// transfer country code to name based on selected language
|
// transfer country code to name based on selected language
|
||||||
var countries = require("i18n-iso-countries");
|
var countries = require("i18n-iso-countries");
|
||||||
@ -138,7 +175,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Name"),
|
title: i18next.t("general:Name"),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: (Setting.isMobile()) ? "80px" : "100px",
|
width: (Setting.isMobile()) ? "80px" : "110px",
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('name'),
|
...this.getColumnSearchProps('name'),
|
||||||
@ -164,7 +201,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Display name"),
|
title: i18next.t("general:Display name"),
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
key: 'displayName',
|
key: 'displayName',
|
||||||
width: '100px',
|
// width: '100px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('displayName'),
|
...this.getColumnSearchProps('displayName'),
|
||||||
},
|
},
|
||||||
@ -215,7 +252,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("user:Affiliation"),
|
title: i18next.t("user:Affiliation"),
|
||||||
dataIndex: 'affiliation',
|
dataIndex: 'affiliation',
|
||||||
key: 'affiliation',
|
key: 'affiliation',
|
||||||
width: '120px',
|
width: '140px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('affiliation'),
|
...this.getColumnSearchProps('affiliation'),
|
||||||
},
|
},
|
||||||
@ -223,7 +260,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("user:Country/Region"),
|
title: i18next.t("user:Country/Region"),
|
||||||
dataIndex: 'region',
|
dataIndex: 'region',
|
||||||
key: 'region',
|
key: 'region',
|
||||||
width: '120px',
|
width: '140px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('region'),
|
...this.getColumnSearchProps('region'),
|
||||||
},
|
},
|
||||||
@ -231,7 +268,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("user:Tag"),
|
title: i18next.t("user:Tag"),
|
||||||
dataIndex: 'tag',
|
dataIndex: 'tag',
|
||||||
key: 'tag',
|
key: 'tag',
|
||||||
width: '100px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('tag'),
|
...this.getColumnSearchProps('tag'),
|
||||||
},
|
},
|
||||||
@ -251,7 +288,7 @@ class UserListPage extends BaseListPage {
|
|||||||
title: i18next.t("user:Is global admin"),
|
title: i18next.t("user:Is global admin"),
|
||||||
dataIndex: 'isGlobalAdmin',
|
dataIndex: 'isGlobalAdmin',
|
||||||
key: 'isGlobalAdmin',
|
key: 'isGlobalAdmin',
|
||||||
width: '110px',
|
width: '140px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
@ -318,7 +355,10 @@ class UserListPage extends BaseListPage {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Users")}
|
{i18next.t("general:Users")}
|
||||||
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
{
|
||||||
|
this.renderUpload()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
@ -72,7 +72,7 @@ class WebhookListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Organization"),
|
title: i18next.t("general:Organization"),
|
||||||
dataIndex: 'organization',
|
dataIndex: 'organization',
|
||||||
key: 'organization',
|
key: 'organization',
|
||||||
width: '80px',
|
width: '110px',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps('organization'),
|
...this.getColumnSearchProps('organization'),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
|
@ -448,6 +448,8 @@ class LoginPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span style={{float: "left"}}>
|
<span style={{float: "left"}}>
|
||||||
|
{
|
||||||
|
!application.enableCodeSignin ? null : (
|
||||||
<a onClick={() => {
|
<a onClick={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isCodeSignin: !this.state.isCodeSignin,
|
isCodeSignin: !this.state.isCodeSignin,
|
||||||
@ -455,6 +457,8 @@ class LoginPage extends React.Component {
|
|||||||
}}>
|
}}>
|
||||||
{this.state.isCodeSignin ? i18next.t("login:Sign in with password") : i18next.t("login:Sign in with code")}
|
{this.state.isCodeSignin ? i18next.t("login:Sign in with password") : i18next.t("login:Sign in with code")}
|
||||||
</a>
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
<span style={{float: "right"}}>
|
<span style={{float: "right"}}>
|
||||||
{i18next.t("login:No account?")}
|
{i18next.t("login:No account?")}
|
||||||
|
@ -225,6 +225,28 @@ class SignupPage extends React.Component {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
|
} else if (signupItem.name === "ID card") {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
name="idCard"
|
||||||
|
key="idCard"
|
||||||
|
label={i18next.t("user:ID card")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: required,
|
||||||
|
message: i18next.t("signup:Please input your ID card number!"),
|
||||||
|
whitespace: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: required,
|
||||||
|
pattern: new RegExp(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9X]$/, "g"),
|
||||||
|
message: i18next.t("signup:Please input the correct ID card number!"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
} else if (signupItem.name === "Country/Region") {
|
} else if (signupItem.name === "Country/Region") {
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
56
web/src/backend/CertBackend.js
Normal file
56
web/src/backend/CertBackend.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2021 The casbin 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 * as Setting from "../Setting";
|
||||||
|
|
||||||
|
export function getCerts(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-certs?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCert(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-cert?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCert(owner, name, cert) {
|
||||||
|
let newCert = Setting.deepCopy(cert);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/update-cert?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newCert),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCert(cert) {
|
||||||
|
let newCert = Setting.deepCopy(cert);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/add-cert`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newCert),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteCert(cert) {
|
||||||
|
let newCert = Setting.deepCopy(cert);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/delete-cert`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newCert),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar to show to others",
|
"Avatar - Tooltip": "Avatar to show to others",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar - Tooltip",
|
"Avatar - Tooltip": "Avatar - Tooltip",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar to show to others",
|
"Avatar - Tooltip": "Avatar to show to others",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar to show to others",
|
"Avatar - Tooltip": "Avatar to show to others",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar to show to others",
|
"Avatar - Tooltip": "Avatar to show to others",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "Token format - Tooltip"
|
"Token format - Tooltip": "Token format - Tooltip"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "Bit size",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "Copy private key",
|
||||||
|
"Copy public key": "Copy public key",
|
||||||
|
"Crypto algorithm": "Crypto algorithm",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "Download private key",
|
||||||
|
"Download public key": "Download public key",
|
||||||
|
"Edit Cert": "Edit Cert",
|
||||||
|
"Expire in years": "Expire in years",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "Private key",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "Private key copied to clipboard successfully",
|
||||||
|
"Public key": "Public key",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "Public key copied to clipboard successfully",
|
||||||
|
"Scope": "Scope",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "Type",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "Code You Received",
|
"Code You Received": "Code You Received",
|
||||||
"Email code": "Email code",
|
"Email code": "Email code",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "Avatar to show to others",
|
"Avatar - Tooltip": "Avatar to show to others",
|
||||||
"Back Home": "Back Home",
|
"Back Home": "Back Home",
|
||||||
"Captcha": "Captcha",
|
"Captcha": "Captcha",
|
||||||
|
"Certs": "Certs",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
"Please confirm your password!": "Please confirm your password!",
|
"Please confirm your password!": "Please confirm your password!",
|
||||||
|
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||||
"Please input your Email!": "Please input your Email!",
|
"Please input your Email!": "Please input your Email!",
|
||||||
|
"Please input your ID card number!": "Please input your ID card number!",
|
||||||
"Please input your address!": "Please input your address!",
|
"Please input your address!": "Please input your address!",
|
||||||
"Please input your affiliation!": "Please input your affiliation!",
|
"Please input your affiliation!": "Please input your affiliation!",
|
||||||
"Please input your display name!": "Please input your display name!",
|
"Please input your display name!": "Please input your display name!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "Empty input!",
|
"Empty input!": "Empty input!",
|
||||||
"Homepage": "Homepage",
|
"Homepage": "Homepage",
|
||||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||||
|
"ID card": "ID card",
|
||||||
"Input your email": "Input your email",
|
"Input your email": "Input your email",
|
||||||
"Input your phone number": "Input your phone number",
|
"Input your phone number": "Input your phone number",
|
||||||
"Is admin": "Is admin",
|
"Is admin": "Is admin",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "Title - Tooltip",
|
"Title - Tooltip": "Title - Tooltip",
|
||||||
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
"Two passwords you typed do not match.": "Two passwords you typed do not match.",
|
||||||
"Unlink": "Unlink",
|
"Unlink": "Unlink",
|
||||||
|
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||||
"Upload a photo": "Upload a photo",
|
"Upload a photo": "Upload a photo",
|
||||||
"input password": "input password"
|
"input password": "input password"
|
||||||
},
|
},
|
||||||
|
@ -33,6 +33,29 @@
|
|||||||
"Token format": "Access Token格式",
|
"Token format": "Access Token格式",
|
||||||
"Token format - Tooltip": "Access Token格式"
|
"Token format - Tooltip": "Access Token格式"
|
||||||
},
|
},
|
||||||
|
"cert": {
|
||||||
|
"Bit size": "位大小",
|
||||||
|
"Bit size - Tooltip": "Bit size - Tooltip",
|
||||||
|
"Copy private key": "复制私钥",
|
||||||
|
"Copy public key": "复制公钥",
|
||||||
|
"Crypto algorithm": "加密算法",
|
||||||
|
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
|
||||||
|
"Download private key": "下载私钥",
|
||||||
|
"Download public key": "下载公钥",
|
||||||
|
"Edit Cert": "修改证书",
|
||||||
|
"Expire in years": "有效期(年)",
|
||||||
|
"Expire in years - Tooltip": "Expire in years - Tooltip",
|
||||||
|
"Private key": "私钥",
|
||||||
|
"Private key - Tooltip": "Private key - Tooltip",
|
||||||
|
"Private key copied to clipboard successfully": "私钥已成功复制到剪贴板",
|
||||||
|
"Public key": "公钥",
|
||||||
|
"Public key - Tooltip": "Public key - Tooltip",
|
||||||
|
"Public key copied to clipboard successfully": "公钥已成功复制到剪贴板",
|
||||||
|
"Scope": "用途",
|
||||||
|
"Scope - Tooltip": "Scope - Tooltip",
|
||||||
|
"Type": "类型",
|
||||||
|
"Type - Tooltip": "Type - Tooltip"
|
||||||
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"Code You Received": "验证码",
|
"Code You Received": "验证码",
|
||||||
"Email code": "邮箱验证码",
|
"Email code": "邮箱验证码",
|
||||||
@ -69,6 +92,7 @@
|
|||||||
"Avatar - Tooltip": "向其他人展示的头像",
|
"Avatar - Tooltip": "向其他人展示的头像",
|
||||||
"Back Home": "返回到首页",
|
"Back Home": "返回到首页",
|
||||||
"Captcha": "人机验证码",
|
"Captcha": "人机验证码",
|
||||||
|
"Certs": "证书",
|
||||||
"Client IP": "客户端IP",
|
"Client IP": "客户端IP",
|
||||||
"Created time": "创建时间",
|
"Created time": "创建时间",
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
@ -246,7 +270,7 @@
|
|||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
"Parse": "Parse",
|
"Parse": "Parse",
|
||||||
"Parse Metadata successfully": "Parse Metadata successfully",
|
"Parse Metadata successfully": "Parse Metadata successfully",
|
||||||
"Port": "端口号",
|
"Port": "端口",
|
||||||
"Port - Tooltip": "端口号",
|
"Port - Tooltip": "端口号",
|
||||||
"Provider URL": "提供商URL",
|
"Provider URL": "提供商URL",
|
||||||
"Provider URL - Tooltip": "提供商URL",
|
"Provider URL - Tooltip": "提供商URL",
|
||||||
@ -292,8 +316,8 @@
|
|||||||
"Application": "应用",
|
"Application": "应用",
|
||||||
"Copy Link": "复制链接",
|
"Copy Link": "复制链接",
|
||||||
"File name": "文件名",
|
"File name": "文件名",
|
||||||
"File size": "文件大小",
|
"File size": "大小",
|
||||||
"Format": "文件格式",
|
"Format": "格式",
|
||||||
"Link copied to clipboard successfully": "链接已成功复制到剪贴板",
|
"Link copied to clipboard successfully": "链接已成功复制到剪贴板",
|
||||||
"Parent": "属主",
|
"Parent": "属主",
|
||||||
"Tag": "标签",
|
"Tag": "标签",
|
||||||
@ -309,7 +333,9 @@
|
|||||||
"Please accept the agreement!": "请先同意用户协议!",
|
"Please accept the agreement!": "请先同意用户协议!",
|
||||||
"Please click the below button to sign in": "请点击下方按钮登录",
|
"Please click the below button to sign in": "请点击下方按钮登录",
|
||||||
"Please confirm your password!": "请再次确认您的密码!",
|
"Please confirm your password!": "请再次确认您的密码!",
|
||||||
|
"Please input the correct ID card number!": "请输入正确的身份证号!",
|
||||||
"Please input your Email!": "请输入您的电子邮箱!",
|
"Please input your Email!": "请输入您的电子邮箱!",
|
||||||
|
"Please input your ID card number!": "请输入您的身份证号!",
|
||||||
"Please input your address!": "请输入您的地址!",
|
"Please input your address!": "请输入您的地址!",
|
||||||
"Please input your affiliation!": "请输入您所在的工作单位!",
|
"Please input your affiliation!": "请输入您所在的工作单位!",
|
||||||
"Please input your display name!": "请输入您的显示名称!",
|
"Please input your display name!": "请输入您的显示名称!",
|
||||||
@ -375,6 +401,7 @@
|
|||||||
"Empty input!": "输入为空!",
|
"Empty input!": "输入为空!",
|
||||||
"Homepage": "个人主页",
|
"Homepage": "个人主页",
|
||||||
"Homepage - Tooltip": "个人主页链接",
|
"Homepage - Tooltip": "个人主页链接",
|
||||||
|
"ID card": "身份证号",
|
||||||
"Input your email": "请输入邮箱",
|
"Input your email": "请输入邮箱",
|
||||||
"Input your phone number": "输入手机号",
|
"Input your phone number": "输入手机号",
|
||||||
"Is admin": "是管理员",
|
"Is admin": "是管理员",
|
||||||
@ -410,6 +437,7 @@
|
|||||||
"Title - Tooltip": "在单位/公司的职务",
|
"Title - Tooltip": "在单位/公司的职务",
|
||||||
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
|
"Two passwords you typed do not match.": "两次输入的密码不匹配。",
|
||||||
"Unlink": "解绑",
|
"Unlink": "解绑",
|
||||||
|
"Upload (.xlsx)": "上传(.xlsx)",
|
||||||
"Upload a photo": "上传头像",
|
"Upload a photo": "上传头像",
|
||||||
"input password": "输入密码"
|
"input password": "输入密码"
|
||||||
},
|
},
|
||||||
|
@ -5190,6 +5190,11 @@ file-loader@6.1.1:
|
|||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
|
|
||||||
|
file-saver@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||||
|
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||||
|
|
||||||
file-uri-to-path@1.0.0:
|
file-uri-to-path@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||||
|
43
xlsx/xlsx.go
Normal file
43
xlsx/xlsx.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2021 The casbin 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 xlsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casbin/casdoor/util"
|
||||||
|
"github.com/tealeg/xlsx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadXlsxFile(fileId string) [][]string {
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
file, err := xlsx.OpenFile(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := [][]string{}
|
||||||
|
for _, sheet := range file.Sheets {
|
||||||
|
for _, row := range sheet.Rows {
|
||||||
|
line := []string{}
|
||||||
|
for _, cell := range row.Cells {
|
||||||
|
text := cell.String()
|
||||||
|
line = append(line, text)
|
||||||
|
}
|
||||||
|
res = append(res, line)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
22
xlsx/xlsx_test.go
Normal file
22
xlsx/xlsx_test.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2021 The casbin 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 xlsx
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestReadSheet(t *testing.T) {
|
||||||
|
ticket := ReadXlsxFile("../../tmpFiles/example")
|
||||||
|
println(ticket)
|
||||||
|
}
|
Reference in New Issue
Block a user