Compare commits

...

37 Commits

Author SHA1 Message Date
Denis Plynskiy
0d48da24dc feat: fix wrong rowKey for tables (#2070) 2023-07-12 21:12:36 +08:00
Yaodong Yu
de9eeaa1ef fix: init groups modify rule with admin (#2054) 2023-07-11 09:49:49 +08:00
Baihhh
ae6e35ee73 feat: fix bug that the password input disappears in login window (#2051)
Signed-off-by: baihhh <2542274498@qq.com>
2023-07-08 23:46:31 +08:00
Yaodong Yu
a58df645bf fix: fix state after mfa is enabled (#2050) 2023-07-08 22:35:31 +08:00
WintBit
68417a2d7a fix: /api/upload-resource panics when parsing file_type (#2046) 2023-07-07 16:18:25 +08:00
WintBit
9511fae9d9 docs: add swagger docs for Resource-API (#2044)
swagger files are all auto generated.
2023-07-07 14:28:10 +08:00
Yaodong Yu
347d3d2b53 feat: fix bugs in MFA (#2033)
* fix: prompt mfa binding

* fix: clean session when leave promptpage

* fix: css

* fix: force enable mfa

* fix: add prompt rule

* fix: refactor directory structure

* fix: prompt notification

* fix: fix some bug and clean code

* fix: rebase

* fix: improve notification

* fix: i18n

* fix: router

* fix: prompt

* fix: remove localStorage
2023-07-07 12:30:07 +08:00
Gucheng Wang
6edfc08b28 Refactor the code 2023-07-07 00:13:05 +08:00
Baihhh
bc1c4d32f0 feat: user can upload ID card info (#2040)
* feat:user can upload ID card(#1999)

Signed-off-by: baihhh <2542274498@qq.com>

* feat: user can upload ID card, add diff languages

Signed-off-by: baihhh <2542274498@qq.com>

---------

Signed-off-by: baihhh <2542274498@qq.com>
2023-07-06 20:36:32 +08:00
YunShu
96250aa70a docs: replace gitter links with discord (#2041) 2023-07-06 18:16:16 +08:00
Yaodong Yu
3d4ca1adb1 feat: support custom user mapping (#2029)
* feat: support custom user mapping

* fix: parse id to string

* Update data.json

* Update data.json

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-05 20:35:02 +08:00
Yang Luo
ba97458edd feat: fix StaticFilter issue 2023-07-05 17:54:39 +08:00
Yang Luo
855259c6e7 feat: improve getOriginFromHost() for local machine name 2023-07-05 09:51:08 +08:00
June
28297e06f7 feat: IntrospectToken return the right Jti (JWT ID instead of User Id) (#2035) 2023-07-03 19:01:06 +08:00
Yang Luo
f3aed0b6a8 Fix null panic in GetOrganizationByUser() 2023-07-03 14:56:14 +08:00
haiwu
35e1f8538e feat: fix panic when url.Parse() fails to parse URL (#2034) 2023-07-03 12:35:22 +08:00
Yang Luo
30a14ff54a Fix null issue in getDefaultApplication() 2023-07-02 09:44:48 +08:00
Yang Luo
1ab7a54133 Add DefaultApplication to conf 2023-07-02 09:15:22 +08:00
Yang Luo
0e2dad35f3 Improve OrganizationSelect width 2023-06-30 02:04:44 +08:00
Yang Luo
d31077a510 Remove conf values 2023-06-30 01:38:48 +08:00
Denis Plynskiy
eee9b8b9fe feat: add organization context select box for admin (#2013)
* feat: organization as context

* feat: organization as context with backend filtration

* Update app.conf

* update app.conf and hide organization select for mobile.

---------

Co-authored-by: dplynsky <dplynsky@ptsecurity.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-30 01:32:34 +08:00
Baihhh
91cb5f393a fix: fix Swagger docs page (#2025)
Signed-off-by: baihhh <2542274498@qq.com>
2023-06-30 00:48:39 +08:00
haiwu
807aea5ec7 feat: add tags to application (#2027)
* feat: add tags to application

* fix: fix for merge master

* feat: update i18n(backend&frontend) for application tags
2023-06-30 00:04:12 +08:00
Yaodong Yu
1c42b6e395 fix: refactor the idp and regex code (#2030)
* refactor: validate util and idp

* chore: clean code

* chore: clean code
2023-06-29 21:44:14 +08:00
Ilya Sulimanov
49a73f8138 fix: getOrganization without pagination for global admin (#2028)
* fix: getOrganization without pagination for global admin return only built-in org

* fix gofumpt
2023-06-29 18:56:19 +08:00
Yang Luo
55784c68a3 Fix bug in /get-organizations API for org admin 2023-06-28 09:19:39 +08:00
June
8080b10b3b feat: show code signin page with password disabled (#2021) 2023-06-28 00:38:48 +08:00
Trần Thanh Tịnh
cd7589775c feat: replace all panic by response err (#1993)
* fix: missing return after response error

* feat: handle error in frontend

* feat: disable loading and catch org edit error

* chore: i18 for error message

* chore: remove break line

* feat: application catching error
2023-06-27 21:33:47 +08:00
Yaodong Yu
0a8c2a35fe feat: add TOTP multi-factor authentication (#2014)
* feat: add totp multi-factor authentication

* feat: add license

* feat:i18n and update yarn.lock

* feat:i18n

* fix: i18n
2023-06-24 18:39:54 +08:00
XDTD
d1e734e4ce fix: set the default value of user.Groups for syncer (#2016)
fix: set the default value of user.Groups for syncer
2023-06-24 18:29:50 +08:00
XDTD
68f032b54d fix: add isReadOnly for syncer (#2015)
* feat: add read only mod for syncer

* feat: change readOnlyEnable to isReadOnly
2023-06-24 17:56:41 +08:00
June
1780620ef4 feat: handle error when permission not found (#2012) 2023-06-24 00:30:43 +08:00
Yang Luo
5c968ed1ce Fix avatar cannot show issue 2023-06-23 15:53:41 +08:00
Yang Luo
4016fc0f65 Add EnableChatPages to Conf 2023-06-23 11:35:34 +08:00
June
463b3ad976 fix: refactor and optimize Enforce() API (#2009) 2023-06-22 17:45:24 +08:00
Yang Luo
b817a55f9f Fix error handling in SetPassword() 2023-06-22 14:51:56 +08:00
June
2c2ddfbb92 feat: optimize batch-enforce (#1997) 2023-06-22 14:40:09 +08:00
152 changed files with 5553 additions and 2764 deletions

View File

@@ -37,8 +37,8 @@
<a href="https://crowdin.com/project/casdoor-site"> <a href="https://crowdin.com/project/casdoor-site">
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg"> <img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
</a> </a>
<a href="https://gitter.im/casbin/casdoor"> <a href="https://discord.gg/5rPsrAzK7S">
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg"> <img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
</a> </a>
</p> </p>
@@ -71,7 +71,7 @@ https://casdoor.org/docs/category/integrations
## How to contact? ## How to contact?
- Gitter: https://gitter.im/casbin/casdoor - Discord: https://discord.gg/5rPsrAzK7S
- Forum: https://forum.casbin.com - Forum: https://forum.casbin.com
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e - Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e

View File

@@ -50,7 +50,8 @@ func (c *ApiController) GetApplications() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplications(applications, userId) c.Data["json"] = object.GetMaskedApplications(applications, userId)
@@ -59,13 +60,15 @@ func (c *ApiController) GetApplications() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetApplicationCount(owner, field, value) count, err := object.GetApplicationCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
applications := object.GetMaskedApplications(app, userId) applications := object.GetMaskedApplications(app, userId)
@@ -85,7 +88,8 @@ func (c *ApiController) GetApplication() {
id := c.Input().Get("id") id := c.Input().Get("id")
app, err := object.GetApplication(id) app, err := object.GetApplication(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplication(app, userId) c.Data["json"] = object.GetMaskedApplication(app, userId)
@@ -104,7 +108,8 @@ func (c *ApiController) GetUserApplication() {
id := c.Input().Get("id") id := c.Input().Get("id")
user, err := object.GetUser(id) user, err := object.GetUser(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if user == nil { if user == nil {
@@ -114,7 +119,8 @@ func (c *ApiController) GetUserApplication() {
app, err := object.GetApplicationByUser(user) app, err := object.GetApplicationByUser(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplication(app, userId) c.Data["json"] = object.GetMaskedApplication(app, userId)
@@ -147,7 +153,8 @@ func (c *ApiController) GetOrganizationApplications() {
if limit == "" || page == "" { if limit == "" || page == "" {
applications, err := object.GetOrganizationApplications(owner, organization) applications, err := object.GetOrganizationApplications(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedApplications(applications, userId) c.Data["json"] = object.GetMaskedApplications(applications, userId)

View File

@@ -69,10 +69,13 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
return return
} }
if form.Password != "" && user.IsMfaEnabled() { // check user's tag
c.setMfaSessionData(&object.MfaSessionData{UserId: userId}) if !user.IsGlobalAdmin && !user.IsAdmin && len(application.Tags) > 0 {
resp = &Response{Status: object.NextMfa, Data: user.GetPreferredMfaProps(true)} // only users with the tag that is listed in the application tags can login
return if !util.InSlice(application.Tags, user.Tag) {
c.ResponseError(fmt.Sprintf(c.T("auth:User's tag: %s is not listed in the application's tags"), user.Tag))
return
}
} }
if form.Type == ResponseTypeLogin { if form.Type == ResponseTypeLogin {
@@ -238,7 +241,7 @@ func isProxyProviderType(providerType string) bool {
// @Param code_challenge_method query string false code_challenge_method // @Param code_challenge_method query string false code_challenge_method
// @Param code_challenge query string false code_challenge // @Param code_challenge query string false code_challenge
// @Param form body controllers.AuthForm true "Login information" // @Param form body controllers.AuthForm true "Login information"
// @Success 200 {object} Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /login [post] // @router /login [post]
func (c *ApiController) Login() { func (c *ApiController) Login() {
resp := &Response{} resp := &Response{}
@@ -344,17 +347,26 @@ func (c *ApiController) Login() {
return return
} }
resp = c.HandleLoggedIn(application, user, &authForm)
organization, err := object.GetOrganizationByUser(user) organization, err := object.GetOrganizationByUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
} }
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() { if object.IsNeedPromptMfa(organization, user) {
resp.Msg = object.RequiredMfa // The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
c.ResponseOk(object.RequiredMfa)
return
} }
if user.IsMfaEnabled() {
c.setMfaUserSession(user.GetId())
c.ResponseOk(object.NextMfa, user.GetPreferredMfaProps(true))
return
}
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
@@ -407,15 +419,8 @@ func (c *ApiController) Login() {
} }
} else if provider.Category == "OAuth" { } else if provider.Category == "OAuth" {
// OAuth // OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
clientId := provider.ClientId idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
clientSecret := provider.ClientSecret
if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") {
clientId = provider.ClientId2
clientSecret = provider.ClientSecret2
}
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, authForm.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
if idProvider == nil { if idProvider == nil {
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
return return
@@ -647,13 +652,16 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked} resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
} }
} }
} else if c.getMfaSessionData() != nil { } else if c.getMfaUserSession() != "" {
mfaSession := c.getMfaSessionData() user, err := object.GetUser(c.getMfaUserSession())
user, err := object.GetUser(mfaSession.UserId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if user == nil {
c.ResponseError("expired user session")
return
}
if authForm.Passcode != "" { if authForm.Passcode != "" {
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false)) mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
@@ -667,13 +675,15 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
} } else if authForm.RecoveryCode != "" {
if authForm.RecoveryCode != "" {
err = object.MfaRecover(user, authForm.RecoveryCode) err = object.MfaRecover(user, authForm.RecoveryCode)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
} else {
c.ResponseError("missing passcode or recovery code")
return
} }
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
@@ -688,6 +698,7 @@ func (c *ApiController) Login() {
} }
resp = c.HandleLoggedIn(application, user, &authForm) resp = c.HandleLoggedIn(application, user, &authForm)
c.setMfaUserSession("")
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
@@ -756,7 +767,8 @@ func (c *ApiController) HandleSamlLogin() {
func (c *ApiController) HandleOfficialAccountEvent() { func (c *ApiController) HandleOfficialAccountEvent() {
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body) respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
var data struct { var data struct {
@@ -766,7 +778,8 @@ func (c *ApiController) HandleOfficialAccountEvent() {
} }
err = xml.Unmarshal(respBytes, &data) err = xml.Unmarshal(respBytes, &data)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
lock.Lock() lock.Lock()

View File

@@ -79,7 +79,8 @@ func (c *ApiController) getCurrentUser() *object.User {
} else { } else {
user, err = object.GetUser(userId) user, err = object.GetUser(userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil
} }
} }
return user return user
@@ -112,7 +113,8 @@ func (c *ApiController) GetSessionApplication() *object.Application {
} }
application, err := object.GetApplicationByClientId(clientId.(string)) application, err := object.GetApplicationByClientId(clientId.(string))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil
} }
return application return application
@@ -176,24 +178,16 @@ func (c *ApiController) SetSessionData(s *SessionData) {
c.SetSession("SessionData", util.StructToJson(s)) c.SetSession("SessionData", util.StructToJson(s))
} }
func (c *ApiController) setMfaSessionData(data *object.MfaSessionData) { func (c *ApiController) setMfaUserSession(userId string) {
if data == nil { c.SetSession(object.MfaSessionUserId, userId)
c.SetSession(object.MfaSessionUserId, nil)
return
}
c.SetSession(object.MfaSessionUserId, data.UserId)
} }
func (c *ApiController) getMfaSessionData() *object.MfaSessionData { func (c *ApiController) getMfaUserSession() string {
userId := c.GetSession(object.MfaSessionUserId) userId := c.Ctx.Input.CruSession.Get(object.MfaSessionUserId)
if userId == nil { if userId == nil {
return nil return ""
} }
return userId.(string)
data := &object.MfaSessionData{
UserId: userId.(string),
}
return data
} }
func (c *ApiController) setExpireForSession() { func (c *ApiController) setExpireForSession() {

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetCerts() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner)) maskedCerts, err := object.GetMaskedCerts(object.GetCerts(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedCerts c.Data["json"] = maskedCerts
@@ -50,13 +51,15 @@ func (c *ApiController) GetCerts() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetCertCount(owner, field, value) count, err := object.GetCertCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(certs, paginator.Nums()) c.ResponseOk(certs, paginator.Nums())
@@ -80,7 +83,8 @@ func (c *ApiController) GetGlobleCerts() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts()) maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedCerts c.Data["json"] = maskedCerts
@@ -89,13 +93,15 @@ func (c *ApiController) GetGlobleCerts() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetGlobalCertsCount(field, value) count, err := object.GetGlobalCertsCount(field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder)) certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(certs, paginator.Nums()) c.ResponseOk(certs, paginator.Nums())
@@ -113,7 +119,8 @@ func (c *ApiController) GetCert() {
id := c.Input().Get("id") id := c.Input().Get("id")
cert, err := object.GetCert(id) cert, err := object.GetCert(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedCert(cert) c.Data["json"] = object.GetMaskedCert(cert)

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetChats() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedChats, err := object.GetMaskedChats(object.GetChats(owner)) maskedChats, err := object.GetMaskedChats(object.GetChats(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedChats c.Data["json"] = maskedChats
@@ -77,7 +78,8 @@ func (c *ApiController) GetChat() {
maskedChat, err := object.GetMaskedChat(object.GetChat(id)) maskedChat, err := object.GetMaskedChat(object.GetChat(id))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedChat c.Data["json"] = maskedChat

View File

@@ -44,14 +44,26 @@ func (c *ApiController) Enforce() {
} }
if permissionId != "" { if permissionId != "" {
enforceResult, err := object.Enforce(permissionId, &request) permission, err := object.GetPermission(permissionId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res := []bool{} res := []bool{}
res = append(res, enforceResult)
if permission == nil {
res = append(res, false)
} else {
enforceResult, err := object.Enforce(permission, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res) c.ResponseOk(res)
return return
} }
@@ -76,8 +88,16 @@ func (c *ApiController) Enforce() {
} }
res := []bool{} res := []bool{}
for _, permission := range permissions {
enforceResult, err := object.Enforce(permission.GetId(), &request) listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -85,6 +105,7 @@ func (c *ApiController) Enforce() {
res = append(res, enforceResult) res = append(res, enforceResult)
} }
c.ResponseOk(res) c.ResponseOk(res)
} }
@@ -109,14 +130,32 @@ func (c *ApiController) BatchEnforce() {
} }
if permissionId != "" { if permissionId != "" {
enforceResult, err := object.BatchEnforce(permissionId, &requests) permission, err := object.GetPermission(permissionId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res := [][]bool{} res := [][]bool{}
res = append(res, enforceResult)
if permission == nil {
l := len(requests)
resRequest := make([]bool, l)
for i := 0; i < l; i++ {
resRequest[i] = false
}
res = append(res, resRequest)
} else {
enforceResult, err := object.BatchEnforce(permission, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
}
c.ResponseOk(res) c.ResponseOk(res)
return return
} }
@@ -135,8 +174,16 @@ func (c *ApiController) BatchEnforce() {
} }
res := [][]bool{} res := [][]bool{}
for _, permission := range permissions {
enforceResult, err := object.BatchEnforce(permission.GetId(), &requests) listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -144,6 +191,7 @@ func (c *ApiController) BatchEnforce() {
res = append(res, enforceResult) res = append(res, enforceResult)
} }
c.ResponseOk(res) c.ResponseOk(res)
} }

View File

@@ -53,7 +53,8 @@ func (c *ApiController) GetMessages() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedMessages(messages) c.Data["json"] = object.GetMaskedMessages(messages)
@@ -89,7 +90,8 @@ func (c *ApiController) GetMessage() {
id := c.Input().Get("id") id := c.Input().Get("id")
message, err := object.GetMessage(id) message, err := object.GetMessage(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = object.GetMaskedMessage(message) c.Data["json"] = object.GetMaskedMessage(message)
@@ -100,7 +102,8 @@ func (c *ApiController) ResponseErrorStream(errorText string) {
event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText) event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText)
_, err := c.Ctx.ResponseWriter.Write([]byte(event)) _, err := c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }
@@ -196,7 +199,8 @@ func (c *ApiController) GetMessageAnswer() {
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end") event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
_, err = c.Ctx.ResponseWriter.Write([]byte(event)) _, err = c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
answer := stringBuilder.String() answer := stringBuilder.String()
@@ -204,7 +208,8 @@ func (c *ApiController) GetMessageAnswer() {
message.Text = answer message.Text = answer
_, err = object.UpdateMessage(message.GetId(), message) _, err = object.UpdateMessage(message.GetId(), message)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }

View File

@@ -17,7 +17,6 @@ package controllers
import ( import (
"net/http" "net/http"
"github.com/beego/beego"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@@ -29,7 +28,7 @@ import (
// @param owner form string true "owner of user" // @param owner form string true "owner of user"
// @param name form string true "name of user" // @param name form string true "name of user"
// @param type form string true "MFA auth type" // @param type form string true "MFA auth type"
// @Success 200 {object} The Response object // @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/initiate [post] // @router /mfa/setup/initiate [post]
func (c *ApiController) MfaSetupInitiate() { func (c *ApiController) MfaSetupInitiate() {
owner := c.Ctx.Request.Form.Get("owner") owner := c.Ctx.Request.Form.Get("owner")
@@ -58,10 +57,7 @@ func (c *ApiController) MfaSetupInitiate() {
return return
} }
issuer := beego.AppConfig.String("appname") mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
accountName := user.GetId()
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetModels() {
if limit == "" || page == "" { if limit == "" || page == "" {
models, err := object.GetModels(owner) models, err := object.GetModels(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = models c.Data["json"] = models
@@ -77,7 +78,8 @@ func (c *ApiController) GetModel() {
model, err := object.GetModel(id) model, err := object.GetModel(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = model c.Data["json"] = model

View File

@@ -37,17 +37,27 @@ func (c *ApiController) GetOrganizations() {
value := c.Input().Get("value") value := c.Input().Get("value")
sortField := c.Input().Get("sortField") sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
organizationName := c.Input().Get("organizationName")
isGlobalAdmin := c.IsGlobalAdmin()
if limit == "" || page == "" { if limit == "" || page == "" {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner)) var maskedOrganizations []*object.Organization
var err error
if isGlobalAdmin {
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
} else {
maskedOrganizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
}
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedOrganizations c.Data["json"] = maskedOrganizations
c.ServeJSON() c.ServeJSON()
} else { } else {
isGlobalAdmin := c.IsGlobalAdmin()
if !isGlobalAdmin { if !isGlobalAdmin {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner)) maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
if err != nil { if err != nil {
@@ -64,7 +74,7 @@ func (c *ApiController) GetOrganizations() {
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, organizationName, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@@ -42,7 +42,8 @@ func (c *ApiController) GetPayments() {
if limit == "" || page == "" { if limit == "" || page == "" {
payments, err := object.GetPayments(owner) payments, err := object.GetPayments(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = payments c.Data["json"] = payments
@@ -51,13 +52,15 @@ func (c *ApiController) GetPayments() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetPaymentCount(owner, organization, field, value) count, err := object.GetPaymentCount(owner, organization, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder) payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(payments, paginator.Nums()) c.ResponseOk(payments, paginator.Nums())
@@ -99,7 +102,8 @@ func (c *ApiController) GetPayment() {
payment, err := object.GetPayment(id) payment, err := object.GetPayment(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = payment c.Data["json"] = payment
@@ -190,7 +194,8 @@ func (c *ApiController) NotifyPayment() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
} }

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetPermissions() {
if limit == "" || page == "" { if limit == "" || page == "" {
permissions, err := object.GetPermissions(owner) permissions, err := object.GetPermissions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = permissions c.Data["json"] = permissions
@@ -50,13 +51,15 @@ func (c *ApiController) GetPermissions() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
count, err := object.GetPermissionCount(owner, field, value) count, err := object.GetPermissionCount(owner, field, value)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
paginator := pagination.SetPaginator(c.Ctx, limit, count) paginator := pagination.SetPaginator(c.Ctx, limit, count)
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(permissions, paginator.Nums()) c.ResponseOk(permissions, paginator.Nums())
@@ -116,7 +119,8 @@ func (c *ApiController) GetPermission() {
permission, err := object.GetPermission(id) permission, err := object.GetPermission(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = permission c.Data["json"] = permission

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetPlans() {
if limit == "" || page == "" { if limit == "" || page == "" {
plans, err := object.GetPlans(owner) plans, err := object.GetPlans(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = plans c.Data["json"] = plans
@@ -79,13 +80,15 @@ func (c *ApiController) GetPlan() {
plan, err := object.GetPlan(id) plan, err := object.GetPlan(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if includeOption { if includeOption {
options, err := object.GetPermissionsByRole(plan.Role) options, err := object.GetPermissionsByRole(plan.Role)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
for _, option := range options { for _, option := range options {

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetPricings() {
if limit == "" || page == "" { if limit == "" || page == "" {
pricings, err := object.GetPricings(owner) pricings, err := object.GetPricings(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = pricings c.Data["json"] = pricings
@@ -70,14 +71,15 @@ func (c *ApiController) GetPricings() {
// @Tag Pricing API // @Tag Pricing API
// @Description get pricing // @Description get pricing
// @Param id query string true "The id ( owner/name ) of the pricing" // @Param id query string true "The id ( owner/name ) of the pricing"
// @Success 200 {object} object.pricing The Response object // @Success 200 {object} object.Pricing The Response object
// @router /get-pricing [get] // @router /get-pricing [get]
func (c *ApiController) GetPricing() { func (c *ApiController) GetPricing() {
id := c.Input().Get("id") id := c.Input().Get("id")
pricing, err := object.GetPricing(id) pricing, err := object.GetPricing(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = pricing c.Data["json"] = pricing

View File

@@ -42,7 +42,8 @@ func (c *ApiController) GetProducts() {
if limit == "" || page == "" { if limit == "" || page == "" {
products, err := object.GetProducts(owner) products, err := object.GetProducts(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = products c.Data["json"] = products
@@ -78,12 +79,14 @@ func (c *ApiController) GetProduct() {
product, err := object.GetProduct(id) product, err := object.GetProduct(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
err = object.ExtendProductWithProviders(product) err = object.ExtendProductWithProviders(product)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = product c.Data["json"] = product

View File

@@ -46,7 +46,8 @@ func (c *ApiController) GetProviders() {
if limit == "" || page == "" { if limit == "" || page == "" {
providers, err := object.GetProviders(owner) providers, err := object.GetProviders(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled)) c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
@@ -92,7 +93,8 @@ func (c *ApiController) GetGlobalProviders() {
if limit == "" || page == "" { if limit == "" || page == "" {
globalProviders, err := object.GetGlobalProviders() globalProviders, err := object.GetGlobalProviders()
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled)) c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))

View File

@@ -42,17 +42,22 @@ func (c *ApiController) GetRecords() {
value := c.Input().Get("value") value := c.Input().Get("value")
sortField := c.Input().Get("sortField") sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
organizationName := c.Input().Get("organizationName")
if limit == "" || page == "" { if limit == "" || page == "" {
records, err := object.GetRecords() records, err := object.GetRecords()
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = records c.Data["json"] = records
c.ServeJSON() c.ServeJSON()
} else { } else {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
if c.IsGlobalAdmin() && organizationName != "" {
organization = organizationName
}
filterRecord := &object.Record{Organization: organization} filterRecord := &object.Record{Organization: organization}
count, err := object.GetRecordCount(field, value, filterRecord) count, err := object.GetRecordCount(field, value, filterRecord)
if err != nil { if err != nil {
@@ -84,12 +89,14 @@ func (c *ApiController) GetRecordsByFilter() {
record := &object.Record{} record := &object.Record{}
err := util.JsonToStruct(body, record) err := util.JsonToStruct(body, record)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
records, err := object.GetRecordsByField(record) records, err := object.GetRecordsByField(record)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = records c.Data["json"] = records

View File

@@ -29,9 +29,19 @@ import (
) )
// GetResources // GetResources
// @router /get-resources [get]
// @Tag Resource API // @Tag Resource API
// @Title GetResources // @Title GetResources
// @Description get resources
// @Param owner query string true "Owner"
// @Param user query string true "User"
// @Param pageSize query integer false "Page Size"
// @Param p query integer false "Page Number"
// @Param field query string false "Field"
// @Param value query string false "Value"
// @Param sortField query string false "Sort Field"
// @Param sortOrder query string false "Sort Order"
// @Success 200 {array} object.Resource The Response object
// @router /get-resources [get]
func (c *ApiController) GetResources() { func (c *ApiController) GetResources() {
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
user := c.Input().Get("user") user := c.Input().Get("user")
@@ -53,7 +63,8 @@ func (c *ApiController) GetResources() {
if limit == "" || page == "" { if limit == "" || page == "" {
resources, err := object.GetResources(owner, user) resources, err := object.GetResources(owner, user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resources c.Data["json"] = resources
@@ -80,13 +91,17 @@ func (c *ApiController) GetResources() {
// GetResource // GetResource
// @Tag Resource API // @Tag Resource API
// @Title GetResource // @Title GetResource
// @Description get resource
// @Param id query string true "The id ( owner/name ) of resource"
// @Success 200 {object} object.Resource The Response object
// @router /get-resource [get] // @router /get-resource [get]
func (c *ApiController) GetResource() { func (c *ApiController) GetResource() {
id := c.Input().Get("id") id := c.Input().Get("id")
resource, err := object.GetResource(id) resource, err := object.GetResource(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resource c.Data["json"] = resource
@@ -96,6 +111,10 @@ func (c *ApiController) GetResource() {
// UpdateResource // UpdateResource
// @Tag Resource API // @Tag Resource API
// @Title UpdateResource // @Title UpdateResource
// @Description get resource
// @Param id query string true "The id ( owner/name ) of resource"
// @Param resource body object.Resource true "The resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /update-resource [post] // @router /update-resource [post]
func (c *ApiController) UpdateResource() { func (c *ApiController) UpdateResource() {
id := c.Input().Get("id") id := c.Input().Get("id")
@@ -114,6 +133,8 @@ func (c *ApiController) UpdateResource() {
// AddResource // AddResource
// @Tag Resource API // @Tag Resource API
// @Title AddResource // @Title AddResource
// @Param resource body object.Resource true "Resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /add-resource [post] // @router /add-resource [post]
func (c *ApiController) AddResource() { func (c *ApiController) AddResource() {
var resource object.Resource var resource object.Resource
@@ -130,6 +151,8 @@ func (c *ApiController) AddResource() {
// DeleteResource // DeleteResource
// @Tag Resource API // @Tag Resource API
// @Title DeleteResource // @Title DeleteResource
// @Param resource body object.Resource true "Resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /delete-resource [post] // @router /delete-resource [post]
func (c *ApiController) DeleteResource() { func (c *ApiController) DeleteResource() {
var resource object.Resource var resource object.Resource
@@ -158,6 +181,16 @@ func (c *ApiController) DeleteResource() {
// UploadResource // UploadResource
// @Tag Resource API // @Tag Resource API
// @Title UploadResource // @Title UploadResource
// @Param owner query string true "Owner"
// @Param user query string true "User"
// @Param application query string true "Application"
// @Param tag query string false "Tag"
// @Param parent query string false "Parent"
// @Param fullFilePath query string true "Full File Path"
// @Param createdTime query string false "Created Time"
// @Param description query string false "Description"
// @Param file formData file true "Resource file"
// @Success 200 {object} object.Resource FileUrl, objectKey
// @router /upload-resource [post] // @router /upload-resource [post]
func (c *ApiController) UploadResource() { func (c *ApiController) UploadResource() {
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
@@ -196,16 +229,16 @@ func (c *ApiController) UploadResource() {
fileType := "unknown" fileType := "unknown"
contentType := header.Header.Get("Content-Type") contentType := header.Header.Get("Content-Type")
fileType, _ = util.GetOwnerAndNameFromId(contentType) fileType, _ = util.GetOwnerAndNameFromIdNoCheck(contentType + "/")
if fileType != "image" && fileType != "video" { if fileType != "image" && fileType != "video" {
ext := filepath.Ext(filename) ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext) mimeType := mime.TypeByExtension(ext)
fileType, _ = util.GetOwnerAndNameFromId(mimeType) fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
} }
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175) fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175)
if tag != "avatar" && tag != "termsOfUse" { if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
ext := filepath.Ext(filepath.Base(fullFilePath)) ext := filepath.Ext(filepath.Base(fullFilePath))
index := len(fullFilePath) - len(ext) index := len(fullFilePath) - len(ext)
for i := 1; ; i++ { for i := 1; ; i++ {
@@ -292,7 +325,7 @@ func (c *ApiController) UploadResource() {
return return
} }
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimRight(fullFilePath, ".html")) _, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimSuffix(fullFilePath, ".html"))
applicationObj, err := object.GetApplication(applicationId) applicationObj, err := object.GetApplication(applicationId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@@ -305,6 +338,25 @@ func (c *ApiController) UploadResource() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
case "idCardFront", "idCardBack", "idCardWithPerson":
user, err := object.GetUserNoCheck(util.GetId(owner, username))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
return
}
user.Properties[tag] = fileUrl
user.Properties["isIdCardVerified"] = "false"
_, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false)
if err != nil {
c.ResponseError(err.Error())
return
}
} }
c.ResponseOk(fileUrl, objectKey) c.ResponseOk(fileUrl, objectKey)

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetRoles() {
if limit == "" || page == "" { if limit == "" || page == "" {
roles, err := object.GetRoles(owner) roles, err := object.GetRoles(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = roles c.Data["json"] = roles
@@ -77,7 +78,8 @@ func (c *ApiController) GetRole() {
role, err := object.GetRole(id) role, err := object.GetRole(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = role c.Data["json"] = role

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetSessions() {
if limit == "" || page == "" { if limit == "" || page == "" {
sessions, err := object.GetSessions(owner) sessions, err := object.GetSessions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = sessions c.Data["json"] = sessions
@@ -76,7 +77,8 @@ func (c *ApiController) GetSingleSession() {
session, err := object.GetSingleSession(id) session, err := object.GetSingleSession(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = session c.Data["json"] = session
@@ -155,7 +157,8 @@ func (c *ApiController) IsSessionDuplicated() {
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId) isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated} c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetSubscriptions() {
if limit == "" || page == "" { if limit == "" || page == "" {
subscriptions, err := object.GetSubscriptions(owner) subscriptions, err := object.GetSubscriptions(owner)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = subscriptions c.Data["json"] = subscriptions
@@ -70,14 +71,15 @@ func (c *ApiController) GetSubscriptions() {
// @Tag Subscription API // @Tag Subscription API
// @Description get subscription // @Description get subscription
// @Param id query string true "The id ( owner/name ) of the subscription" // @Param id query string true "The id ( owner/name ) of the subscription"
// @Success 200 {object} object.subscription The Response object // @Success 200 {object} object.Subscription The Response object
// @router /get-subscription [get] // @router /get-subscription [get]
func (c *ApiController) GetSubscription() { func (c *ApiController) GetSubscription() {
id := c.Input().Get("id") id := c.Input().Get("id")
subscription, err := object.GetSubscription(id) subscription, err := object.GetSubscription(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = subscription c.Data["json"] = subscription

View File

@@ -42,7 +42,8 @@ func (c *ApiController) GetSyncers() {
if limit == "" || page == "" { if limit == "" || page == "" {
organizationSyncers, err := object.GetOrganizationSyncers(owner, organization) organizationSyncers, err := object.GetOrganizationSyncers(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = organizationSyncers c.Data["json"] = organizationSyncers
@@ -78,7 +79,8 @@ func (c *ApiController) GetSyncer() {
syncer, err := object.GetSyncer(id) syncer, err := object.GetSyncer(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = syncer c.Data["json"] = syncer

View File

@@ -43,7 +43,8 @@ func (c *ApiController) GetTokens() {
if limit == "" || page == "" { if limit == "" || page == "" {
token, err := object.GetTokens(owner, organization) token, err := object.GetTokens(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = token c.Data["json"] = token
@@ -78,7 +79,8 @@ func (c *ApiController) GetToken() {
id := c.Input().Get("id") id := c.Input().Get("id")
token, err := object.GetToken(id) token, err := object.GetToken(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = token c.Data["json"] = token
@@ -193,7 +195,8 @@ func (c *ApiController) GetOAuthToken() {
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage()) oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = oAuthtoken c.Data["json"] = oAuthtoken
@@ -236,7 +239,8 @@ func (c *ApiController) RefreshToken() {
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host) refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = refreshToken2 c.Data["json"] = refreshToken2
@@ -276,7 +280,8 @@ func (c *ApiController) IntrospectToken() {
} }
application, err := object.GetApplicationByClientId(clientId) application, err := object.GetApplicationByClientId(clientId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if application == nil || application.ClientSecret != clientSecret { if application == nil || application.ClientSecret != clientSecret {
@@ -289,7 +294,8 @@ func (c *ApiController) IntrospectToken() {
} }
token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name) token, err := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if token == nil { if token == nil {
@@ -319,7 +325,7 @@ func (c *ApiController) IntrospectToken() {
Sub: jwtToken.Subject, Sub: jwtToken.Subject,
Aud: jwtToken.Audience, Aud: jwtToken.Audience,
Iss: jwtToken.Issuer, Iss: jwtToken.Issuer,
Jti: jwtToken.Id, Jti: jwtToken.ID,
} }
c.ServeJSON() c.ServeJSON()
} }

View File

@@ -41,7 +41,8 @@ func (c *ApiController) GetGlobalUsers() {
if limit == "" || page == "" { if limit == "" || page == "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers()) maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers())
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers
@@ -101,7 +102,8 @@ func (c *ApiController) GetUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner)) maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers
@@ -153,7 +155,8 @@ func (c *ApiController) GetUser() {
if userId != "" && owner != "" { if userId != "" && owner != "" {
userFromUserId, err = object.GetUserByUserId(owner, userId) userFromUserId, err = object.GetUserByUserId(owner, userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
id = util.GetId(userFromUserId.Owner, userFromUserId.Name) id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
@@ -165,7 +168,8 @@ func (c *ApiController) GetUser() {
organization, err := object.GetOrganization(util.GetId("admin", owner)) organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
if !organization.IsProfilePublic { if !organization.IsProfilePublic {
@@ -190,18 +194,21 @@ func (c *ApiController) GetUser() {
} }
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
user.MultiFactorAuths = object.GetAllMfaProps(user, true) user.MultiFactorAuths = object.GetAllMfaProps(user, true)
err = object.ExtendUserWithRolesAndPermissions(user) err = object.ExtendUserWithRolesAndPermissions(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
maskedUser, err := object.GetMaskedUser(user) maskedUser, err := object.GetMaskedUser(user)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUser c.Data["json"] = maskedUser
@@ -416,6 +423,7 @@ func (c *ApiController) SetPassword() {
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" { if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first")
return return
} else if code == "" { } else if code == "" {
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage()) hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
@@ -425,7 +433,7 @@ func (c *ApiController) SetPassword() {
} }
} else { } else {
if code != c.GetSession("verifiedCode") { if code != c.GetSession("verifiedCode") {
c.ResponseError("") c.ResponseError(c.T("general:Missing parameter"))
return return
} }
c.SetSession("verifiedCode", "") c.SetSession("verifiedCode", "")
@@ -497,7 +505,8 @@ func (c *ApiController) GetSortedUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit)) maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = maskedUsers c.Data["json"] = maskedUsers

View File

@@ -97,7 +97,8 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user, err := object.GetUser(userId) user, err := object.GetUser(userId)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return nil, false
} }
if user == nil { if user == nil {

View File

@@ -93,10 +93,9 @@ func (c *ApiController) SendVerificationCode() {
} }
} }
// mfaSessionData != nil, means method is MfaAuthVerification // mfaUserSession != "", means method is MfaAuthVerification
if mfaSessionData := c.getMfaSessionData(); mfaSessionData != nil { if mfaUserSession := c.getMfaUserSession(); mfaUserSession != "" {
user, err = object.GetUser(mfaSessionData.UserId) user, err = object.GetUser(mfaUserSession)
c.setMfaSessionData(nil)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -134,6 +133,8 @@ func (c *ApiController) SendVerificationCode() {
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest { if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret vform.Dest = mfaProps.Secret
} }
} else if vform.Method == MfaSetupVerification {
c.SetSession(object.MfaDestSession, vform.Dest)
} }
provider, err := application.GetEmailProvider() provider, err := application.GetEmailProvider()
@@ -164,6 +165,11 @@ func (c *ApiController) SendVerificationCode() {
vform.CountryCode = user.GetCountryCode(vform.CountryCode) vform.CountryCode = user.GetCountryCode(vform.CountryCode)
} }
} }
if vform.Method == MfaSetupVerification {
c.SetSession(object.MfaCountryCodeSession, vform.CountryCode)
c.SetSession(object.MfaDestSession, vform.Dest)
}
} else if vform.Method == MfaAuthVerification { } else if vform.Method == MfaAuthVerification {
mfaProps := user.GetPreferredMfaProps(false) mfaProps := user.GetPreferredMfaProps(false)
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest { if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
@@ -187,11 +193,6 @@ func (c *ApiController) SendVerificationCode() {
} }
} }
if vform.Method == MfaSetupVerification {
c.SetSession(object.MfaSmsCountryCodeSession, vform.CountryCode)
c.SetSession(object.MfaSmsDestSession, vform.Dest)
}
if sendResp != nil { if sendResp != nil {
c.ResponseError(sendResp.Error()) c.ResponseError(sendResp.Error())
} else { } else {

View File

@@ -66,7 +66,7 @@ func (c *ApiController) WebAuthnSignupBegin() {
// @Tag User API // @Tag User API
// @Description WebAuthn Registration Flow 2nd stage // @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response" // @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object" // @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signup/finish [post] // @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() { func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host) webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
@@ -150,7 +150,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @Tag Login API // @Tag Login API
// @Description WebAuthn Login Flow 2nd stage // @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response" // @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object" // @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signin/finish [post] // @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() { func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")

View File

@@ -42,7 +42,8 @@ func (c *ApiController) GetWebhooks() {
if limit == "" || page == "" { if limit == "" || page == "" {
webhooks, err := object.GetWebhooks(owner, organization) webhooks, err := object.GetWebhooks(owner, organization)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = webhooks c.Data["json"] = webhooks
@@ -79,7 +80,8 @@ func (c *ApiController) GetWebhook() {
webhook, err := object.GetWebhook(id) webhook, err := object.GetWebhook(id)
if err != nil { if err != nil {
panic(err) c.ResponseError(err.Error())
return
} }
c.Data["json"] = webhook c.Data["json"] = webhook

7
go.mod
View File

@@ -39,17 +39,18 @@ require (
github.com/lib/pq v1.10.2 github.com/lib/pq v1.10.2
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2 github.com/markbates/goth v1.75.2
github.com/mitchellh/mapstructure v1.5.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5 github.com/nyaruka/phonenumbers v1.1.5
github.com/pkoukk/tiktoken-go v0.1.1 github.com/pkoukk/tiktoken-go v0.1.1
github.com/plutov/paypal/v4 v4.7.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0 github.com/prometheus/client_model v0.2.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0 github.com/russellhaering/goxmldsig v1.2.0
github.com/sashabaranov/go-openai v1.9.1 github.com/sashabaranov/go-openai v1.12.0
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
@@ -59,7 +60,7 @@ require (
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/xorm-io/builder v0.3.13 // indirect github.com/xorm-io/builder v0.3.13
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect

9
go.sum
View File

@@ -105,6 +105,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
@@ -495,10 +497,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo= github.com/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw= github.com/pkoukk/tiktoken-go v0.1.1/go.mod h1:boMWvk9pQCOTx11pgu0DrIdrAKgQzzJKUP6vLXaz7Rw=
github.com/plutov/paypal/v4 v4.7.0 h1:6TRvYD4ny6yQfHaABeStNf43GFM1wpW5jU/XEDGQmq0=
github.com/plutov/paypal/v4 v4.7.0/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -546,6 +548,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM= github.com/sashabaranov/go-openai v1.9.1 h1:3N52HkJKo9Zlo/oe1AVv5ZkCOny0ra58/ACvAxkN3MM=
github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.9.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.12.0 h1:aRNHH0gtVfrpIaEolD0sWrLLRnYQNK4cH/bIAHwL8Rk=
github.com/sashabaranov/go-openai v1.12.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -595,7 +599,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert", "The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert",
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert", "The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
"Unauthorized operation": "Nicht autorisierte Operation", "Unauthorized operation": "Nicht autorisierte Operation",
"Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s" "Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein" "Service %s and %s do not match": "Service %s und %s stimmen nicht überein"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application", "The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s" "Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación", "The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación",
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación", "The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
"Unauthorized operation": "Operación no autorizada", "Unauthorized operation": "Operación no autorizada",
"Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %s" "Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden" "Service %s and %s do not match": "Los servicios %s y %s no coinciden"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application", "The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application",
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application", "The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
"Unauthorized operation": "Opération non autorisée", "Unauthorized operation": "Opération non autorisée",
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s" "Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas" "Service %s and %s do not match": "Les services %s et %s ne correspondent pas"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut", "The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini", "The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
"Unauthorized operation": "Operasi tidak sah", "Unauthorized operation": "Operasi tidak sah",
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s" "Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok" "Service %s and %s do not match": "Layanan %s dan %s tidak cocok"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません", "The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません",
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません", "The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
"Unauthorized operation": "不正操作", "Unauthorized operation": "不正操作",
"Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%s" "Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "サービス%sと%sは一致しません" "Service %s and %s do not match": "サービス%sと%sは一致しません"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다", "The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다",
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다", "The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
"Unauthorized operation": "무단 조작", "Unauthorized operation": "무단 조작",
"Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %s" "Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다" "Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application", "The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s" "Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения", "The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения", "The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
"Unauthorized operation": "Несанкционированная операция", "Unauthorized operation": "Несанкционированная операция",
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s" "Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Сервисы %s и %s не совпадают" "Service %s and %s do not match": "Сервисы %s и %s не совпадают"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng", "The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng",
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng", "The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
"Unauthorized operation": "Hoạt động không được ủy quyền", "Unauthorized operation": "Hoạt động không được ủy quyền",
"Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %s" "Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp" "Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"

View File

@@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式", "The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式",
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用", "The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s" "Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s",
"User's tag: %s is not listed in the application's tags": "用户的标签: %s不在该应用的标签列表中"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "服务%s与%s不匹配" "Service %s and %s do not match": "服务%s与%s不匹配"

View File

@@ -20,32 +20,37 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
_ "net/url"
_ "time"
"github.com/casdoor/casdoor/util"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
type CustomIdProvider struct { type CustomIdProvider struct {
Client *http.Client Client *http.Client
Config *oauth2.Config Config *oauth2.Config
UserInfoUrl string
UserInfoURL string
TokenURL string
AuthURL string
UserMapping map[string]string
Scopes []string
} }
func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl string, authUrl string, tokenUrl string, userInfoUrl string) *CustomIdProvider { func NewCustomIdProvider(idpInfo *ProviderInfo, redirectUrl string) *CustomIdProvider {
idp := &CustomIdProvider{} idp := &CustomIdProvider{}
idp.UserInfoUrl = userInfoUrl
config := &oauth2.Config{ idp.Config = &oauth2.Config{
ClientID: clientId, ClientID: idpInfo.ClientId,
ClientSecret: clientSecret, ClientSecret: idpInfo.ClientSecret,
RedirectURL: redirectUrl, RedirectURL: redirectUrl,
Endpoint: oauth2.Endpoint{ Endpoint: oauth2.Endpoint{
AuthURL: authUrl, AuthURL: idpInfo.AuthURL,
TokenURL: tokenUrl, TokenURL: idpInfo.TokenURL,
}, },
} }
idp.Config = config idp.UserInfoURL = idpInfo.UserInfoURL
idp.UserMapping = idpInfo.UserMapping
return idp return idp
} }
@@ -60,22 +65,20 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
} }
type CustomUserInfo struct { type CustomUserInfo struct {
Id string `json:"sub"` Id string `mapstructure:"id"`
Name string `json:"preferred_username,omitempty"` Username string `mapstructure:"username"`
DisplayName string `json:"name"` DisplayName string `mapstructure:"displayName"`
Email string `json:"email"` Email string `mapstructure:"email"`
AvatarUrl string `json:"picture"` AvatarUrl string `mapstructure:"avatarUrl"`
Status string `json:"status"`
Msg string `json:"msg"`
} }
func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
ctUserinfo := &CustomUserInfo{}
accessToken := token.AccessToken accessToken := token.AccessToken
request, err := http.NewRequest("GET", idp.UserInfoUrl, nil) request, err := http.NewRequest("GET", idp.UserInfoURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// add accessToken to request header // add accessToken to request header
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request) resp, err := idp.Client.Do(request)
@@ -89,21 +92,40 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
err = json.Unmarshal(data, ctUserinfo) var dataMap map[string]interface{}
err = json.Unmarshal(data, &dataMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if ctUserinfo.Status != "" { // map user info
return nil, fmt.Errorf("err: %s", ctUserinfo.Msg) for k, v := range idp.UserMapping {
_, ok := dataMap[v]
if !ok {
return nil, fmt.Errorf("cannot find %s in user from castom provider", v)
}
dataMap[k] = dataMap[v]
}
// try to parse id to string
id, err := util.ParseIdToString(dataMap["id"])
if err != nil {
return nil, err
}
dataMap["id"] = id
customUserinfo := &CustomUserInfo{}
err = mapstructure.Decode(dataMap, customUserinfo)
if err != nil {
return nil, err
} }
userInfo := &UserInfo{ userInfo := &UserInfo{
Id: ctUserinfo.Id, Id: customUserinfo.Id,
Username: ctUserinfo.Name, Username: customUserinfo.Username,
DisplayName: ctUserinfo.DisplayName, DisplayName: customUserinfo.DisplayName,
Email: ctUserinfo.Email, Email: customUserinfo.Email,
AvatarUrl: ctUserinfo.AvatarUrl, AvatarUrl: customUserinfo.AvatarUrl,
} }
return userInfo, nil return userInfo, nil
} }

View File

@@ -32,72 +32,89 @@ type UserInfo struct {
AvatarUrl string AvatarUrl string
} }
type ProviderInfo struct {
Type string
SubType string
ClientId string
ClientSecret string
AppId string
HostUrl string
RedirectUrl string
TokenURL string
AuthURL string
UserInfoURL string
UserMapping map[string]string
}
type IdProvider interface { type IdProvider interface {
SetHttpClient(client *http.Client) SetHttpClient(client *http.Client)
GetToken(code string) (*oauth2.Token, error) GetToken(code string) (*oauth2.Token, error)
GetUserInfo(token *oauth2.Token) (*UserInfo, error) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider { func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
if typ == "GitHub" { switch idpInfo.Type {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl) case "GitHub":
} else if typ == "Google" { return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl) case "Google":
} else if typ == "QQ" { return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewQqIdProvider(clientId, clientSecret, redirectUrl) case "QQ":
} else if typ == "WeChat" { return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl) case "WeChat":
} else if typ == "Facebook" { return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl) case "Facebook":
} else if typ == "DingTalk" { return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl) case "DingTalk":
} else if typ == "Weibo" { return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl) case "Weibo":
} else if typ == "Gitee" { return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl) case "Gitee":
} else if typ == "LinkedIn" { return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl) case "LinkedIn":
} else if typ == "WeCom" { return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
if subType == "Internal" { case "WeCom":
return NewWeComInternalIdProvider(clientId, clientSecret, redirectUrl) if idpInfo.SubType == "Internal" {
} else if subType == "Third-party" { return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeComIdProvider(clientId, clientSecret, redirectUrl) } else if idpInfo.SubType == "Third-party" {
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else { } else {
return nil return nil
} }
} else if typ == "Lark" { case "Lark":
return NewLarkIdProvider(clientId, clientSecret, redirectUrl) return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if typ == "GitLab" { case "GitLab":
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl) return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if typ == "Adfs" { case "Adfs":
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
} else if typ == "Baidu" { case "Baidu":
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl) return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if typ == "Alipay" { case "Alipay":
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl) return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if typ == "Custom" { case "Custom":
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl) return NewCustomIdProvider(idpInfo, redirectUrl)
} else if typ == "Infoflow" { case "Infoflow":
if subType == "Internal" { if idpInfo.SubType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl) return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
} else if subType == "Third-party" { } else if idpInfo.SubType == "Third-party" {
return NewInfoflowIdProvider(clientId, clientSecret, appId, redirectUrl) return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
} else { } else {
return nil return nil
} }
} else if typ == "Casdoor" { case "Casdoor":
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
} else if typ == "Okta" { case "Okta":
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
} else if typ == "Douyin" { case "Douyin":
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl) return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if isGothSupport(typ) { case "Bilibili":
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl, hostUrl) return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
} else if typ == "Bilibili" { default:
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl) if isGothSupport(idpInfo.Type) {
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
}
return nil
} }
return nil
} }
var gothList = []string{ var gothList = []string{

View File

@@ -198,12 +198,22 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) { func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret) accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
request, err := http.NewRequest("GET", accessTokenUrl, nil) request, err := http.NewRequest("GET", accessTokenUrl, nil)
if err != nil {
return "", err
}
client := new(http.Client) client := new(http.Client)
resp, err := client.Do(request) resp, err := client.Do(request)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body) respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err
} }
var data struct { var data struct {
ExpireIn int `json:"expires_in"` ExpireIn int `json:"expires_in"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
@@ -212,20 +222,30 @@ func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (
if err != nil { if err != nil {
return "", err return "", err
} }
return data.AccessToken, nil return data.AccessToken, nil
} }
func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) { func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) {
accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret) accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret)
client := new(http.Client) client := new(http.Client)
params := "{\"action_name\": \"QR_LIMIT_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}"
weChatEndpoint := "https://api.weixin.qq.com/cgi-bin/qrcode/create"
qrCodeUrl := fmt.Sprintf("%s?access_token=%s", weChatEndpoint, accessToken)
params := `{"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "test"}}}`
bodyData := bytes.NewReader([]byte(params)) bodyData := bytes.NewReader([]byte(params))
qrCodeUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", accessToken)
requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData) requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData)
if err != nil {
return "", err
}
resp, err := client.Do(requeset) resp, err := client.Do(requeset)
if err != nil { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body) respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -275,7 +275,7 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
session = session.And("owner=?", owner) session = session.And("owner=?", owner)
} }
if field != "" && value != "" { if field != "" && value != "" {
if filterField(field) { if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
} }
} }
@@ -303,7 +303,7 @@ func GetSessionForUser(owner string, offset, limit int, field, value, sortField,
} }
} }
if field != "" && value != "" { if field != "" && value != "" {
if filterField(field) { if util.FilterField(field) {
if offset != -1 { if offset != -1 {
field = fmt.Sprintf("a.%s", field) field = fmt.Sprintf("a.%s", field)
} }

View File

@@ -57,6 +57,7 @@ type Application struct {
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
Tags []string `xorm:"mediumtext" json:"tags"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`

View File

@@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@@ -28,21 +27,11 @@ import (
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
) )
var (
reWhiteSpace *regexp.Regexp
reFieldWhiteList *regexp.Regexp
)
const ( const (
SigninWrongTimesLimit = 5 SigninWrongTimesLimit = 5
LastSignWrongTimeDuration = time.Minute * 15 LastSignWrongTimeDuration = time.Minute * 15
) )
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
}
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string { func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
if organization == nil { if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist") return i18n.Translate(lang, "check:Organization does not exist")
@@ -58,7 +47,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
if util.IsEmailValid(form.Username) { if util.IsEmailValid(form.Username) {
return i18n.Translate(lang, "check:Username cannot be an email address") return i18n.Translate(lang, "check:Username cannot be an email address")
} }
if reWhiteSpace.MatchString(form.Username) { if util.ReWhiteSpace.MatchString(form.Username) {
return i18n.Translate(lang, "check:Username cannot contain white spaces") return i18n.Translate(lang, "check:Username cannot contain white spaces")
} }
@@ -294,10 +283,6 @@ func CheckUserPassword(organization string, username string, password string, la
return user, "" return user, ""
} }
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) { func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
if requestUserId == "" { if requestUserId == "" {
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first")) return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
@@ -397,8 +382,8 @@ func CheckUsername(username string, lang string) string {
} }
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex // https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(username) { if !util.ReUserName.MatchString(username) {
return i18n.Translate(lang, "check:The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.") return i18n.Translate(lang, "check:The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.")
} }

View File

@@ -61,7 +61,7 @@ func getBuiltInAccountItems() []*AccountItem {
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"}, {Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"}, {Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Groups", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"}, {Name: "Groups", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
@@ -93,7 +93,7 @@ func initBuiltInOrganization() bool {
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")), Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain", PasswordType: "plain",
PasswordOptions: []string{"AtLeast6"}, PasswordOptions: []string{"AtLeast6"},
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"}, CountryCodes: []string{"US", "ES", "FR", "DE", "GB", "CN", "JP", "KR", "VN", "ID", "SG", "IN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "pt"}, Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "pt"},
@@ -130,7 +130,7 @@ func initBuiltInUser() {
Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), Avatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Email: "admin@example.com", Email: "admin@example.com",
Phone: "12345678910", Phone: "12345678910",
CountryCode: "CN", CountryCode: "US",
Address: []string{}, Address: []string{},
Affiliation: "Example Inc.", Affiliation: "Example Inc.",
Tag: "staff", Tag: "staff",
@@ -184,6 +184,7 @@ func initBuiltInApplication() {
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
}, },
Tags: []string{},
RedirectUris: []string{}, RedirectUris: []string{},
ExpireInHours: 168, ExpireInHours: 168,
FormOffset: 2, FormOffset: 2,

View File

@@ -145,6 +145,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
if application.RedirectUris == nil { if application.RedirectUris == nil {
application.RedirectUris = []string{} application.RedirectUris = []string{}
} }
if application.Tags == nil {
application.Tags = []string{}
}
} }
for _, permission := range data.Permissions { for _, permission := range data.Permissions {
if permission.Actions == nil { if permission.Actions == nil {

View File

@@ -22,9 +22,7 @@ import (
"github.com/beego/beego/context" "github.com/beego/beego/context"
) )
type MfaSessionData struct { const MfaRecoveryCodesSession = "mfa_recovery_codes"
UserId string
}
type MfaProps struct { type MfaProps struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
@@ -37,10 +35,10 @@ type MfaProps struct {
} }
type MfaInterface interface { type MfaInterface interface {
SetupVerify(ctx *context.Context, passCode string) error Initiate(ctx *context.Context, userId string) (*MfaProps, error)
Verify(passCode string) error SetupVerify(ctx *context.Context, passcode string) error
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
Enable(ctx *context.Context, user *User) error Enable(ctx *context.Context, user *User) error
Verify(passcode string) error
} }
const ( const (
@@ -58,11 +56,11 @@ const (
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface { func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
switch mfaType { switch mfaType {
case SmsType: case SmsType:
return NewSmsTwoFactor(config) return NewSmsMfaUtil(config)
case EmailType: case EmailType:
return NewEmailTwoFactor(config) return NewEmailMfaUtil(config)
case TotpType: case TotpType:
return nil return NewTotpMfaUtil(config)
} }
return nil return nil
@@ -97,23 +95,9 @@ func MfaRecover(user *User, recoveryCode string) error {
func GetAllMfaProps(user *User, masked bool) []*MfaProps { func GetAllMfaProps(user *User, masked bool) []*MfaProps {
mfaProps := []*MfaProps{} mfaProps := []*MfaProps{}
if user.MfaPhoneEnabled { for _, mfaType := range []string{SmsType, EmailType, TotpType} {
mfaProps = append(mfaProps, user.GetMfaProps(SmsType, masked)) mfaProps = append(mfaProps, user.GetMfaProps(mfaType, masked))
} else {
mfaProps = append(mfaProps, &MfaProps{
Enabled: false,
MfaType: SmsType,
})
} }
if user.MfaEmailEnabled {
mfaProps = append(mfaProps, user.GetMfaProps(EmailType, masked))
} else {
mfaProps = append(mfaProps, &MfaProps{
Enabled: false,
MfaType: EmailType,
})
}
return mfaProps return mfaProps
} }
@@ -121,6 +105,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps := &MfaProps{} mfaProps := &MfaProps{}
if mfaType == SmsType { if mfaType == SmsType {
if !user.MfaPhoneEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: user.MfaPhoneEnabled, Enabled: user.MfaPhoneEnabled,
MfaType: mfaType, MfaType: mfaType,
@@ -132,6 +123,13 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps.Secret = user.Phone mfaProps.Secret = user.Phone
} }
} else if mfaType == EmailType { } else if mfaType == EmailType {
if !user.MfaEmailEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: user.MfaEmailEnabled, Enabled: user.MfaEmailEnabled,
MfaType: mfaType, MfaType: mfaType,
@@ -142,9 +140,22 @@ func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps.Secret = user.Email mfaProps.Secret = user.Email
} }
} else if mfaType == TotpType { } else if mfaType == TotpType {
if user.TotpSecret == "" {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{ mfaProps = &MfaProps{
Enabled: true,
MfaType: mfaType, MfaType: mfaType,
} }
if masked {
mfaProps.Secret = ""
} else {
mfaProps.Secret = user.TotpSecret
}
} }
if user.PreferredMfaType == mfaType { if user.PreferredMfaType == mfaType {
@@ -158,8 +169,9 @@ func DisabledMultiFactorAuth(user *User) error {
user.RecoveryCodes = []string{} user.RecoveryCodes = []string{}
user.MfaPhoneEnabled = false user.MfaPhoneEnabled = false
user.MfaEmailEnabled = false user.MfaEmailEnabled = false
user.TotpSecret = ""
_, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled"}, user.IsAdminUser()) _, err := updateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled", "totp_secret"})
if err != nil { if err != nil {
return err return err
} }

View File

@@ -18,26 +18,24 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/casdoor/casdoor/util"
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid" "github.com/google/uuid"
) )
const ( const (
MfaSmsCountryCodeSession = "mfa_country_code" MfaCountryCodeSession = "mfa_country_code"
MfaSmsDestSession = "mfa_dest" MfaDestSession = "mfa_dest"
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
) )
type SmsMfa struct { type SmsMfa struct {
Config *MfaProps Config *MfaProps
} }
func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*MfaProps, error) { func (mfa *SmsMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
recoveryCode := uuid.NewString() recoveryCode := uuid.NewString()
err := ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode}) err := ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -50,9 +48,19 @@ func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*
} }
func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error { func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
dest := ctx.Input.CruSession.Get(MfaSmsDestSession).(string) destSession := ctx.Input.CruSession.Get(MfaDestSession)
countryCode := ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string) if destSession == nil {
return errors.New("dest session is missing")
}
dest := destSession.(string)
if !util.IsEmailValid(dest) { if !util.IsEmailValid(dest) {
countryCodeSession := ctx.Input.CruSession.Get(MfaCountryCodeSession)
if countryCodeSession == nil {
return errors.New("country code is missing")
}
countryCode := countryCodeSession.(string)
dest, _ = util.GetE164Number(dest, countryCode) dest, _ = util.GetE164Number(dest, countryCode)
} }
@@ -63,9 +71,9 @@ func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
} }
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error { func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string) recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
if len(recoveryCodes) == 0 { if len(recoveryCodes) == 0 {
return fmt.Errorf("recovery codes is empty") return fmt.Errorf("recovery codes is missing")
} }
columns := []string{"recovery_codes", "preferred_mfa_type"} columns := []string{"recovery_codes", "preferred_mfa_type"}
@@ -80,8 +88,8 @@ func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
columns = append(columns, "mfa_phone_enabled") columns = append(columns, "mfa_phone_enabled")
if user.Phone == "" { if user.Phone == "" {
user.Phone = ctx.Input.CruSession.Get(MfaSmsDestSession).(string) user.Phone = ctx.Input.CruSession.Get(MfaDestSession).(string)
user.CountryCode = ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string) user.CountryCode = ctx.Input.CruSession.Get(MfaCountryCodeSession).(string)
columns = append(columns, "phone", "country_code") columns = append(columns, "phone", "country_code")
} }
} else if mfa.Config.MfaType == EmailType { } else if mfa.Config.MfaType == EmailType {
@@ -89,7 +97,7 @@ func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
columns = append(columns, "mfa_email_enabled") columns = append(columns, "mfa_email_enabled")
if user.Email == "" { if user.Email == "" {
user.Email = ctx.Input.CruSession.Get(MfaSmsDestSession).(string) user.Email = ctx.Input.CruSession.Get(MfaDestSession).(string)
columns = append(columns, "email") columns = append(columns, "email")
} }
} }
@@ -98,6 +106,11 @@ func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
if err != nil { if err != nil {
return err return err
} }
ctx.Input.CruSession.Delete(MfaRecoveryCodesSession)
ctx.Input.CruSession.Delete(MfaDestSession)
ctx.Input.CruSession.Delete(MfaCountryCodeSession)
return nil return nil
} }
@@ -111,7 +124,7 @@ func (mfa *SmsMfa) Verify(passCode string) error {
return nil return nil
} }
func NewSmsTwoFactor(config *MfaProps) *SmsMfa { func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
if config == nil { if config == nil {
config = &MfaProps{ config = &MfaProps{
MfaType: SmsType, MfaType: SmsType,
@@ -122,7 +135,7 @@ func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
} }
} }
func NewEmailTwoFactor(config *MfaProps) *SmsMfa { func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
if config == nil { if config == nil {
config = &MfaProps{ config = &MfaProps{
MfaType: EmailType, MfaType: EmailType,

140
object/mfa_totp.go Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"errors"
"fmt"
"github.com/beego/beego"
"github.com/beego/beego/context"
"github.com/google/uuid"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
const MfaTotpSecretSession = "mfa_totp_secret"
type TotpMfa struct {
Config *MfaProps
period uint
secretSize uint
digits otp.Digits
}
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
issuer := beego.AppConfig.String("appname")
if issuer == "" {
issuer = "casdoor"
}
key, err := totp.Generate(totp.GenerateOpts{
Issuer: issuer,
AccountName: userId,
Period: mfa.period,
SecretSize: mfa.secretSize,
Digits: mfa.digits,
})
if err != nil {
return nil, err
}
err = ctx.Input.CruSession.Set(MfaTotpSecretSession, key.Secret())
if err != nil {
return nil, err
}
recoveryCode := uuid.NewString()
err = ctx.Input.CruSession.Set(MfaRecoveryCodesSession, []string{recoveryCode})
if err != nil {
return nil, err
}
mfaProps := MfaProps{
MfaType: mfa.Config.MfaType,
RecoveryCodes: []string{recoveryCode},
Secret: key.Secret(),
URL: key.URL(),
}
return &mfaProps, nil
}
func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession)
if secret == nil {
return errors.New("totp secret is missing")
}
result := totp.Validate(passcode, secret.(string))
if result {
return nil
} else {
return errors.New("totp passcode error")
}
}
func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
recoveryCodes := ctx.Input.CruSession.Get(MfaRecoveryCodesSession).([]string)
if len(recoveryCodes) == 0 {
return fmt.Errorf("recovery codes is missing")
}
secret := ctx.Input.CruSession.Get(MfaTotpSecretSession).(string)
if secret == "" {
return fmt.Errorf("totp secret is missing")
}
columns := []string{"recovery_codes", "preferred_mfa_type", "totp_secret"}
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
user.TotpSecret = secret
if user.PreferredMfaType == "" {
user.PreferredMfaType = mfa.Config.MfaType
}
_, err := updateUser(user.GetId(), user, columns)
if err != nil {
return err
}
ctx.Input.CruSession.Delete(MfaRecoveryCodesSession)
ctx.Input.CruSession.Delete(MfaTotpSecretSession)
return nil
}
func (mfa *TotpMfa) Verify(passcode string) error {
result := totp.Validate(passcode, mfa.Config.Secret)
if result {
return nil
} else {
return errors.New("totp passcode error")
}
}
func NewTotpMfaUtil(config *MfaProps) *TotpMfa {
if config == nil {
config = &MfaProps{
MfaType: TotpType,
}
}
return &TotpMfa{
Config: config,
period: 30,
secretSize: 20,
digits: otp.DigitsSix,
}
}

View File

@@ -65,10 +65,13 @@ func getOriginFromHost(host string) (string, string) {
return origin, origin return origin, origin
} }
// "door.casdoor.com"
protocol := "https://" protocol := "https://"
if strings.HasPrefix(host, "localhost") { if !strings.Contains(host, ".") {
// "localhost:8000" or "computer-name:80"
protocol = "http://" protocol = "http://"
} else if isIpAddress(host) { } else if isIpAddress(host) {
// "192.168.0.10"
protocol = "http://" protocol = "http://"
} }

View File

@@ -69,7 +69,7 @@ type Organization struct {
IsProfilePublic bool `json:"isProfilePublic"` IsProfilePublic bool `json:"isProfilePublic"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"` MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"` AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
} }
func GetOrganizationCount(owner, field, value string) (int64, error) { func GetOrganizationCount(owner, field, value string) (int64, error) {
@@ -104,10 +104,15 @@ func GetOrganizationsByFields(owner string, fields ...string) ([]*Organization,
return organizations, nil return organizations, nil
} }
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Organization, error) { func GetPaginationOrganizations(owner string, name string, offset, limit int, field, value, sortField, sortOrder string) ([]*Organization, error) {
organizations := []*Organization{} organizations := []*Organization{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&organizations) var err error
if name != "" {
err = session.Find(&organizations, &Organization{Name: name})
} else {
err = session.Find(&organizations)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -231,6 +236,10 @@ func DeleteOrganization(organization *Organization) (bool, error) {
} }
func GetOrganizationByUser(user *User) (*Organization, error) { func GetOrganizationByUser(user *User) (*Organization, error) {
if user == nil {
return nil, nil
}
return getOrganization("admin", user.Owner) return getOrganization("admin", user.Owner)
} }
@@ -467,10 +476,21 @@ func organizationChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func (org *Organization) HasRequiredMfa() bool { func IsNeedPromptMfa(org *Organization, user *User) bool {
if org == nil || user == nil {
return false
}
for _, item := range org.MfaItems { for _, item := range org.MfaItems {
if item.Rule == "Required" { if item.Rule == "Required" {
return true if item.Name == EmailType && !user.MfaEmailEnabled {
return true
}
if item.Name == SmsType && !user.MfaPhoneEnabled {
return true
}
if item.Name == TotpType && user.TotpSecret == "" {
return true
}
} }
} }
return false return false

View File

@@ -370,3 +370,24 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
return permissions return permissions
} }
// GroupPermissionsByModelAdapter group permissions by model and adapter.
// Every model and adapter will be a key, and the value is a list of permission ids.
// With each list of permission ids have the same key, we just need to init the
// enforcer and do the enforce/batch-enforce once (with list of permission ids
// as the policyFilter when the enforcer load policy).
func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string {
m := make(map[string][]string)
for _, permission := range permissions {
key := permission.Model + permission.Adapter
permissionIds, ok := m[key]
if !ok {
m[key] = []string{permission.GetId()}
} else {
m[key] = append(permissionIds, permission.GetId())
}
}
return m
}

View File

@@ -26,7 +26,7 @@ import (
xormadapter "github.com/casdoor/xorm-adapter/v3" xormadapter "github.com/casdoor/xorm-adapter/v3"
) )
func getEnforcer(permission *Permission) *casbin.Enforcer { func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforcer {
tableName := "permission_rule" tableName := "permission_rule"
if len(permission.Adapter) != 0 { if len(permission.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter) adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter)
@@ -77,8 +77,13 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
enforcer.SetAdapter(adapter) enforcer.SetAdapter(adapter)
policyFilterV5 := []string{permission.GetId()}
if len(permissionIDs) != 0 {
policyFilterV5 = permissionIDs
}
policyFilter := xormadapter.Filter{ policyFilter := xormadapter.Filter{
V5: []string{permission.GetId()}, V5: policyFilterV5,
} }
if !HasRoleDefinition(m) { if !HasRoleDefinition(m) {
@@ -241,28 +246,13 @@ func removePolicies(permission *Permission) {
type CasbinRequest = []interface{} type CasbinRequest = []interface{}
func Enforce(permissionId string, request *CasbinRequest) (bool, error) { func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
permission, err := GetPermission(permissionId) enforcer := getEnforcer(permission, permissionIds...)
if err != nil {
return false, err
}
enforcer := getEnforcer(permission)
return enforcer.Enforce(*request...) return enforcer.Enforce(*request...)
} }
func BatchEnforce(permissionId string, requests *[]CasbinRequest) ([]bool, error) { func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
permission, err := GetPermission(permissionId) enforcer := getEnforcer(permission, permissionIds...)
if err != nil {
res := []bool{}
for i := 0; i < len(*requests); i++ {
res = append(res, false)
}
return res, err
}
enforcer := getEnforcer(permission)
return enforcer.BatchEnforce(*requests) return enforcer.BatchEnforce(*requests)
} }

View File

@@ -16,8 +16,11 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
@@ -28,21 +31,22 @@ type Provider struct {
Name string `xorm:"varchar(100) notnull pk unique" json:"name"` Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"` Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"` Cert string `xorm:"varchar(100)" json:"cert"`
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomScope string `xorm:"varchar(200)" json:"customScope"` CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"` CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
CustomLogo string `xorm:"varchar(200)" json:"customLogo"` Scopes string `xorm:"varchar(100)" json:"scopes"`
UserMapping map[string]string `xorm:"varchar(500)" json:"userMapping"`
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `json:"port"`
@@ -365,3 +369,27 @@ func providerChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
providerInfo := &idp.ProviderInfo{
Type: provider.Type,
SubType: provider.SubType,
ClientId: provider.ClientId,
ClientSecret: provider.ClientSecret,
AppId: provider.AppId,
HostUrl: provider.Host,
TokenURL: provider.CustomTokenUrl,
AuthURL: provider.CustomAuthUrl,
UserInfoURL: provider.CustomUserInfoUrl,
UserMapping: provider.UserMapping,
}
if provider.Type == "WeChat" {
if ctx != nil && strings.Contains(ctx.Request.UserAgent(), "MicroMessenger") {
providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2
}
}
return providerInfo
}

View File

@@ -50,6 +50,7 @@ type Syncer struct {
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"` AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
ErrorText string `xorm:"mediumtext" json:"errorText"` ErrorText string `xorm:"mediumtext" json:"errorText"`
SyncInterval int `json:"syncInterval"` SyncInterval int `json:"syncInterval"`
IsReadOnly bool `json:"isReadOnly"`
IsEnabled bool `json:"isEnabled"` IsEnabled bool `json:"isEnabled"`
Adapter *Adapter `xorm:"-" json:"-"` Adapter *Adapter `xorm:"-" json:"-"`

View File

@@ -63,9 +63,11 @@ func (syncer *Syncer) syncUsers() {
} }
} else { } else {
if user.PreHash == oHash { if user.PreHash == oHash {
updatedOUser := syncer.createOriginalUserFromUser(user) if !syncer.IsReadOnly {
syncer.updateUser(updatedOUser) updatedOUser := syncer.createOriginalUserFromUser(user)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser) syncer.updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
}
// update preHash // update preHash
user.PreHash = user.Hash user.PreHash = user.Hash
@@ -91,15 +93,17 @@ func (syncer *Syncer) syncUsers() {
panic(err) panic(err)
} }
for _, user := range users { if !syncer.IsReadOnly {
id := user.Id for _, user := range users {
if _, ok := oUserMap[id]; !ok { id := user.Id
newOUser := syncer.createOriginalUserFromUser(user) if _, ok := oUserMap[id]; !ok {
_, err = syncer.addUser(newOUser) newOUser := syncer.createOriginalUserFromUser(user)
if err != nil { _, err = syncer.addUser(newOUser)
panic(err) if err != nil {
panic(err)
}
fmt.Printf("New oUser: %v\n", newOUser)
} }
fmt.Printf("New oUser: %v\n", newOUser)
} }
} }
} }

View File

@@ -170,6 +170,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
originalUser := &OriginalUser{ originalUser := &OriginalUser{
Address: []string{}, Address: []string{},
Properties: map[string]string{}, Properties: map[string]string{},
Groups: []string{},
} }
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {

View File

@@ -160,7 +160,8 @@ type User struct {
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"` PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"` RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes"`
TotpSecret string `xorm:"varchar(100)" json:"totpSecret"`
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"` MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
MfaEmailEnabled bool `json:"mfaEmailEnabled"` MfaEmailEnabled bool `json:"mfaEmailEnabled"`
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"` MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
@@ -432,16 +433,19 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
if user.AccessSecret != "" { if user.AccessSecret != "" {
user.AccessSecret = "***" user.AccessSecret = "***"
} }
if user.RecoveryCodes != nil {
user.RecoveryCodes = nil
}
if user.ManagedAccounts != nil { if user.ManagedAccounts != nil {
for _, manageAccount := range user.ManagedAccounts { for _, manageAccount := range user.ManagedAccounts {
manageAccount.Password = "***" manageAccount.Password = "***"
} }
} }
if user.TotpSecret != "" {
user.TotpSecret = ""
}
if user.RecoveryCodes != nil {
user.RecoveryCodes = nil
}
return user, nil return user, nil
} }
@@ -828,11 +832,14 @@ func userChangeTrigger(oldName string, newName string) error {
} }
func (user *User) IsMfaEnabled() bool { func (user *User) IsMfaEnabled() bool {
if user == nil {
return false
}
return user.PreferredMfaType != "" return user.PreferredMfaType != ""
} }
func (user *User) GetPreferredMfaProps(masked bool) *MfaProps { func (user *User) GetPreferredMfaProps(masked bool) *MfaProps {
if user.PreferredMfaType == "" { if user == nil || user.PreferredMfaType == "" {
return nil return nil
} }
return user.GetMfaProps(user.PreferredMfaType, masked) return user.GetMfaProps(user.PreferredMfaType, masked)

View File

@@ -55,7 +55,7 @@ func StaticFilter(ctx *context.Context) {
path += urlPath path += urlPath
} }
path2 := strings.TrimLeft(path, "web/build/images/") path2 := strings.TrimPrefix(path, "web/build/images/")
if util.FileExist(path2) { if util.FileExist(path2) {
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2) makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2)
return return

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,24 @@ paths:
description: "" description: ""
schema: schema:
$ref: '#/definitions/object.OidcDiscovery' $ref: '#/definitions/object.OidcDiscovery'
/api/add-adapter:
post:
tags:
- Adapter API
description: add adapter
operationId: ApiController.AddCasbinAdapter
parameters:
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-application: /api/add-application:
post: post:
tags: tags:
@@ -82,6 +100,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-group:
post:
tags:
- Group API
description: add group
operationId: ApiController.AddGroup
parameters:
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-ldap: /api/add-ldap:
post: post:
tags: tags:
@@ -272,6 +308,18 @@ paths:
tags: tags:
- Resource API - Resource API
operationId: ApiController.AddResource operationId: ApiController.AddResource
parameters:
- in: body
name: resource
description: Resource object
required: true
schema:
$ref: '#/definitions/object.Resource'
responses:
"200":
description: Success or error
schema:
$ref: '#/definitions/controllers.Response'
/api/add-role: /api/add-role:
post: post:
tags: tags:
@@ -386,6 +434,11 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-user-keys:
post:
tags:
- User API
operationId: ApiController.AddUserkeys
/api/add-webhook: /api/add-webhook:
post: post:
tags: tags:
@@ -506,12 +559,12 @@ paths:
post: post:
tags: tags:
- Enforce API - Enforce API
description: perform enforce description: Call Casbin BatchEnforce API
operationId: ApiController.BatchEnforce operationId: ApiController.BatchEnforce
parameters: parameters:
- in: body - in: body
name: body name: body
description: casbin request array description: array of casbin requests
required: true required: true
schema: schema:
$ref: '#/definitions/object.CasbinRequest' $ref: '#/definitions/object.CasbinRequest'
@@ -555,6 +608,24 @@ paths:
tags: tags:
- User API - User API
operationId: ApiController.CheckUserPassword operationId: ApiController.CheckUserPassword
/api/delete-adapter:
post:
tags:
- Adapter API
description: delete adapter
operationId: ApiController.DeleteCasbinAdapter
parameters:
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-application: /api/delete-application:
post: post:
tags: tags:
@@ -609,6 +680,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/delete-group:
post:
tags:
- Group API
description: delete group
operationId: ApiController.DeleteGroup
parameters:
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-ldap: /api/delete-ldap:
post: post:
tags: tags:
@@ -792,6 +881,18 @@ paths:
tags: tags:
- Resource API - Resource API
operationId: ApiController.DeleteResource operationId: ApiController.DeleteResource
parameters:
- in: body
name: resource
description: Resource object
required: true
schema:
$ref: '#/definitions/object.Resource'
responses:
"200":
description: Success or error
schema:
$ref: '#/definitions/controllers.Response'
/api/delete-role: /api/delete-role:
post: post:
tags: tags:
@@ -923,12 +1024,12 @@ paths:
post: post:
tags: tags:
- Enforce API - Enforce API
description: perform enforce description: Call Casbin Enforce API
operationId: ApiController.Enforce operationId: ApiController.Enforce
parameters: parameters:
- in: body - in: body
name: body name: body
description: casbin request description: Casbin request
required: true required: true
schema: schema:
$ref: '#/definitions/object.CasbinRequest' $ref: '#/definitions/object.CasbinRequest'
@@ -960,6 +1061,42 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/get-adapter:
get:
tags:
- Adapter API
description: get adapter
operationId: ApiController.GetCasbinAdapter
parameters:
- in: query
name: id
description: The id ( owner/name ) of the adapter
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Adapter'
/api/get-adapters:
get:
tags:
- Adapter API
description: get adapters
operationId: ApiController.GetCasbinAdapters
parameters:
- in: query
name: owner
description: The owner of adapters
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Adapter'
/api/get-app-login: /api/get-app-login:
get: get:
tags: tags:
@@ -1183,6 +1320,42 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Cert' $ref: '#/definitions/object.Cert'
/api/get-group:
get:
tags:
- Group API
description: get group
operationId: ApiController.GetGroup
parameters:
- in: query
name: id
description: The id ( owner/name ) of the group
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Group'
/api/get-groups:
get:
tags:
- Group API
description: get groups
operationId: ApiController.GetGroups
parameters:
- in: query
name: owner
description: The owner of groups
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Group'
/api/get-ldap: /api/get-ldap:
get: get:
tags: tags:
@@ -1327,7 +1500,7 @@ paths:
get: get:
tags: tags:
- Organization API - Organization API
description: get all organization names description: get all organization name and displayName
operationId: ApiController.GetOrganizationNames operationId: ApiController.GetOrganizationNames
parameters: parameters:
- in: query - in: query
@@ -1521,7 +1694,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.pricing' $ref: '#/definitions/object.Pricing'
/api/get-pricings: /api/get-pricings:
get: get:
tags: tags:
@@ -1669,12 +1842,67 @@ paths:
get: get:
tags: tags:
- Resource API - Resource API
description: get resource
operationId: ApiController.GetResource operationId: ApiController.GetResource
parameters:
- in: query
name: id
description: The id ( owner/name ) of resource
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.Resource'
/api/get-resources: /api/get-resources:
get: get:
tags: tags:
- Resource API - Resource API
description: get resources
operationId: ApiController.GetResources operationId: ApiController.GetResources
parameters:
- in: query
name: owner
description: Owner
required: true
type: string
- in: query
name: user
description: User
required: true
type: string
- in: query
name: pageSize
description: Page Size
type: integer
- in: query
name: p
description: Page Number
type: integer
- in: query
name: field
description: Field
type: string
- in: query
name: value
description: Value
type: string
- in: query
name: sortField
description: Sort Field
type: string
- in: query
name: sortOrder
description: Sort Order
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Resource'
/api/get-role: /api/get-role:
get: get:
tags: tags:
@@ -1793,7 +2021,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.subscription' $ref: '#/definitions/object.Subscription'
/api/get-subscriptions: /api/get-subscriptions:
get: get:
tags: tags:
@@ -2172,7 +2400,7 @@ paths:
"200": "200":
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
/api/login/oauth/access_token: /api/login/oauth/access_token:
post: post:
tags: tags:
@@ -2351,9 +2579,9 @@ paths:
operationId: ApiController.MfaSetupInitiate operationId: ApiController.MfaSetupInitiate
responses: responses:
"200": "200":
description: Response object description: The Response object
schema: schema:
$ref: '#/definitions/The' $ref: '#/definitions/controllers.Response'
/api/mfa/setup/verify: /api/mfa/setup/verify:
post: post:
tags: tags:
@@ -2480,6 +2708,29 @@ paths:
post: post:
tags: tags:
- Login API - Login API
/api/update-adapter:
post:
tags:
- Adapter API
description: update adapter
operationId: ApiController.UpdateCasbinAdapter
parameters:
- in: query
name: id
description: The id ( owner/name ) of the adapter
required: true
type: string
- in: body
name: body
description: The details of the adapter
required: true
schema:
$ref: '#/definitions/object.Adapter'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-application: /api/update-application:
post: post:
tags: tags:
@@ -2549,6 +2800,29 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/update-group:
post:
tags:
- Group API
description: update group
operationId: ApiController.UpdateGroup
parameters:
- in: query
name: id
description: The id ( owner/name ) of the group
required: true
type: string
- in: body
name: body
description: The details of the group
required: true
schema:
$ref: '#/definitions/object.Group'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/update-ldap: /api/update-ldap:
post: post:
tags: tags:
@@ -2765,7 +3039,25 @@ paths:
post: post:
tags: tags:
- Resource API - Resource API
description: get resource
operationId: ApiController.UpdateResource operationId: ApiController.UpdateResource
parameters:
- in: query
name: id
description: The id ( owner/name ) of resource
required: true
type: string
- in: body
name: resource
description: The resource object
required: true
schema:
$ref: '#/definitions/object.Resource'
responses:
"200":
description: Success or error
schema:
$ref: '#/definitions/controllers.Response'
/api/update-role: /api/update-role:
post: post:
tags: tags:
@@ -2928,6 +3220,53 @@ paths:
tags: tags:
- Resource API - Resource API
operationId: ApiController.UploadResource operationId: ApiController.UploadResource
parameters:
- in: query
name: owner
description: Owner
required: true
type: string
- in: query
name: user
description: User
required: true
type: string
- in: query
name: application
description: Application
required: true
type: string
- in: query
name: tag
description: Tag
type: string
- in: query
name: parent
description: Parent
type: string
- in: query
name: fullFilePath
description: Full File Path
required: true
type: string
- in: query
name: createdTime
description: Created Time
type: string
- in: query
name: description
description: Description
type: string
- in: formData
name: file
description: Resource file
required: true
type: file
responses:
"200":
description: FileUrl, objectKey
schema:
$ref: '#/definitions/object.Resource'
/api/user: /api/user:
get: get:
tags: tags:
@@ -2994,7 +3333,7 @@ paths:
"200": "200":
description: '"The Response object"' description: '"The Response object"'
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
/api/webauthn/signup/begin: /api/webauthn/signup/begin:
get: get:
tags: tags:
@@ -3023,23 +3362,14 @@ paths:
"200": "200":
description: '"The Response object"' description: '"The Response object"'
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/controllers.Response'
definitions: definitions:
1225.0xc0002e2ae0.false:
title: "false"
type: object
1260.0xc0002e2b10.false:
title: "false"
type: object
LaravelResponse: LaravelResponse:
title: LaravelResponse title: LaravelResponse
type: object type: object
Response: Response:
title: Response title: Response
type: object type: object
The:
title: The
type: object
controllers.AuthForm: controllers.AuthForm:
title: AuthForm title: AuthForm
type: object type: object
@@ -3064,9 +3394,13 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/1225.0xc0002e2ae0.false' additionalProperties:
description: support string, struct or []struct
type: string
data2: data2:
$ref: '#/definitions/1260.0xc0002e2b10.false' additionalProperties:
description: support string, struct or []struct
type: string
msg: msg:
type: string type: string
name: name:
@@ -3090,8 +3424,8 @@ definitions:
jose.JSONWebKey: jose.JSONWebKey:
title: JSONWebKey title: JSONWebKey
type: object type: object
object.&{179844 0xc000a02f90 false}: object:
title: '&{179844 0xc000a02f90 false}' title: object
type: object type: object
object.AccountItem: object.AccountItem:
title: AccountItem title: AccountItem
@@ -3210,6 +3544,10 @@ definitions:
$ref: '#/definitions/object.SignupItem' $ref: '#/definitions/object.SignupItem'
signupUrl: signupUrl:
type: string type: string
tags:
type: array
items:
type: string
termsOfUse: termsOfUse:
type: string type: string
themeData: themeData:
@@ -3220,7 +3558,7 @@ definitions:
title: CasbinRequest title: CasbinRequest
type: array type: array
items: items:
$ref: '#/definitions/object.&{179844 0xc000a02f90 false}' $ref: '#/definitions/object.CasbinRequest'
object.Cert: object.Cert:
title: Cert title: Cert
type: object type: object
@@ -3295,6 +3633,44 @@ definitions:
throughput: throughput:
type: number type: number
format: double format: double
object.Group:
title: Group
type: object
properties:
children:
type: array
items:
$ref: '#/definitions/object.Group'
contactEmail:
type: string
createdTime:
type: string
displayName:
type: string
isEnabled:
type: boolean
isTopGroup:
type: boolean
key:
type: string
manager:
type: string
name:
type: string
owner:
type: string
parentId:
type: string
title:
type: string
type:
type: string
updatedTime:
type: string
users:
type: array
items:
$ref: '#/definitions/object.User'
object.Header: object.Header:
title: Header title: Header
type: object type: object
@@ -3395,18 +3771,18 @@ definitions:
properties: properties:
countryCode: countryCode:
type: string type: string
id: enabled:
type: string type: boolean
isPreferred: isPreferred:
type: boolean type: boolean
mfaType:
type: string
recoveryCodes: recoveryCodes:
type: array type: array
items: items:
type: string type: string
secret: secret:
type: string type: string
type:
type: string
url: url:
type: string type: string
object.Model: object.Model:
@@ -3415,6 +3791,8 @@ definitions:
properties: properties:
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
isEnabled: isEnabled:
@@ -3520,6 +3898,10 @@ definitions:
type: string type: string
owner: owner:
type: string type: string
passwordOptions:
type: array
items:
type: string
passwordSalt: passwordSalt:
type: string type: string
passwordType: passwordType:
@@ -3607,6 +3989,8 @@ definitions:
type: string type: string
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
domains: domains:
@@ -3687,8 +4071,6 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
hasTrial:
type: boolean
isEnabled: isEnabled:
type: boolean type: boolean
name: name:
@@ -3792,8 +4174,6 @@ definitions:
type: string type: string
customLogo: customLogo:
type: string type: string
customScope:
type: string
customTokenUrl: customTokenUrl:
type: string type: string
customUserInfoUrl: customUserInfoUrl:
@@ -3835,6 +4215,8 @@ definitions:
type: string type: string
regionId: regionId:
type: string type: string
scopes:
type: string
signName: signName:
type: string type: string
subType: subType:
@@ -3845,6 +4227,9 @@ definitions:
type: string type: string
type: type:
type: string type: string
userMapping:
additionalProperties:
type: string
object.ProviderItem: object.ProviderItem:
title: ProviderItem title: ProviderItem
type: object type: object
@@ -3898,12 +4283,47 @@ definitions:
type: string type: string
user: user:
type: string type: string
object.Resource:
title: Resource
type: object
properties:
application:
type: string
createdTime:
type: string
description:
type: string
fileFormat:
type: string
fileName:
type: string
fileSize:
type: integer
format: int64
fileType:
type: string
name:
type: string
owner:
type: string
parent:
type: string
provider:
type: string
tag:
type: string
url:
type: string
user:
type: string
object.Role: object.Role:
title: Role title: Role
type: object type: object
properties: properties:
createdTime: createdTime:
type: string type: string
description:
type: string
displayName: displayName:
type: string type: string
domains: domains:
@@ -3995,6 +4415,8 @@ definitions:
type: string type: string
isEnabled: isEnabled:
type: boolean type: boolean
isReadOnly:
type: boolean
name: name:
type: string type: string
organization: organization:
@@ -4117,6 +4539,10 @@ definitions:
title: User title: User
type: object type: object
properties: properties:
accessKey:
type: string
accessSecret:
type: string
address: address:
type: array type: array
items: items:
@@ -4135,6 +4561,8 @@ definitions:
type: string type: string
avatar: avatar:
type: string type: string
avatarType:
type: string
azuread: azuread:
type: string type: string
baidu: baidu:
@@ -4205,6 +4633,10 @@ definitions:
type: string type: string
google: google:
type: string type: string
groups:
type: array
items:
type: string
hash: hash:
type: string type: string
heroku: heroku:
@@ -4272,6 +4704,10 @@ definitions:
$ref: '#/definitions/object.ManagedAccount' $ref: '#/definitions/object.ManagedAccount'
meetup: meetup:
type: string type: string
mfaEmailEnabled:
type: boolean
mfaPhoneEnabled:
type: boolean
microsoftonline: microsoftonline:
type: string type: string
multiFactorAuths: multiFactorAuths:
@@ -4312,6 +4748,8 @@ definitions:
type: string type: string
preHash: preHash:
type: string type: string
preferredMfaType:
type: string
properties: properties:
additionalProperties: additionalProperties:
type: string type: string
@@ -4320,6 +4758,10 @@ definitions:
ranking: ranking:
type: integer type: integer
format: int64 format: int64
recoveryCodes:
type: array
items:
type: string
region: region:
type: string type: string
roles: roles:
@@ -4356,6 +4798,8 @@ definitions:
type: string type: string
title: title:
type: string type: string
totpSecret:
type: string
tumblr: tumblr:
type: string type: string
twitch: twitch:
@@ -4404,12 +4848,14 @@ definitions:
type: string type: string
email: email:
type: string type: string
groups:
type: array
items:
type: string
iss: iss:
type: string type: string
name: name:
type: string type: string
organization:
type: string
phone: phone:
type: string type: string
picture: picture:
@@ -4448,12 +4894,6 @@ definitions:
type: string type: string
url: url:
type: string type: string
object.pricing:
title: pricing
type: object
object.subscription:
title: subscription
type: object
protocol.CredentialAssertion: protocol.CredentialAssertion:
title: CredentialAssertion title: CredentialAssertion
type: object type: object

View File

@@ -72,6 +72,9 @@ func UrlJoin(base string, path string) string {
func GetUrlPath(urlString string) string { func GetUrlPath(urlString string) string {
u, _ := url.Parse(urlString) u, _ := url.Parse(urlString)
if u == nil {
return ""
}
return u.Path return u.Path
} }

View File

@@ -43,6 +43,15 @@ func ContainsString(values []string, val string) bool {
return sort.SearchStrings(values, val) != len(values) return sort.SearchStrings(values, val) != len(values)
} }
func InSlice(slice []string, elem string) bool {
for _, val := range slice {
if val == elem {
return true
}
}
return false
}
func ReturnAnyNotEmpty(strs ...string) string { func ReturnAnyNotEmpty(strs ...string) string {
for _, str := range strs { for _, str := range strs {
if str != "" { if str != "" {

View File

@@ -289,3 +289,18 @@ func HasString(strs []string, str string) bool {
} }
return false return false
} }
func ParseIdToString(input interface{}) (string, error) {
switch v := input.(type) {
case string:
return v, nil
case int:
return strconv.Itoa(v), nil
case int64:
return strconv.FormatInt(v, 10), nil
case float64:
return strconv.FormatFloat(v, 'f', -1, 64), nil
default:
return "", fmt.Errorf("unsupported id type: %T", input)
}
}

View File

@@ -246,3 +246,23 @@ func TestSnakeString(t *testing.T) {
}) })
} }
} }
func TestParseId(t *testing.T) {
scenarios := []struct {
description string
input interface{}
expected interface{}
}{
{"Should be return 123456", "123456", "123456"},
{"Should be return 123456", 123456, "123456"},
{"Should be return 123456", int64(123456), "123456"},
{"Should be return 123456", float64(123456), "123456"},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := ParseIdToString(scenery.input)
assert.Nil(t, err, "The returned value not is expected")
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
})
}
}

View File

@@ -22,10 +22,18 @@ import (
"github.com/nyaruka/phonenumbers" "github.com/nyaruka/phonenumbers"
) )
var rePhone *regexp.Regexp var (
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
)
func init() { func init() {
rePhone, _ = regexp.Compile(`(\d{3})\d*(\d{4})`) rePhone, _ = regexp.Compile(`(\d{3})\d*(\d{4})`)
ReWhiteSpace, _ = regexp.Compile(`\s`)
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
} }
func IsEmailValid(email string) bool { func IsEmailValid(email string) bool {
@@ -70,3 +78,7 @@ func GetCountryCode(prefix string, phone string) (string, error) {
return countryCode, nil return countryCode, nil
} }
func FilterField(field string) bool {
return ReFieldWhiteList.MatchString(field)
}

View File

@@ -24,8 +24,6 @@
"i18next": "^19.8.9", "i18next": "^19.8.9",
"libphonenumber-js": "^1.10.19", "libphonenumber-js": "^1.10.19",
"moment": "^2.29.1", "moment": "^2.29.1",
"qrcode.react": "^3.1.0",
"qs": "^6.10.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-app-polyfill": "^3.0.0", "react-app-polyfill": "^3.0.0",
"react-codemirror2": "^7.2.1", "react-codemirror2": "^7.2.1",

View File

@@ -76,6 +76,10 @@ class AdapterEditPage extends React.Component {
getModels(organizationName) { getModels(organizationName) {
ModelBackend.getModels(organizationName) ModelBackend.getModels(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
models: res, models: res,
}); });

View File

@@ -25,8 +25,9 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class AdapterListPage extends BaseListPage { class AdapterListPage extends BaseListPage {
newAdapter() { newAdapter() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.props.account.owner, owner: owner,
name: `adapter_${randomName}`, name: `adapter_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
type: "Database", type: "Database",
@@ -87,7 +88,7 @@ class AdapterListPage extends BaseListPage {
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/adapters/${record.organization}/${text}`}> <Link to={`/adapters/${record.owner}/${text}`}>
{text} {text}
</Link> </Link>
); );
@@ -246,7 +247,7 @@ class AdapterListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
AdapterBackend.getAdapters(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) AdapterBackend.getAdapters(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -15,9 +15,11 @@
import React, {Component} from "react"; import React, {Component} from "react";
import "./App.less"; import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
import GroupTreePage from "./GroupTreePage"; import GroupTreePage from "./GroupTreePage";
import GroupEditPage from "./GroupEdit"; import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList"; import GroupListPage from "./GroupList";
import {MfaRuleRequired} from "./Setting";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs"; import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
@@ -64,6 +66,13 @@ import ProductBuyPage from "./ProductBuyPage";
import PaymentListPage from "./PaymentListPage"; import PaymentListPage from "./PaymentListPage";
import PaymentEditPage from "./PaymentEditPage"; import PaymentEditPage from "./PaymentEditPage";
import PaymentResultPage from "./PaymentResultPage"; import PaymentResultPage from "./PaymentResultPage";
import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import SessionListPage from "./SessionListPage";
import MfaSetupPage from "./auth/MfaSetupPage";
import SystemInfo from "./SystemInfo";
import AccountPage from "./account/AccountPage"; import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage"; import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./common/CustomGithubCorner"; import CustomGithubCorner from "./common/CustomGithubCorner";
@@ -73,19 +82,13 @@ import * as Auth from "./auth/Auth";
import EntryPage from "./EntryPage"; import EntryPage from "./EntryPage";
import * as AuthBackend from "./auth/AuthBackend"; import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import LanguageSelect from "./common/select/LanguageSelect";
import i18next from "i18next";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from "./auth/SamlCallback"; import SamlCallback from "./auth/SamlCallback";
import ModelListPage from "./ModelListPage"; import i18next from "i18next";
import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import LanguageSelect from "./common/select/LanguageSelect";
import ThemeSelect from "./common/select/ThemeSelect"; import ThemeSelect from "./common/select/ThemeSelect";
import SessionListPage from "./SessionListPage"; import OrganizationSelect from "./common/select/OrganizationSelect";
import MfaSetupPage from "./auth/MfaSetupPage";
const {Header, Footer, Content} = Layout; const {Header, Footer, Content} = Layout;
@@ -101,12 +104,13 @@ class App extends Component {
themeAlgorithm: ["default"], themeAlgorithm: ["default"],
themeData: Conf.ThemeDefault, themeData: Conf.ThemeDefault,
logo: this.getLogo(Setting.getAlgorithmNames(Conf.ThemeDefault)), logo: this.getLogo(Setting.getAlgorithmNames(Conf.ThemeDefault)),
requiredEnableMfa: false,
}; };
Setting.initServerUrl(); Setting.initServerUrl();
Auth.initAuthWithConfig({ Auth.initAuthWithConfig({
serverUrl: Setting.ServerUrl, serverUrl: Setting.ServerUrl,
appName: "app-built-in", // the application name of Casdoor itself, do not change it appName: Conf.DefaultApplication, // the application used in Casdoor root path: "/"
}); });
} }
@@ -115,16 +119,29 @@ class App extends Component {
this.getAccount(); this.getAccount();
} }
componentDidUpdate() { componentDidUpdate(prevProps, prevState, snapshot) {
// eslint-disable-next-line no-restricted-globals
const uri = location.pathname; const uri = location.pathname;
if (this.state.uri !== uri) { if (this.state.uri !== uri) {
this.updateMenuKey(); this.updateMenuKey();
} }
if (this.state.account !== prevState.account) {
const requiredEnableMfa = Setting.isRequiredEnableMfa(this.state.account, this.state.account?.organization);
this.setState({
requiredEnableMfa: requiredEnableMfa,
});
if (requiredEnableMfa === true) {
const mfaType = Setting.getMfaItemsByRules(this.state.account, this.state.account?.organization, [MfaRuleRequired])
.find((item) => item.rule === MfaRuleRequired)?.name;
if (mfaType !== undefined) {
this.props.history.push(`/mfa/setup?mfaType=${mfaType}`, {from: "/login"});
}
}
}
} }
updateMenuKey() { updateMenuKey() {
// eslint-disable-next-line no-restricted-globals
const uri = location.pathname; const uri = location.pathname;
this.setState({ this.setState({
uri: uri, uri: uri,
@@ -340,12 +357,16 @@ class App extends Component {
renderRightDropdown() { renderRightDropdown() {
const items = []; const items = [];
items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>, if (this.state.requiredEnableMfa === false) {
"/account" items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>,
)); "/account"
items.push(Setting.getItem(<><CommentOutlined />&nbsp;&nbsp;{i18next.t("account:Chats & Messages")}</>, ));
"/chat" if (Conf.EnableChatPages) {
)); items.push(Setting.getItem(<><CommentOutlined />&nbsp;&nbsp;{i18next.t("account:Chats & Messages")}</>,
"/chat"
));
}
}
items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>, items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>,
"/logout")); "/logout"));
@@ -396,6 +417,17 @@ class App extends Component {
}); });
}} /> }} />
<LanguageSelect languages={this.state.account.organization.languages} /> <LanguageSelect languages={this.state.account.organization.languages} />
{Setting.isAdminUser(this.state.account) && !Setting.isMobile() &&
<OrganizationSelect
initValue={Setting.getOrganization()}
withAll={true}
style={{marginRight: "20px", width: "180px", display: "flex"}}
onChange={(value) => {
Setting.setOrganization(value);
}}
className="select-box"
/>
}
</React.Fragment> </React.Fragment>
); );
} }
@@ -411,6 +443,14 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/")); res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
if (Setting.isLocalAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
if (Conf.ShowGithubCorner) {
res.push(Setting.getItem(<a href={"https://casdoor.com"}>
<span style={{fontWeight: "bold", backgroundColor: "rgba(87,52,211,0.4)", marginTop: "12px", paddingLeft: "5px", paddingRight: "5px", display: "flex", alignItems: "center", height: "40px", borderRadius: "5px"}}>
🚀 SaaS Hosting 🔥
</span>
</a>, "#"));
}
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
"/organizations")); "/organizations"));
@@ -449,13 +489,15 @@ class App extends Component {
"/providers" "/providers"
)); ));
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>, if (Conf.EnableChatPages) {
"/chats" res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
)); "/chats"
));
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>, res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
"/messages" "/messages"
)); ));
}
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
"/resources" "/resources"
@@ -476,7 +518,6 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
"/subscriptions" "/subscriptions"
)); ));
} }
if (Setting.isLocalAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
@@ -501,7 +542,6 @@ class App extends Component {
)); ));
if (Conf.EnableExtraPages) { if (Conf.EnableExtraPages) {
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
"/products" "/products"
)); ));
@@ -510,8 +550,8 @@ class App extends Component {
"/payments" "/payments"
)); ));
} }
} }
if (Setting.isAdminUser(this.state.account)) { if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo" "/sysinfo"
@@ -525,14 +565,6 @@ class App extends Component {
return res; return res;
} }
renderHomeIfLoggedIn(component) {
if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to="/" />;
} else {
return component;
}
}
renderLoginIfNotLoggedIn(component) { renderLoginIfNotLoggedIn(component) {
if (this.state.account === null) { if (this.state.account === null) {
sessionStorage.setItem("from", window.location.pathname); sessionStorage.setItem("from", window.location.pathname);
@@ -544,12 +576,6 @@ class App extends Component {
} }
} }
isStartPages() {
return window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/signup") ||
window.location.pathname === "/";
}
renderRouter() { renderRouter() {
return ( return (
<Switch> <Switch>
@@ -601,13 +627,13 @@ class App extends Component {
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} /> <Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} /> <Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} /> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} /> <Route exact path="/products/:organizationName/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} /> <Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} /> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} /> <Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} /> <Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage 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="/mfa-authentication/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} {...props} />)} /> <Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} /> <Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} /> <Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
<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.")}
@@ -638,18 +664,24 @@ class App extends Component {
if (key === "/swagger") { if (key === "/swagger") {
window.open(Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger", "_blank"); window.open(Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger", "_blank");
} else { } else {
this.props.history.push(key); if (this.state.requiredEnableMfa) {
Setting.showMessage("info", "Please enable MFA first!");
} else {
this.props.history.push(key);
}
} }
}; };
const menuStyleRight = Setting.isAdminUser(this.state.account) && !Setting.isMobile() ? "calc(180px + 260px)" : "260px";
return ( return (
<Layout id="parent-area"> <Layout id="parent-area">
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}> <EnableMfaNotification account={this.state.account} />
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}} >
{Setting.isMobile() ? null : ( {Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" style={{background: `url(${this.state.logo})`}} /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
)} )}
{Setting.isMobile() ? {this.state.requiredEnableMfa || (Setting.isMobile() ?
<React.Fragment> <React.Fragment>
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}> <Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
<Menu <Menu
@@ -670,9 +702,9 @@ class App extends Component {
items={this.getMenuItems()} items={this.getMenuItems()}
mode={"horizontal"} mode={"horizontal"}
selectedKeys={[this.state.selectedMenuKey]} selectedKeys={[this.state.selectedMenuKey]}
style={{position: "absolute", left: "145px", right: "260px"}} style={{position: "absolute", left: "145px", right: menuStyleRight}}
/> />
} )}
{ {
this.renderAccountMenu() this.renderAccountMenu()
} }
@@ -736,9 +768,11 @@ class App extends Component {
<EntryPage <EntryPage
account={this.state.account} account={this.state.account}
theme={this.state.themeData} theme={this.state.themeData}
onUpdateAccount={(account) => { onLoginSuccess={(redirectUrl) => {
this.onUpdateAccount(account); localStorage.setItem("mfaRedirectUrl", redirectUrl);
this.getAccount();
}} }}
onUpdateAccount={(account) => this.onUpdateAccount(account)}
updataThemeData={this.setTheme} updataThemeData={this.setTheme}
/> : /> :
<Switch> <Switch>

View File

@@ -118,20 +118,30 @@ class ApplicationEditPage extends React.Component {
getApplication() { getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((res) => {
if (application === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) { if (res.status === "error") {
application.grantTypes = ["authorization_code"]; Setting.showMessage("error", res.msg);
return;
} }
if (res.grantTypes === null || res.grantTypes === undefined || res.grantTypes.length === 0) {
res.grantTypes = ["authorization_code"];
}
if (res.tags === null || res.tags === undefined) {
res.tags = [];
}
this.setState({ this.setState({
application: application, application: res,
}); });
this.getCerts(application.organization); this.getCerts(res.organization);
}); });
} }
@@ -307,6 +317,18 @@ class ApplicationEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("application:Tags - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.application.tags} onChange={(value => {this.updateApplicationField("tags", value);})}>
{
this.state.application.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :

View File

@@ -28,18 +28,13 @@ class ApplicationListPage extends BaseListPage {
super(props); super(props);
} }
componentDidMount() {
this.setState({
organizationName: this.props.account.owner,
});
}
newApplication() { newApplication() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return { return {
owner: "admin", // this.props.account.applicationName, owner: "admin", // this.props.account.applicationName,
name: `application_${randomName}`, name: `application_${randomName}`,
organization: this.state.organizationName, organization: organizationName,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Application - ${randomName}`, displayName: `New Application - ${randomName}`,
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`, logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
@@ -273,8 +268,8 @@ class ApplicationListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText; const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder; const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true}); this.setState({loading: true});
(Setting.isAdminUser(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : (Setting.isDefaultOrganizationSelected(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) :
ApplicationBackend.getApplicationsByOrganization("admin", this.props.account.organization.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)) ApplicationBackend.getApplicationsByOrganization("admin", Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -17,6 +17,7 @@ import {Button, Input, Result, Space} from "antd";
import {SearchOutlined} from "@ant-design/icons"; import {SearchOutlined} from "@ant-design/icons";
import Highlighter from "react-highlight-words"; import Highlighter from "react-highlight-words";
import i18next from "i18next"; import i18next from "i18next";
import * as Setting from "./Setting";
class BaseListPage extends React.Component { class BaseListPage extends React.Component {
constructor(props) { constructor(props) {
@@ -35,6 +36,22 @@ class BaseListPage extends React.Component {
}; };
} }
handleOrganizationChange = () => {
const {pagination} = this.state;
this.fetch({pagination});
};
componentDidMount() {
window.addEventListener("storageOrganizationChanged", this.handleOrganizationChange);
if (!Setting.isAdminUser(this.props.account)) {
Setting.setOrganization("All");
}
}
componentWillUnmount() {
window.removeEventListener("storageOrganizationChanged", this.handleOrganizationChange);
}
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
const {pagination} = this.state; const {pagination} = this.state;
this.fetch({pagination}); this.fetch({pagination});

View File

@@ -44,14 +44,19 @@ class CertEditPage extends React.Component {
getCert() { getCert() {
CertBackend.getCert(this.state.owner, this.state.certName) CertBackend.getCert(this.state.owner, this.state.certName)
.then((cert) => { .then((res) => {
if (cert === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
cert: cert, cert: res,
}); });
}); });
} }

View File

@@ -28,6 +28,7 @@ class CertListPage extends BaseListPage {
} }
componentDidMount() { componentDidMount() {
super.componentDidMount();
this.setState({ this.setState({
owner: Setting.isAdminUser(this.props.account) ? "admin" : this.props.account.owner, owner: Setting.isAdminUser(this.props.account) ? "admin" : this.props.account.owner,
}); });
@@ -35,8 +36,9 @@ class CertListPage extends BaseListPage {
newCert() { newCert() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.owner : Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.state.owner, owner: owner,
name: `cert_${randomName}`, name: `cert_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Cert - ${randomName}`, displayName: `New Cert - ${randomName}`,
@@ -211,7 +213,7 @@ class CertListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={certs} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={certs} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Certs")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Certs")}&nbsp;&nbsp;&nbsp;&nbsp;
@@ -236,8 +238,8 @@ class CertListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
(Setting.isAdminUser(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) (Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: CertBackend.getCerts(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)) : CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -40,17 +40,21 @@ class ChatEditPage extends React.Component {
getChat() { getChat() {
ChatBackend.getChat("admin", this.state.chatName) ChatBackend.getChat("admin", this.state.chatName)
.then((chat) => { .then((res) => {
if (chat === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
chat: chat, chat: res,
}); });
this.getUsers(chat.organization); this.getUsers(res.organization);
}); });
} }
@@ -66,6 +70,11 @@ class ChatEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@@ -25,12 +25,13 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class ChatListPage extends BaseListPage { class ChatListPage extends BaseListPage {
newChat() { newChat() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return { return {
owner: "admin", // this.props.account.applicationName, owner: "admin", // this.props.account.applicationName,
name: `chat_${randomName}`, name: `chat_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
updatedTime: moment().format(), updatedTime: moment().format(),
organization: this.props.account.owner, organization: organizationName,
displayName: `New Chat - ${randomName}`, displayName: `New Chat - ${randomName}`,
type: "Single", type: "Single",
category: "Chat Category - 1", category: "Chat Category - 1",

View File

@@ -1,32 +1,34 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved. // Copyright 2021 The Casdoor Authors. All Rights Reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
export const ShowGithubCorner = false; export const DefaultApplication = "app-built-in";
export const GithubRepo = "https://github.com/casdoor/casdoor";
export const IsDemoMode = false; export const ShowGithubCorner = false;
export const IsDemoMode = false;
export const ForceLanguage = "";
export const DefaultLanguage = "en"; export const ForceLanguage = "";
export const DefaultLanguage = "en";
export const EnableExtraPages = true;
export const EnableExtraPages = true;
export const InitThemeAlgorithm = true; export const EnableChatPages = true;
export const ThemeDefault = {
themeType: "default", export const InitThemeAlgorithm = true;
colorPrimary: "#5734d3", export const ThemeDefault = {
borderRadius: 6, themeType: "default",
isCompact: false, colorPrimary: "#5734d3",
}; borderRadius: 6,
isCompact: false,
export const CustomFooter = null; };
export const CustomFooter = null;

View File

@@ -41,7 +41,7 @@ class EntryPage extends React.Component {
renderHomeIfLoggedIn(component) { renderHomeIfLoggedIn(component) {
if (this.props.account !== null && this.props.account !== undefined) { if (this.props.account !== null && this.props.account !== undefined) {
return <Redirect to="/" />; return <Redirect to={{pathname: "/", state: {from: "/login"}}} />;
} else { } else {
return component; return component;
} }
@@ -74,8 +74,12 @@ class EntryPage extends React.Component {
}); });
ApplicationBackend.getApplication("admin", pricing.application) ApplicationBackend.getApplication("admin", pricing.application)
.then((application) => { .then((res) => {
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault; if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
const themeData = res !== null ? Setting.getThemeData(res.organizationObj, res) : Conf.ThemeDefault;
this.props.updataThemeData(themeData); this.props.updataThemeData(themeData);
}); });
}; };

View File

@@ -49,8 +49,9 @@ class GroupListPage extends BaseListPage {
newGroup() { newGroup() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.props.account.owner, owner: owner,
name: `group_${randomName}`, name: `group_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
updatedTime: moment().format(), updatedTime: moment().format(),
@@ -251,7 +252,7 @@ class GroupListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
GroupBackend.getGroups(this.state.owner, false, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) GroupBackend.getGroups(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), false, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -45,17 +45,20 @@ class MessageEditPage extends React.Component {
getMessage() { getMessage() {
MessageBackend.getMessage("admin", this.state.messageName) MessageBackend.getMessage("admin", this.state.messageName)
.then((message) => { .then((res) => {
if (message === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
message: message, message: res,
}); });
this.getUsers(message.organization); this.getUsers(res.organization);
}); });
} }
@@ -80,6 +83,10 @@ class MessageEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@@ -25,11 +25,12 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class MessageListPage extends BaseListPage { class MessageListPage extends BaseListPage {
newMessage() { newMessage() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return { return {
owner: "admin", // this.props.account.messagename, owner: "admin", // this.props.account.messagename,
name: `message_${randomName}`, name: `message_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
organization: this.props.account.owner, organization: organizationName,
chat: "", chat: "",
replyTo: "", replyTo: "",
author: `${this.props.account.owner}/${this.props.account.name}`, author: `${this.props.account.owner}/${this.props.account.name}`,
@@ -208,7 +209,7 @@ class MessageListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
MessageBackend.getMessages("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) MessageBackend.getMessages("admin", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -47,14 +47,19 @@ class ModelEditPage extends React.Component {
getModel() { getModel() {
ModelBackend.getModel(this.state.organizationName, this.state.modelName) ModelBackend.getModel(this.state.organizationName, this.state.modelName)
.then((model) => { .then((res) => {
if (model === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
model: model, model: res,
}); });
}); });
} }

View File

@@ -40,8 +40,9 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`;
class ModelListPage extends BaseListPage { class ModelListPage extends BaseListPage {
newModel() { newModel() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.props.account.owner, owner: owner,
name: `model_${randomName}`, name: `model_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Model - ${randomName}`, displayName: `New Model - ${randomName}`,
@@ -202,7 +203,7 @@ class ModelListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
ModelBackend.getModels(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ModelBackend.getModels(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -68,9 +68,14 @@ class OrganizationEditPage extends React.Component {
getApplications() { getApplications() {
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName) ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName)
.then((applications) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
applications: applications, applications: res,
}); });
}); });
} }
@@ -422,6 +427,7 @@ class OrganizationEditPage extends React.Component {
this.setState({ this.setState({
organizationName: this.state.organization.name, organizationName: this.state.organization.name,
}); });
window.dispatchEvent(new Event("storageOrganizationsChanged"));
if (willExist) { if (willExist) {
this.props.history.push("/organizations"); this.props.history.push("/organizations");
@@ -443,6 +449,7 @@ class OrganizationEditPage extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.props.history.push("/organizations"); this.props.history.push("/organizations");
window.dispatchEvent(new Event("storageOrganizationsChanged"));
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
} }

View File

@@ -35,7 +35,7 @@ class OrganizationListPage extends BaseListPage {
passwordType: "plain", passwordType: "plain",
PasswordSalt: "", PasswordSalt: "",
passwordOptions: [], passwordOptions: [],
countryCodes: ["CN"], countryCodes: ["US"],
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "", defaultApplication: "",
tags: [], tags: [],
@@ -53,25 +53,40 @@ class OrganizationListPage extends BaseListPage {
{name: "Password", visible: true, viewRule: "Self", modifyRule: "Self"}, {name: "Password", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Email", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Email", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Phone", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Phone", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Country code", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Country/Region", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Country/Region", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Location", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Location", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Address", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Affiliation", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Affiliation", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Title", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Title", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "ID card type", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "ID card", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "ID card info", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Homepage", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Homepage", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Language", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Gender", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Birthday", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Education", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Score", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Karma", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Ranking", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "API key", label: i18next.t("general:API key")}, {name: "API key", label: i18next.t("general:API key"), modifyRule: "Self"},
{name: "Groups", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"}, {name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"}, {name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Groups", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"}, {name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"}, {name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is online", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is global admin", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is global admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
], ],
}; };
} }
@@ -83,6 +98,7 @@ class OrganizationListPage extends BaseListPage {
if (res.status === "ok") { if (res.status === "ok") {
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"}); this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added")); Setting.showMessage("success", i18next.t("general:Successfully added"));
window.dispatchEvent(new Event("storageOrganizationsChanged"));
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
} }
@@ -99,8 +115,11 @@ class OrganizationListPage extends BaseListPage {
Setting.showMessage("success", i18next.t("general:Successfully deleted")); Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {
...this.state.pagination,
total: this.state.pagination.total - 1},
}); });
window.dispatchEvent(new Event("storageOrganizationsChanged"));
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
} }
@@ -275,7 +294,7 @@ class OrganizationListPage extends BaseListPage {
value = params.passwordType; value = params.passwordType;
} }
this.setState({loading: true}); this.setState({loading: true});
OrganizationBackend.getOrganizations("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) OrganizationBackend.getOrganizations("admin", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -26,6 +26,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class PaymentListPage extends BaseListPage { class PaymentListPage extends BaseListPage {
newPayment() { newPayment() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return { return {
owner: "admin", owner: "admin",
name: `payment_${randomName}`, name: `payment_${randomName}`,
@@ -33,7 +34,7 @@ class PaymentListPage extends BaseListPage {
displayName: `New Payment - ${randomName}`, displayName: `New Payment - ${randomName}`,
provider: "provider_pay_paypal", provider: "provider_pay_paypal",
type: "PayPal", type: "PayPal",
organization: this.props.account.owner, organization: organizationName,
user: "admin", user: "admin",
productName: "computer-1", productName: "computer-1",
productDisplayName: "A notebook computer", productDisplayName: "A notebook computer",
@@ -265,7 +266,7 @@ class PaymentListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
PaymentBackend.getPayments("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) PaymentBackend.getPayments("admin", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -49,21 +49,26 @@ class PermissionEditPage extends React.Component {
getPermission() { getPermission() {
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName) PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
.then((permission) => { .then((res) => {
if (permission === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
permission: permission, permission: res,
}); });
this.getUsers(permission.owner); this.getUsers(res.owner);
this.getRoles(permission.owner); this.getRoles(res.owner);
this.getModels(permission.owner); this.getModels(res.owner);
this.getResources(permission.owner); this.getResources(res.owner);
this.getModel(permission.owner, permission.model); this.getModel(res.owner, res.model);
}); });
} }
@@ -79,6 +84,10 @@ class PermissionEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });
@@ -88,6 +97,10 @@ class PermissionEditPage extends React.Component {
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
roles: res, roles: res,
}); });
@@ -97,6 +110,10 @@ class PermissionEditPage extends React.Component {
getModels(organizationName) { getModels(organizationName) {
ModelBackend.getModels(organizationName) ModelBackend.getModels(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
models: res, models: res,
}); });
@@ -106,6 +123,10 @@ class PermissionEditPage extends React.Component {
getModel(organizationName, modelName) { getModel(organizationName, modelName) {
ModelBackend.getModel(organizationName, modelName) ModelBackend.getModel(organizationName, modelName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
model: res, model: res,
}); });

View File

@@ -26,8 +26,9 @@ import {UploadOutlined} from "@ant-design/icons";
class PermissionListPage extends BaseListPage { class PermissionListPage extends BaseListPage {
newPermission() { newPermission() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.props.account.owner, owner: owner,
name: `permission_${randomName}`, name: `permission_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Permission - ${randomName}`, displayName: `New Permission - ${randomName}`,
@@ -383,7 +384,7 @@ class PermissionListPage extends BaseListPage {
this.setState({loading: true}); this.setState({loading: true});
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter; const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
getPermissions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) getPermissions(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -64,6 +64,10 @@ class PlanEditPage extends React.Component {
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
roles: res, roles: res,
}); });
@@ -73,6 +77,10 @@ class PlanEditPage extends React.Component {
getUsers(organizationName) { getUsers(organizationName) {
UserBackend.getUsers(organizationName) UserBackend.getUsers(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
users: res, users: res,
}); });

View File

@@ -25,8 +25,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class PlanListPage extends BaseListPage { class PlanListPage extends BaseListPage {
newPlan() { newPlan() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner; const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: owner, owner: owner,
name: `plan_${randomName}`, name: `plan_${randomName}`,
@@ -197,7 +196,7 @@ class PlanListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={plans} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={plans} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Plans")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Plans")}&nbsp;&nbsp;&nbsp;&nbsp;
@@ -219,7 +218,7 @@ class PlanListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
PlanBackend.getPlans(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) PlanBackend.getPlans(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -49,22 +49,31 @@ class PricingEditPage extends React.Component {
getPricing() { getPricing() {
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName) PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
.then((pricing) => { .then((res) => {
if (pricing === null) { if (res === null) {
this.props.history.push("/404"); this.props.history.push("/404");
return; return;
} }
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
pricing: pricing, pricing: res,
}); });
this.getPlans(pricing.owner); this.getPlans(res.owner);
}); });
} }
getPlans(organizationName) { getPlans(organizationName) {
PlanBackend.getPlans(organizationName) PlanBackend.getPlans(organizationName)
.then((res) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
plans: res, plans: res,
}); });
@@ -109,9 +118,13 @@ class PricingEditPage extends React.Component {
getUserApplication() { getUserApplication() {
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName) ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
.then((application) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
application: application, application: res,
}); });
}); });
} }

View File

@@ -25,8 +25,7 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class PricingListPage extends BaseListPage { class PricingListPage extends BaseListPage {
newPricing() { newPricing() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner; const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: owner, owner: owner,
name: `pricing_${randomName}`, name: `pricing_${randomName}`,
@@ -166,7 +165,7 @@ class PricingListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={pricings} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={pricings} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Pricings")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Pricings")}&nbsp;&nbsp;&nbsp;&nbsp;
@@ -188,7 +187,7 @@ class PricingListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
PricingBackend.getPricings(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) PricingBackend.getPricings(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

View File

@@ -41,9 +41,14 @@ class ProductBuyPage extends React.Component {
} }
ProductBackend.getProduct(this.props.account.owner, this.state.productName) ProductBackend.getProduct(this.props.account.owner, this.state.productName)
.then((product) => { .then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({ this.setState({
product: product, product: res,
}); });
}); });
} }

View File

@@ -20,6 +20,7 @@ import i18next from "i18next";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import ProductBuyPage from "./ProductBuyPage"; import ProductBuyPage from "./ProductBuyPage";
import * as OrganizationBackend from "./backend/OrganizationBackend";
const {Option} = Select; const {Option} = Select;
@@ -39,11 +40,12 @@ class ProductEditPage extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getProduct(); this.getProduct();
this.getOrganizations();
this.getPaymentProviders(); this.getPaymentProviders();
} }
getProduct() { getProduct() {
ProductBackend.getProduct(this.props.account.owner, this.state.productName) ProductBackend.getProduct(this.state.organizationName, this.state.productName)
.then((product) => { .then((product) => {
if (product === null) { if (product === null) {
this.props.history.push("/404"); this.props.history.push("/404");
@@ -56,6 +58,15 @@ class ProductEditPage extends React.Component {
}); });
} }
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
});
}
getPaymentProviders() { getPaymentProviders() {
ProviderBackend.getProviders(this.props.account.owner) ProviderBackend.getProviders(this.props.account.owner)
.then((res) => { .then((res) => {
@@ -312,7 +323,7 @@ class ProductEditPage extends React.Component {
if (willExist) { if (willExist) {
this.props.history.push("/products"); this.props.history.push("/products");
} else { } else {
this.props.history.push(`/products/${this.state.product.name}`); this.props.history.push(`/products/${this.state.product.owner}/${this.state.product.name}`);
} }
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);

View File

@@ -26,8 +26,9 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class ProductListPage extends BaseListPage { class ProductListPage extends BaseListPage {
newProduct() { newProduct() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return { return {
owner: this.props.account.owner, owner: owner,
name: `product_${randomName}`, name: `product_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Product - ${randomName}`, displayName: `New Product - ${randomName}`,
@@ -47,7 +48,7 @@ class ProductListPage extends BaseListPage {
ProductBackend.addProduct(newProduct) ProductBackend.addProduct(newProduct)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"}); this.props.history.push({pathname: `/products/${newProduct.owner}/${newProduct.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added")); Setting.showMessage("success", i18next.t("general:Successfully added"));
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
@@ -88,7 +89,7 @@ class ProductListPage extends BaseListPage {
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/products/${text}`}> <Link to={`/products/${record.owner}/${text}`}>
{text} {text}
</Link> </Link>
); );
@@ -201,7 +202,7 @@ class ProductListPage extends BaseListPage {
size="small" size="small"
locale={{emptyText: " "}} locale={{emptyText: " "}}
dataSource={providers} dataSource={providers}
renderItem={(providerName, i) => { renderItem={(providerName, record, i) => {
return ( return (
<List.Item> <List.Item>
<div style={{display: "inline"}}> <div style={{display: "inline"}}>
@@ -247,7 +248,7 @@ class ProductListPage extends BaseListPage {
return ( return (
<div> <div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal <PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`} title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteProduct(index)} onConfirm={() => this.deleteProduct(index)}
@@ -268,7 +269,7 @@ class ProductListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={products} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={products} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp;
@@ -290,7 +291,7 @@ class ProductListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
ProductBackend.getProducts(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ProductBackend.getProducts(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
this.setState({ this.setState({
loading: false, loading: false,

Some files were not shown because too many files have changed in this diff Show More