Compare commits

...

47 Commits

Author SHA1 Message Date
35e1f8538e feat: fix panic when url.Parse() fails to parse URL (#2034) 2023-07-03 12:35:22 +08:00
30a14ff54a Fix null issue in getDefaultApplication() 2023-07-02 09:44:48 +08:00
1ab7a54133 Add DefaultApplication to conf 2023-07-02 09:15:22 +08:00
0e2dad35f3 Improve OrganizationSelect width 2023-06-30 02:04:44 +08:00
d31077a510 Remove conf values 2023-06-30 01:38:48 +08:00
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
91cb5f393a fix: fix Swagger docs page (#2025)
Signed-off-by: baihhh <2542274498@qq.com>
2023-06-30 00:48:39 +08:00
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
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
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
55784c68a3 Fix bug in /get-organizations API for org admin 2023-06-28 09:19:39 +08:00
8080b10b3b feat: show code signin page with password disabled (#2021) 2023-06-28 00:38:48 +08:00
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
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
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
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
1780620ef4 feat: handle error when permission not found (#2012) 2023-06-24 00:30:43 +08:00
5c968ed1ce Fix avatar cannot show issue 2023-06-23 15:53:41 +08:00
4016fc0f65 Add EnableChatPages to Conf 2023-06-23 11:35:34 +08:00
463b3ad976 fix: refactor and optimize Enforce() API (#2009) 2023-06-22 17:45:24 +08:00
b817a55f9f Fix error handling in SetPassword() 2023-06-22 14:51:56 +08:00
2c2ddfbb92 feat: optimize batch-enforce (#1997) 2023-06-22 14:40:09 +08:00
cadb533595 fix: unsafe verification username in CheckUsername (#2006)
* Customization of the initialization file

* Unsafe verification username in CheckUsername
2023-06-21 23:20:23 +08:00
a3b0f1fc74 feat: add owner to getUserByWechatId() 2023-06-21 21:29:53 +08:00
c391af4552 feat: improve MFA by using user's own Email and Phone (#2002)
* refactor: mfa

* fix: clean code

* fix: clean code

* fix: fix crash and improve robot
2023-06-21 18:56:37 +08:00
6ebca6dbe7 fix: Gosec/sec fixes (#2004)
* Customization of the initialization file

* fix: G601 (CWE-118): Implicit memory aliasing in for loop

* fix: G304 (CWE-22): Potential file inclusion via variable

* fix: G110 (CWE-409): Potential DoS vulnerability via decompression bomb
2023-06-21 18:55:20 +08:00
d505a4bf2d Remove org API calls in PasswordModal page 2023-06-21 00:49:03 +08:00
812bc5f6b2 Fix "nu" bug in GetLanguage() 2023-06-20 21:16:01 +08:00
f6f4d44444 feat: remove url.JoinPath() to be compatible with Go 1.17 (#1995) 2023-06-20 17:44:40 +08:00
926e73ed1b fix: fix "Accept-Language" parsing in request (#1996) 2023-06-20 17:43:48 +08:00
65716af89e feat: deprecate the user group relation table (#1990)
* fix: deprecate the user group relation table

* fix: clean code

* fix: fix trigger

* Update group.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-19 19:08:45 +08:00
d9c4f401e3 Fix error in downloadImage() 2023-06-19 17:52:01 +08:00
58aa7dba6a Fix groups in GetUserInfo() 2023-06-19 11:06:55 +08:00
29fc820578 Set User.groups to [] 2023-06-19 09:42:17 +08:00
d0ac265c91 fix: Deprecate the id field in group (#1987) 2023-06-18 23:33:13 +08:00
3562c36817 feat: Revert "fix: fix URL path in MinIO storage provider" (#1988)
This reverts commit 3699177837.
2023-06-18 23:08:40 +08:00
7884e10ca3 Refactor adapter's owner and organization 2023-06-18 00:22:12 +08:00
12dee8afd3 Fix null options in checkPasswordComplexity() 2023-06-17 22:38:02 +08:00
ac4b870309 Improve getFaviconFileBuffer() 2023-06-17 12:50:01 +08:00
b9140e2d5a Refactor refreshAvatar() 2023-06-17 11:43:46 +08:00
501f0dc74f Add user_avatar.go 2023-06-17 01:25:15 +08:00
a932b76fba Remove useless check in SetPassword() 2023-06-17 00:58:31 +08:00
0f57ac297b ci: add password complexity options to organization edit page (#1949)
* Support uploading roles and permissions via xlsx file.

* Template xlsx file for uploading users and permissions.

* reformat according to gofumpt.

* fix typo.

* add password complexity options to organization edit page.

* add password complexity options to organization edit page.

* Fixed Typos.

* Fixed Typos.

* feat:add password complexity options to organization edit page

* Auto generate i18n fields.

* Refactor code according to instructions

* Support autocheck passwd complexity in frontend when setting passwd in user edit page.

* feat:Backend Support for password validation in signup and forget page.

* feat:Frontend Support for password validation in signup and forget page.

* Add default password complex option & Update historical empty filed with default option.

* Migrator for field `password_complex_options` in org table.

* feat: support frontend password complex option check in user_edit/forget/signup page.

* frontend update for user edit page

* update i18n file

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-17 00:07:36 +08:00
edc6aa0d50 feat: get all role/permission of an user (#1978) 2023-06-16 22:44:21 +08:00
ebc0e0f2c9 Update i18n words 2023-06-16 22:06:54 +08:00
63dd2e781e Update backend i18n files 2023-06-16 21:55:08 +08:00
b01ba792bb Rename to accessSecret 2023-06-16 20:42:15 +08:00
174 changed files with 5679 additions and 3022 deletions

View File

@ -110,7 +110,7 @@ func GetLanguage(language string) string {
return "en"
}
if len(language) != 2 {
if len(language) != 2 || language == "nu" {
return "en"
} else {
return language

View File

@ -140,6 +140,13 @@ func (c *ApiController) Signup() {
username = id
}
password := authForm.Password
msg = object.CheckPasswordComplexityByOrg(organization, password)
if msg != "" {
c.ResponseError(msg)
return
}
initScore, err := organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
@ -363,6 +370,7 @@ func (c *ApiController) GetAccount() {
user.Permissions = object.GetMaskedPermissions(user.Permissions)
user.Roles = object.GetMaskedRoles(user.Roles)
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
organization, err := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
if err != nil {

View File

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

View File

@ -69,9 +69,18 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
return
}
// check user's tag
if !user.IsGlobalAdmin && !user.IsAdmin && len(application.Tags) > 0 {
// only users with the tag that is listed in the application tags can login
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.Password != "" && user.IsMfaEnabled() {
c.setMfaSessionData(&object.MfaSessionData{UserId: userId})
resp = &Response{Status: object.NextMfa, Data: user.GetPreferMfa(true)}
resp = &Response{Status: object.NextMfa, Data: user.GetPreferredMfaProps(true)}
return
}
@ -238,7 +247,7 @@ func isProxyProviderType(providerType string) bool {
// @Param code_challenge_method query string false code_challenge_method
// @Param code_challenge query string false code_challenge
// @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]
func (c *ApiController) Login() {
resp := &Response{}
@ -656,15 +665,20 @@ func (c *ApiController) Login() {
}
if authForm.Passcode != "" {
MfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferMfa(false))
err = MfaUtil.Verify(authForm.Passcode)
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
}
err = mfaUtil.Verify(authForm.Passcode)
if err != nil {
c.ResponseError(err.Error())
return
}
}
if authForm.RecoveryCode != "" {
err = object.RecoverTfs(user, authForm.RecoveryCode)
err = object.MfaRecover(user, authForm.RecoveryCode)
if err != nil {
c.ResponseError(err.Error())
return
@ -751,7 +765,8 @@ func (c *ApiController) HandleSamlLogin() {
func (c *ApiController) HandleOfficialAccountEvent() {
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
var data struct {
@ -761,7 +776,8 @@ func (c *ApiController) HandleOfficialAccountEvent() {
}
err = xml.Unmarshal(respBytes, &data)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
lock.Lock()

View File

@ -79,7 +79,8 @@ func (c *ApiController) getCurrentUser() *object.User {
} else {
user, err = object.GetUser(userId)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return nil
}
}
return user
@ -112,7 +113,8 @@ func (c *ApiController) GetSessionApplication() *object.Application {
}
application, err := object.GetApplicationByClientId(clientId.(string))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return nil
}
return application
@ -177,6 +179,10 @@ func (c *ApiController) SetSessionData(s *SessionData) {
}
func (c *ApiController) setMfaSessionData(data *object.MfaSessionData) {
if data == nil {
c.SetSession(object.MfaSessionUserId, nil)
return
}
c.SetSession(object.MfaSessionUserId, data.UserId)
}

View File

@ -23,6 +23,13 @@ import (
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
// GetCasbinAdapters
// @Title GetCasbinAdapters
// @Tag Adapter API
// @Description get adapters
// @Param owner query string true "The owner of adapters"
// @Success 200 {array} object.Adapter The Response object
// @router /get-adapters [get]
func (c *ApiController) GetCasbinAdapters() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
@ -31,9 +38,9 @@ func (c *ApiController) GetCasbinAdapters() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
adapters, err := object.GetCasbinAdapters(owner, organization)
adapters, err := object.GetCasbinAdapters(owner)
if err != nil {
c.ResponseError(err.Error())
return
@ -42,14 +49,14 @@ func (c *ApiController) GetCasbinAdapters() {
c.ResponseOk(adapters)
} else {
limit := util.ParseInt(limit)
count, err := object.GetCasbinAdapterCount(owner, organization, field, value)
count, err := object.GetCasbinAdapterCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
adapters, err := object.GetPaginationCasbinAdapters(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
adapters, err := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
@ -59,8 +66,16 @@ func (c *ApiController) GetCasbinAdapters() {
}
}
// GetCasbinAdapter
// @Title GetCasbinAdapter
// @Tag Adapter API
// @Description get adapter
// @Param id query string true "The id ( owner/name ) of the adapter"
// @Success 200 {object} object.Adapter The Response object
// @router /get-adapter [get]
func (c *ApiController) GetCasbinAdapter() {
id := c.Input().Get("id")
adapter, err := object.GetCasbinAdapter(id)
if err != nil {
c.ResponseError(err.Error())
@ -70,6 +85,14 @@ func (c *ApiController) GetCasbinAdapter() {
c.ResponseOk(adapter)
}
// UpdateCasbinAdapter
// @Title UpdateCasbinAdapter
// @Tag Adapter API
// @Description update adapter
// @Param id query string true "The id ( owner/name ) of the adapter"
// @Param body body object.Adapter true "The details of the adapter"
// @Success 200 {object} controllers.Response The Response object
// @router /update-adapter [post]
func (c *ApiController) UpdateCasbinAdapter() {
id := c.Input().Get("id")
@ -84,6 +107,13 @@ func (c *ApiController) UpdateCasbinAdapter() {
c.ServeJSON()
}
// AddCasbinAdapter
// @Title AddCasbinAdapter
// @Tag Adapter API
// @Description add adapter
// @Param body body object.Adapter true "The details of the adapter"
// @Success 200 {object} controllers.Response The Response object
// @router /add-adapter [post]
func (c *ApiController) AddCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
@ -96,6 +126,13 @@ func (c *ApiController) AddCasbinAdapter() {
c.ServeJSON()
}
// DeleteCasbinAdapter
// @Title DeleteCasbinAdapter
// @Tag Adapter API
// @Description delete adapter
// @Param body body object.Adapter true "The details of the adapter"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-adapter [post]
func (c *ApiController) DeleteCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)

View File

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

View File

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

View File

@ -44,14 +44,26 @@ func (c *ApiController) Enforce() {
}
if permissionId != "" {
enforceResult, err := object.Enforce(permissionId, &request)
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := []bool{}
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)
return
}
@ -76,8 +88,16 @@ func (c *ApiController) Enforce() {
}
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 {
c.ResponseError(err.Error())
return
@ -85,6 +105,7 @@ func (c *ApiController) Enforce() {
res = append(res, enforceResult)
}
c.ResponseOk(res)
}
@ -109,14 +130,32 @@ func (c *ApiController) BatchEnforce() {
}
if permissionId != "" {
enforceResult, err := object.BatchEnforce(permissionId, &requests)
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := [][]bool{}
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)
return
}
@ -135,8 +174,16 @@ func (c *ApiController) BatchEnforce() {
}
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 {
c.ResponseError(err.Error())
return
@ -144,6 +191,7 @@ func (c *ApiController) BatchEnforce() {
res = append(res, enforceResult)
}
c.ResponseOk(res)
}

View File

@ -53,7 +53,8 @@ func (c *ApiController) GetMessages() {
}
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedMessages(messages)
@ -89,7 +90,8 @@ func (c *ApiController) GetMessage() {
id := c.Input().Get("id")
message, err := object.GetMessage(id)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
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)
_, err := c.Ctx.ResponseWriter.Write([]byte(event))
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")
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
answer := stringBuilder.String()
@ -204,7 +208,8 @@ func (c *ApiController) GetMessageAnswer() {
message.Text = answer
_, err = object.UpdateMessage(message.GetId(), message)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
}

View File

@ -17,7 +17,6 @@ package controllers
import (
"net/http"
"github.com/beego/beego"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -29,12 +28,12 @@ import (
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @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]
func (c *ApiController) MfaSetupInitiate() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
authType := c.Ctx.Request.Form.Get("type")
mfaType := c.Ctx.Request.Form.Get("mfaType")
userId := util.GetId(owner, name)
if len(userId) == 0 {
@ -42,10 +41,11 @@ func (c *ApiController) MfaSetupInitiate() {
return
}
MfaUtil := object.GetMfaUtil(authType, nil)
MfaUtil := object.GetMfaUtil(mfaType, nil)
if MfaUtil == nil {
c.ResponseError("Invalid auth type")
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
@ -57,10 +57,7 @@ func (c *ApiController) MfaSetupInitiate() {
return
}
issuer := beego.AppConfig.String("appname")
accountName := user.GetId()
mfaProps, err := MfaUtil.Initiate(c.Ctx, issuer, accountName)
mfaProps, err := MfaUtil.Initiate(c.Ctx, user.GetId())
if err != nil {
c.ResponseError(err.Error())
return
@ -79,16 +76,20 @@ func (c *ApiController) MfaSetupInitiate() {
// @Success 200 {object} Response object
// @router /mfa/setup/verify [post]
func (c *ApiController) MfaSetupVerify() {
authType := c.Ctx.Request.Form.Get("type")
mfaType := c.Ctx.Request.Form.Get("mfaType")
passcode := c.Ctx.Request.Form.Get("passcode")
if authType == "" || passcode == "" {
if mfaType == "" || passcode == "" {
c.ResponseError("missing auth type or passcode")
return
}
MfaUtil := object.GetMfaUtil(authType, nil)
mfaUtil := object.GetMfaUtil(mfaType, nil)
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
}
err := MfaUtil.SetupVerify(c.Ctx, passcode)
err := mfaUtil.SetupVerify(c.Ctx, passcode)
if err != nil {
c.ResponseError(err.Error())
} else {
@ -108,7 +109,7 @@ func (c *ApiController) MfaSetupVerify() {
func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
authType := c.Ctx.Request.Form.Get("type")
mfaType := c.Ctx.Request.Form.Get("mfaType")
user, err := object.GetUser(util.GetId(owner, name))
if err != nil {
@ -121,8 +122,13 @@ func (c *ApiController) MfaSetupEnable() {
return
}
twoFactor := object.GetMfaUtil(authType, nil)
err = twoFactor.Enable(c.Ctx, user)
mfaUtil := object.GetMfaUtil(mfaType, nil)
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
}
err = mfaUtil.Enable(c.Ctx, user)
if err != nil {
c.ResponseError(err.Error())
return
@ -137,11 +143,9 @@ func (c *ApiController) MfaSetupEnable() {
// @Description: Delete MFA
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param id form string true "id of user's MFA props"
// @Success 200 {object} Response object
// @router /delete-mfa/ [post]
func (c *ApiController) DeleteMfa() {
id := c.Ctx.Request.Form.Get("id")
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
userId := util.GetId(owner, name)
@ -151,28 +155,18 @@ func (c *ApiController) DeleteMfa() {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
mfaProps := user.MultiFactorAuths[:0]
i := 0
for _, mfaProp := range mfaProps {
if mfaProp.Id != id {
mfaProps[i] = mfaProp
i++
}
}
user.MultiFactorAuths = mfaProps
_, err = object.UpdateUser(userId, user, []string{"multi_factor_auths"}, user.IsAdminUser())
err = object.DisabledMultiFactorAuth(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(user.MultiFactorAuths)
c.ResponseOk(object.GetAllMfaProps(user, true))
}
// SetPreferredMfa
@ -185,7 +179,7 @@ func (c *ApiController) DeleteMfa() {
// @Success 200 {object} Response object
// @router /set-preferred-mfa [post]
func (c *ApiController) SetPreferredMfa() {
id := c.Ctx.Request.Form.Get("id")
mfaType := c.Ctx.Request.Form.Get("mfaType")
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
userId := util.GetId(owner, name)
@ -195,29 +189,15 @@ func (c *ApiController) SetPreferredMfa() {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
mfaProps := user.MultiFactorAuths
for i, mfaProp := range user.MultiFactorAuths {
if mfaProp.Id == id {
mfaProps[i].IsPreferred = true
} else {
mfaProps[i].IsPreferred = false
}
}
_, err = object.UpdateUser(userId, user, []string{"multi_factor_auths"}, user.IsAdminUser())
err = object.SetPreferredMultiFactorAuth(user, mfaType)
if err != nil {
c.ResponseError(err.Error())
return
}
for i, mfaProp := range mfaProps {
mfaProps[i] = object.GetMaskedProps(mfaProp)
}
c.ResponseOk(mfaProps)
c.ResponseOk(object.GetAllMfaProps(user, true))
}

View File

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

View File

@ -37,17 +37,27 @@ func (c *ApiController) GetOrganizations() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organizationName := c.Input().Get("organizationName")
isGlobalAdmin := c.IsGlobalAdmin()
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 {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedOrganizations
c.ServeJSON()
} else {
isGlobalAdmin := c.IsGlobalAdmin()
if !isGlobalAdmin {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
if err != nil {
@ -64,7 +74,7 @@ func (c *ApiController) GetOrganizations() {
}
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 {
c.ResponseError(err.Error())
return

View File

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

View File

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

View File

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

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetPricings() {
if limit == "" || page == "" {
pricings, err := object.GetPricings(owner)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = pricings
@ -70,14 +71,15 @@ func (c *ApiController) GetPricings() {
// @Tag Pricing API
// @Description get 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]
func (c *ApiController) GetPricing() {
id := c.Input().Get("id")
pricing, err := object.GetPricing(id)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = pricing

View File

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

View File

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

View File

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

View File

@ -53,7 +53,8 @@ func (c *ApiController) GetResources() {
if limit == "" || page == "" {
resources, err := object.GetResources(owner, user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = resources
@ -86,7 +87,8 @@ func (c *ApiController) GetResource() {
resource, err := object.GetResource(id)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = resource

View File

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

View File

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

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetSubscriptions() {
if limit == "" || page == "" {
subscriptions, err := object.GetSubscriptions(owner)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = subscriptions
@ -70,14 +71,15 @@ func (c *ApiController) GetSubscriptions() {
// @Tag Subscription API
// @Description get 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]
func (c *ApiController) GetSubscription() {
id := c.Input().Get("id")
subscription, err := object.GetSubscription(id)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = subscription

View File

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

View File

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

View File

@ -41,7 +41,8 @@ func (c *ApiController) GetGlobalUsers() {
if limit == "" || page == "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGlobalUsers())
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedUsers
@ -80,7 +81,7 @@ func (c *ApiController) GetGlobalUsers() {
// @router /get-users [get]
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
groupId := c.Input().Get("groupId")
groupName := c.Input().Get("groupName")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
@ -89,8 +90,8 @@ func (c *ApiController) GetUsers() {
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
if groupId != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupId))
if groupName != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupName))
if err != nil {
c.ResponseError(err.Error())
return
@ -101,21 +102,22 @@ func (c *ApiController) GetUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
count, err := object.GetUserCount(owner, field, value, groupId)
count, err := object.GetUserCount(owner, field, value, groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupId)
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupName)
if err != nil {
c.ResponseError(err.Error())
return
@ -153,7 +155,8 @@ func (c *ApiController) GetUser() {
if userId != "" && owner != "" {
userFromUserId, err = object.GetUserByUserId(owner, userId)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
@ -165,7 +168,8 @@ func (c *ApiController) GetUser() {
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
if !organization.IsProfilePublic {
@ -190,17 +194,21 @@ func (c *ApiController) GetUser() {
}
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
err = object.ExtendUserWithRolesAndPermissions(user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
maskedUser, err := object.GetMaskedUser(user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedUser
@ -410,15 +418,12 @@ func (c *ApiController) SetPassword() {
c.ResponseError(c.T("user:New password cannot contain blank space."))
return
}
if len(newPassword) <= 5 {
c.ResponseError(c.T("user:New password must have at least 6 characters"))
return
}
userId := util.GetId(userOwner, userName)
requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first")
return
} else if code == "" {
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
@ -428,7 +433,7 @@ func (c *ApiController) SetPassword() {
}
} else {
if code != c.GetSession("verifiedCode") {
c.ResponseError("")
c.ResponseError(c.T("general:Missing parameter"))
return
}
c.SetSession("verifiedCode", "")
@ -448,6 +453,12 @@ func (c *ApiController) SetPassword() {
}
}
msg := object.CheckPasswordComplexity(targetUser, newPassword)
if msg != "" {
c.ResponseError(msg)
return
}
targetUser.Password = newPassword
_, err = object.SetUserField(targetUser, "password", targetUser.Password)
if err != nil {
@ -494,7 +505,8 @@ func (c *ApiController) GetSortedUsers() {
maskedUsers, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = maskedUsers
@ -554,8 +566,8 @@ func (c *ApiController) AddUserkeys() {
func (c *ApiController) RemoveUserFromGroup() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
groupId := c.Ctx.Request.Form.Get("groupId")
groupName := c.Ctx.Request.Form.Get("groupName")
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupId))
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupName))
c.ServeJSON()
}

View File

@ -19,13 +19,14 @@ import (
"io"
"mime/multipart"
"os"
"path/filepath"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func saveFile(path string, file *multipart.File) (err error) {
f, err := os.Create(path)
f, err := os.Create(filepath.Clean(path))
if err != nil {
return err
}

View File

@ -55,6 +55,9 @@ func (c *ApiController) T(error string) string {
// GetAcceptLanguage ...
func (c *ApiController) GetAcceptLanguage() string {
language := c.Ctx.Request.Header.Get("Accept-Language")
if len(language) > 2 {
language = language[0:2]
}
return conf.GetLanguage(language)
}
@ -94,7 +97,8 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user, err := object.GetUser(userId)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return nil, false
}
if user == nil {

View File

@ -93,9 +93,10 @@ func (c *ApiController) SendVerificationCode() {
}
}
// mfaSessionData != nil, means method is MfaSetupVerification
// mfaSessionData != nil, means method is MfaAuthVerification
if mfaSessionData := c.getMfaSessionData(); mfaSessionData != nil {
user, err = object.GetUser(mfaSessionData.UserId)
c.setMfaSessionData(nil)
if err != nil {
c.ResponseError(err.Error())
return
@ -129,7 +130,7 @@ func (c *ApiController) SendVerificationCode() {
} else if vform.Method == ResetVerification {
user = c.getCurrentUser()
} else if vform.Method == MfaAuthVerification {
mfaProps := user.GetPreferMfa(false)
mfaProps := user.GetPreferredMfaProps(false)
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret
}
@ -157,12 +158,14 @@ func (c *ApiController) SendVerificationCode() {
}
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
} else if vform.Method == ResetVerification {
} else if vform.Method == ResetVerification || vform.Method == MfaSetupVerification {
if vform.CountryCode == "" {
if user = c.getCurrentUser(); user != nil {
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
}
}
} else if vform.Method == MfaAuthVerification {
mfaProps := user.GetPreferMfa(false)
mfaProps := user.GetPreferredMfaProps(false)
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret
}

View File

@ -66,7 +66,7 @@ func (c *ApiController) WebAuthnSignupBegin() {
// @Tag User API
// @Description WebAuthn Registration Flow 2nd stage
// @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]
func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
@ -150,7 +150,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @Tag Login API
// @Description WebAuthn Login Flow 2nd stage
// @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]
func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType")

View File

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

View File

@ -17,6 +17,7 @@ package deployment
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/casdoor/casdoor/object"
@ -45,7 +46,7 @@ func uploadFolder(storageProvider oss.StorageInterface, folder string) {
continue
}
file, err := os.Open(path + filename)
file, err := os.Open(filepath.Clean(path + filename))
if err != nil {
panic(err)
}

6
go.mod
View File

@ -42,14 +42,14 @@ require (
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5
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_model v0.2.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.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/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible
@ -59,7 +59,7 @@ require (
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
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/xorm v1.1.6
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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
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/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
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/pkoukk/tiktoken-go v0.1.1 h1:jtkYlIECjyM9OW1w4rjPmTohK4arORP9V25y6TM6nXo=
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/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.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
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/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.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/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
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.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.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.7.0/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 provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
"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": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
@ -68,7 +69,8 @@
"Missing parameter": "Fehlender Parameter",
"Please login first": "Bitte zuerst einloggen",
"The user: %s doesn't exist": "Der Benutzer %s existiert nicht",
"don't support captchaProvider: ": "Unterstütze captchaProvider nicht:"
"don't support captchaProvider: ": "Unterstütze captchaProvider nicht:",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Es gibt einen LDAP-Server"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Anzeigename darf nicht leer sein",
"New password cannot contain blank space.": "Das neue Passwort darf keine Leerzeichen enthalten.",
"New password must have at least 6 characters": "Das neue Passwort muss mindestens 6 Zeichen haben"
"New password cannot contain blank space.": "Das neue Passwort darf keine Leerzeichen enthalten."
},
"user_upload": {
"Failed to import users": "Fehler beim Importieren von Benutzern"

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": "The provider: %s is not enabled for the application",
"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": {
"Service %s and %s do not match": "Service %s and %s do not match"
@ -68,7 +69,8 @@
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: "
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space.",
"New password must have at least 6 characters": "New password must have at least 6 characters"
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"

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 provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
"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": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
@ -68,7 +69,8 @@
"Missing parameter": "Parámetro faltante",
"Please login first": "Por favor, inicia sesión primero",
"The user: %s doesn't exist": "El usuario: %s no existe",
"don't support captchaProvider: ": "No apoyo a captchaProvider"
"don't support captchaProvider: ": "No apoyo a captchaProvider",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "El servidor LDAP existe"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "El nombre de pantalla no puede estar vacío",
"New password cannot contain blank space.": "La nueva contraseña no puede contener espacios en blanco.",
"New password must have at least 6 characters": "La nueva contraseña debe tener al menos 6 caracteres"
"New password cannot contain blank space.": "La nueva contraseña no puede contener espacios en blanco."
},
"user_upload": {
"Failed to import users": "Error al importar usuarios"

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 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",
"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": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
@ -68,7 +69,8 @@
"Missing parameter": "Paramètre manquant",
"Please login first": "Veuillez d'abord vous connecter",
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider"
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Le serveur LDAP existe"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Le nom d'affichage ne peut pas être vide",
"New password cannot contain blank space.": "Le nouveau mot de passe ne peut pas contenir d'espace.",
"New password must have at least 6 characters": "Le nouveau mot de passe doit comporter au moins 6 caractères"
"New password cannot contain blank space.": "Le nouveau mot de passe ne peut pas contenir d'espace."
},
"user_upload": {
"Failed to import users": "Échec de l'importation des utilisateurs"

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 provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
"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": {
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
@ -68,7 +69,8 @@
"Missing parameter": "Parameter hilang",
"Please login first": "Silahkan login terlebih dahulu",
"The user: %s doesn't exist": "Pengguna: %s tidak ada",
"don't support captchaProvider: ": "Jangan mendukung captchaProvider:"
"don't support captchaProvider: ": "Jangan mendukung captchaProvider:",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Server ldap ada"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Nama tampilan tidak boleh kosong",
"New password cannot contain blank space.": "Kata sandi baru tidak boleh mengandung spasi kosong.",
"New password must have at least 6 characters": "Kata sandi baru harus memiliki setidaknya 6 karakter"
"New password cannot contain blank space.": "Kata sandi baru tidak boleh mengandung spasi kosong."
},
"user_upload": {
"Failed to import users": "Gagal mengimpor pengguna"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません",
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
"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": {
"Service %s and %s do not match": "サービス%sと%sは一致しません"
@ -68,7 +69,8 @@
"Missing parameter": "不足しているパラメーター",
"Please login first": "最初にログインしてください",
"The user: %s doesn't exist": "そのユーザー:%sは存在しません",
"don't support captchaProvider: ": "captchaProviderをサポートしないでください"
"don't support captchaProvider: ": "captchaProviderをサポートしないでください",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "LDAPサーバーは存在します"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "表示名は空にできません",
"New password cannot contain blank space.": "新しいパスワードにはスペースを含めることはできません。",
"New password must have at least 6 characters": "新しいパスワードは少なくとも6文字必要です"
"New password cannot contain blank space.": "新しいパスワードにはスペースを含めることはできません。"
},
"user_upload": {
"Failed to import users": "ユーザーのインポートに失敗しました"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다",
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
"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": {
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
@ -68,7 +69,8 @@
"Missing parameter": "누락된 매개변수",
"Please login first": "먼저 로그인 하십시오",
"The user: %s doesn't exist": "사용자 %s는 존재하지 않습니다",
"don't support captchaProvider: ": "CaptchaProvider를 지원하지 마세요"
"don't support captchaProvider: ": "CaptchaProvider를 지원하지 마세요",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "LDAP 서버가 존재합니다"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "디스플레이 이름은 비어 있을 수 없습니다",
"New password cannot contain blank space.": "새 비밀번호에는 공백이 포함될 수 없습니다.",
"New password must have at least 6 characters": "새로운 비밀번호는 최소 6자 이상이어야 합니다"
"New password cannot contain blank space.": "새 비밀번호에는 공백이 포함될 수 없습니다."
},
"user_upload": {
"Failed to import users": "사용자 가져오기를 실패했습니다"

150
i18n/locales/pt/data.json Normal file
View File

@ -0,0 +1,150 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"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",
"Unauthorized operation": "Unauthorized operation",
"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": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
"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": {
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
@ -68,7 +69,8 @@
"Missing parameter": "Отсутствующий параметр",
"Please login first": "Пожалуйста, сначала войдите в систему",
"The user: %s doesn't exist": "Пользователь %s не существует",
"don't support captchaProvider: ": "не поддерживайте captchaProvider:"
"don't support captchaProvider: ": "не поддерживайте captchaProvider:",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "LDAP-сервер существует"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Отображаемое имя не может быть пустым",
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы.",
"New password must have at least 6 characters": "Новый пароль должен содержать не менее 6 символов"
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы."
},
"user_upload": {
"Failed to import users": "Не удалось импортировать пользователей"

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 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",
"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": {
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"
@ -68,7 +69,8 @@
"Missing parameter": "Thiếu tham số",
"Please login first": "Vui lòng đăng nhập trước",
"The user: %s doesn't exist": "Người dùng: %s không tồn tại",
"don't support captchaProvider: ": "không hỗ trợ captchaProvider: "
"don't support captchaProvider: ": "không hỗ trợ captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Máy chủ LDAP tồn tại"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "Tên hiển thị không thể trống",
"New password cannot contain blank space.": "Mật khẩu mới không thể chứa dấu trắng.",
"New password must have at least 6 characters": "Mật khẩu mới phải có ít nhất 6 ký tự"
"New password cannot contain blank space.": "Mật khẩu mới không thể chứa dấu trắng."
},
"user_upload": {
"Failed to import users": "Không thể nhập người dùng"

View File

@ -18,7 +18,8 @@
"The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式",
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
"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": {
"Service %s and %s do not match": "服务%s与%s不匹配"
@ -68,7 +69,8 @@
"Missing parameter": "缺少参数",
"Please login first": "请先登录",
"The user: %s doesn't exist": "用户: %s不存在",
"don't support captchaProvider: ": "不支持验证码提供商: "
"don't support captchaProvider: ": "不支持验证码提供商: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "LDAP服务器已存在"
@ -119,8 +121,7 @@
},
"user": {
"Display name cannot be empty": "显示名称不可为空",
"New password cannot contain blank space.": "新密码不可以包含空格",
"New password must have at least 6 characters": "新密码至少需要6位字符"
"New password cannot contain blank space.": "新密码不可以包含空格"
},
"user_upload": {
"Failed to import users": "导入用户失败"

View File

@ -198,12 +198,22 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, 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)
request, err := http.NewRequest("GET", accessTokenUrl, nil)
if err != nil {
return "", err
}
client := new(http.Client)
resp, err := client.Do(request)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var data struct {
ExpireIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
@ -212,20 +222,30 @@ func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (
if err != nil {
return "", err
}
return data.AccessToken, nil
}
func GetWechatOfficialAccountQRCode(clientId string, clientSecret string) (string, error) {
accessToken, err := GetWechatOfficialAccountAccessToken(clientId, clientSecret)
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))
qrCodeUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", accessToken)
requeset, err := http.NewRequest("POST", qrCodeUrl, bodyData)
if err != nil {
return "", err
}
resp, err := client.Do(requeset)
if err != nil {
return "", err
}
defer resp.Body.Close()
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err

View File

@ -8,6 +8,7 @@
"favicon": "",
"passwordType": "plain",
"passwordSalt": "",
"passwordOptions": ["AtLeast6"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"],
"defaultAvatar": "",
"defaultApplication": "",

View File

@ -145,11 +145,6 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(UserGroupRelation))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Role))
if err != nil {
panic(err)
@ -280,7 +275,7 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if filterField(field) {
if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
@ -308,7 +303,7 @@ func GetSessionForUser(owner string, offset, limit int, field, value, sortField,
}
}
if field != "" && value != "" {
if filterField(field) {
if util.FilterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}

View File

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

View File

@ -1,167 +0,0 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/color"
"image/png"
"io"
"net/http"
"strings"
"github.com/fogleman/gg"
)
func hasGravatar(client *http.Client, email string) (bool, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL with d=404 parameter
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
// Send a request to Gravatar
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Check if the user has a custom Gravatar image
if resp.StatusCode == http.StatusOK {
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
return false, nil
} else {
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
}
}
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
// Download the image
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return nil, "", err
}
resp, err := client.Do(req)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
}
// Get the content type and determine the file extension
contentType := resp.Header.Get("Content-Type")
fileExtension := ""
switch contentType {
case "image/jpeg":
fileExtension = ".jpg"
case "image/png":
fileExtension = ".png"
case "image/gif":
fileExtension = ".gif"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}
// Save the image to a bytes.Buffer
buffer := &bytes.Buffer{}
_, err = io.Copy(buffer, resp.Body)
if err != nil {
return nil, "", err
}
return buffer, fileExtension, nil
}
func getColor(data []byte) color.RGBA {
r := int(data[0]) % 256
g := int(data[1]) % 256
b := int(data[2]) % 256
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
username = strings.TrimSpace(strings.ToLower(username))
hash := md5.New()
io.WriteString(hash, username)
hashedUsername := hash.Sum(nil)
// Define the size of the image
const imageSize = 420
const cellSize = imageSize / 7
// Create a new image
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
// Create a context
dc := gg.NewContextForRGBA(img)
// Set a background color
dc.SetColor(color.RGBA{240, 240, 240, 255})
dc.Clear()
// Get avatar color
avatarColor := getColor(hashedUsername)
// Draw cells
for i := 0; i < 7; i++ {
for j := 0; j < 7; j++ {
if (hashedUsername[i] >> uint(j) & 1) == 1 {
dc.SetColor(avatarColor)
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
dc.Fill()
}
}
}
// Save image to a bytes.Buffer
buffer := &bytes.Buffer{}
err := png.Encode(buffer, img)
if err != nil {
return nil, "", fmt.Errorf("failed to save image: %w", err)
}
return buffer, ".png", nil
}

View File

@ -30,7 +30,6 @@ type CasbinAdapter struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
Type string `xorm:"varchar(100)" json:"type"`
Model string `xorm:"varchar(100)" json:"model"`
@ -46,14 +45,14 @@ type CasbinAdapter struct {
Adapter *xormadapter.Adapter `xorm:"-" json:"-"`
}
func GetCasbinAdapterCount(owner, organization, field, value string) (int64, error) {
func GetCasbinAdapterCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&CasbinAdapter{Organization: organization})
return session.Count(&CasbinAdapter{})
}
func GetCasbinAdapters(owner string, organization string) ([]*CasbinAdapter, error) {
func GetCasbinAdapters(owner string) ([]*CasbinAdapter, error) {
adapters := []*CasbinAdapter{}
err := adapter.Engine.Where("owner = ? and organization = ?", owner, organization).Find(&adapters)
err := adapter.Engine.Desc("created_time").Find(&adapters, &CasbinAdapter{Owner: owner})
if err != nil {
return adapters, err
}
@ -61,10 +60,10 @@ func GetCasbinAdapters(owner string, organization string) ([]*CasbinAdapter, err
return adapters, nil
}
func GetPaginationCasbinAdapters(owner, organization string, page, limit int, field, value, sort, order string) ([]*CasbinAdapter, error) {
session := GetSession(owner, page, limit, field, value, sort, order)
func GetPaginationCasbinAdapters(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*CasbinAdapter, error) {
adapters := []*CasbinAdapter{}
err := session.Find(&adapters, &CasbinAdapter{Organization: organization})
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&adapters)
if err != nil {
return adapters, err
}
@ -214,6 +213,10 @@ func SyncPolicies(casbinAdapter *CasbinAdapter) ([]*xormadapter.CasbinRule, erro
return nil, err
}
if modelObj == nil {
return nil, fmt.Errorf("The model: %s does not exist", util.GetId(casbinAdapter.Owner, casbinAdapter.Model))
}
enforcer, err := initEnforcer(modelObj, casbinAdapter)
if err != nil {
return nil, err

View File

@ -16,7 +16,6 @@ package object
import (
"fmt"
"regexp"
"strings"
"time"
"unicode"
@ -28,21 +27,11 @@ import (
goldap "github.com/go-ldap/ldap/v3"
)
var (
reWhiteSpace *regexp.Regexp
reFieldWhiteList *regexp.Regexp
)
const (
SigninWrongTimesLimit = 5
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 {
if organization == nil {
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) {
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")
}
@ -203,6 +192,16 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
}
}
func CheckPasswordComplexityByOrg(organization *Organization, password string) string {
errorMsg := checkPasswordComplexity(password, organization.PasswordOptions)
return errorMsg
}
func CheckPasswordComplexity(user *User, password string) string {
organization, _ := GetOrganizationByUser(user)
return CheckPasswordComplexityByOrg(organization, password)
}
func checkLdapUserPassword(user *User, password string, lang string) string {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
@ -284,10 +283,6 @@ func CheckUserPassword(organization string, username string, password string, la
return user, ""
}
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
@ -386,14 +381,9 @@ func CheckUsername(username string, lang string) string {
return i18n.Translate(lang, "check:Username is too long (maximum is 39 characters).")
}
exclude, _ := regexp.Compile("^[\u0021-\u007E]+$")
if !exclude.MatchString(username) {
return ""
}
// 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.")
}

View File

@ -0,0 +1,98 @@
// 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 (
"regexp"
)
type ValidatorFunc func(password string) string
var (
regexLowerCase = regexp.MustCompile(`[a-z]`)
regexUpperCase = regexp.MustCompile(`[A-Z]`)
regexDigit = regexp.MustCompile(`\d`)
regexSpecial = regexp.MustCompile(`[!@#$%^&*]`)
)
func isValidOption_AtLeast6(password string) string {
if len(password) < 6 {
return "The password must have at least 6 characters"
}
return ""
}
func isValidOption_AtLeast8(password string) string {
if len(password) < 8 {
return "The password must have at least 8 characters"
}
return ""
}
func isValidOption_Aa123(password string) string {
hasLowerCase := regexLowerCase.MatchString(password)
hasUpperCase := regexUpperCase.MatchString(password)
hasDigit := regexDigit.MatchString(password)
if !hasLowerCase || !hasUpperCase || !hasDigit {
return "The password must contain at least one uppercase letter, one lowercase letter and one digit"
}
return ""
}
func isValidOption_SpecialChar(password string) string {
if !regexSpecial.MatchString(password) {
return "The password must contain at least one special character"
}
return ""
}
func isValidOption_NoRepeat(password string) string {
for i := 0; i < len(password)-1; i++ {
if password[i] == password[i+1] {
return "The password must not contain any repeated characters"
}
}
return ""
}
func checkPasswordComplexity(password string, options []string) string {
if len(password) == 0 {
return "Please input your password!"
}
if len(options) == 0 {
options = []string{"AtLeast6"}
}
checkers := map[string]ValidatorFunc{
"AtLeast6": isValidOption_AtLeast6,
"AtLeast8": isValidOption_AtLeast8,
"Aa123": isValidOption_Aa123,
"SpecialChar": isValidOption_SpecialChar,
"NoRepeat": isValidOption_NoRepeat,
}
for _, option := range options {
checkerFunc, ok := checkers[option]
if ok {
errorMsg := checkerFunc(password)
if errorMsg != "" {
return errorMsg
}
}
}
return ""
}

View File

@ -19,23 +19,23 @@ import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/builder"
"github.com/xorm-io/core"
)
type Group struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
Name string `xorm:"varchar(100) notnull pk unique index" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100) not null index" json:"id"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Manager string `xorm:"varchar(100)" json:"manager"`
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
Type string `xorm:"varchar(100)" json:"type"`
ParentId string `xorm:"varchar(100)" json:"parentId"`
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
Users *[]string `xorm:"-" json:"users"`
Users []*User `xorm:"-" json:"users"`
Title string `json:"title,omitempty"`
Key string `json:"key,omitempty"`
@ -95,24 +95,6 @@ func getGroup(owner string, name string) (*Group, error) {
}
}
func getGroupById(id string) (*Group, error) {
if id == "" {
return nil, nil
}
group := Group{Id: id}
existed, err := adapter.Engine.Get(&group)
if err != nil {
return nil, err
}
if existed {
return &group, nil
} else {
return nil, nil
}
}
func GetGroup(id string) (*Group, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getGroup(owner, name)
@ -125,7 +107,13 @@ func UpdateGroup(id string, group *Group) (bool, error) {
return false, err
}
group.UpdatedTime = util.GetCurrentTime()
if name != group.Name {
err := GroupChangeTrigger(name, group.Name)
if err != nil {
return false, err
}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(group)
if err != nil {
return false, err
@ -135,10 +123,6 @@ func UpdateGroup(id string, group *Group) (bool, error) {
}
func AddGroup(group *Group) (bool, error) {
if group.Id == "" {
group.Id = util.GenerateId()
}
affected, err := adapter.Engine.Insert(group)
if err != nil {
return false, err
@ -164,37 +148,20 @@ func DeleteGroup(group *Group) (bool, error) {
return false, err
}
if count, err := adapter.Engine.Where("parent_id = ?", group.Id).Count(&Group{}); err != nil {
if count, err := adapter.Engine.Where("parent_id = ?", group.Name).Count(&Group{}); err != nil {
return false, err
} else if count > 0 {
return false, errors.New("group has children group")
}
if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil {
if count, err := GetGroupUserCount(group.Name, "", ""); err != nil {
return false, err
} else if count > 0 {
return false, errors.New("group has users")
}
session := adapter.Engine.NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
return false, err
}
if _, err := session.Delete(&UserGroupRelation{GroupId: group.Id}); err != nil {
session.Rollback()
return false, err
}
affected, err := session.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
affected, err := adapter.Engine.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
if err != nil {
session.Rollback()
return false, err
}
if err := session.Commit(); err != nil {
return false, err
}
@ -215,9 +182,8 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
Key: group.Name,
Type: group.Type,
Owner: group.Owner,
Id: group.Id,
}
children := ConvertToTreeData(groups, group.Id)
children := ConvertToTreeData(groups, group.Name)
if len(children) > 0 {
node.Children = children
}
@ -226,3 +192,113 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
}
return treeData
}
func RemoveUserFromGroup(owner, name, groupName string) (bool, error) {
user, err := getUser(owner, name)
if err != nil {
return false, err
}
if user == nil {
return false, errors.New("user not exist")
}
user.Groups = util.DeleteVal(user.Groups, groupName)
affected, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return false, err
}
return affected != 0, err
}
func GetGroupUserCount(groupName string, field, value string) (int64, error) {
if field == "" && value == "" {
return adapter.Engine.Where(builder.Like{"`groups`", groupName}).
Count(&User{})
} else {
return adapter.Engine.Table("user").
Where(builder.Like{"`groups`", groupName}).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
func GetPaginationGroupUsers(groupName string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{}
session := adapter.Engine.Table("user").
Where(builder.Like{"`groups`", groupName})
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if field != "" && value != "" {
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%")
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
} else {
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
}
err := session.Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
func GetGroupUsers(groupName string) ([]*User, error) {
users := []*User{}
err := adapter.Engine.Table("user").
Where(builder.Like{"`groups`", groupName}).
Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
func GroupChangeTrigger(oldName, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
users := []*User{}
err = session.Where(builder.Like{"`groups`", oldName}).Find(&users)
if err != nil {
return err
}
for _, user := range users {
user.Groups = util.ReplaceVal(user.Groups, oldName, newName)
_, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return err
}
}
groups := []*Group{}
err = session.Where("parent_id = ?", oldName).Find(&groups)
for _, group := range groups {
group.ParentId = newName
_, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group)
if err != nil {
return err
}
}
err = session.Commit()
if err != nil {
return err
}
return nil
}

View File

@ -92,6 +92,7 @@ func initBuiltInOrganization() bool {
WebsiteUrl: "https://example.com",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", conf.GetConfigString("staticBaseUrl")),
PasswordType: "plain",
PasswordOptions: []string{"AtLeast6"},
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{},
@ -183,6 +184,7 @@ func initBuiltInApplication() {
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
},
Tags: []string{},
RedirectUris: []string{},
ExpireInHours: 168,
FormOffset: 2,

View File

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

View File

@ -22,14 +22,16 @@ import (
"github.com/beego/beego/context"
)
const MfaRecoveryCodesSession = "mfa_recovery_codes"
type MfaSessionData struct {
UserId string
}
type MfaProps struct {
Id string `json:"id"`
Enabled bool `json:"enabled"`
IsPreferred bool `json:"isPreferred"`
AuthType string `json:"type" form:"type"`
MfaType string `json:"mfaType" form:"mfaType"`
Secret string `json:"secret,omitempty"`
CountryCode string `json:"countryCode,omitempty"`
URL string `json:"url,omitempty"`
@ -37,13 +39,14 @@ type MfaProps struct {
}
type MfaInterface interface {
SetupVerify(ctx *context.Context, passCode string) error
Verify(passCode string) error
Initiate(ctx *context.Context, name1 string, name2 string) (*MfaProps, error)
Initiate(ctx *context.Context, userId string) (*MfaProps, error)
SetupVerify(ctx *context.Context, passcode string) error
Enable(ctx *context.Context, user *User) error
Verify(passcode string) error
}
const (
EmailType = "email"
SmsType = "sms"
TotpType = "app"
)
@ -54,28 +57,30 @@ const (
RequiredMfa = "RequiredMfa"
)
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
switch providerType {
func GetMfaUtil(mfaType string, config *MfaProps) MfaInterface {
switch mfaType {
case SmsType:
return NewSmsTwoFactor(config)
return NewSmsMfaUtil(config)
case EmailType:
return NewEmailMfaUtil(config)
case TotpType:
return nil
return NewTotpMfaUtil(config)
}
return nil
}
func RecoverTfs(user *User, recoveryCode string) error {
func MfaRecover(user *User, recoveryCode string) error {
hit := false
twoFactor := user.GetPreferMfa(false)
if len(twoFactor.RecoveryCodes) == 0 {
if len(user.RecoveryCodes) == 0 {
return fmt.Errorf("do not have recovery codes")
}
for _, code := range twoFactor.RecoveryCodes {
for _, code := range user.RecoveryCodes {
if code == recoveryCode {
hit = true
user.RecoveryCodes = util.DeleteVal(user.RecoveryCodes, code)
break
}
}
@ -83,30 +88,106 @@ func RecoverTfs(user *User, recoveryCode string) error {
return fmt.Errorf("recovery code not found")
}
affected, err := UpdateUser(user.GetId(), user, []string{"two_factor_auth"}, user.IsAdminUser())
_, err := UpdateUser(user.GetId(), user, []string{"recovery_codes"}, user.IsAdminUser())
if err != nil {
return err
}
if !affected {
return fmt.Errorf("")
return nil
}
func GetAllMfaProps(user *User, masked bool) []*MfaProps {
mfaProps := []*MfaProps{}
for _, mfaType := range []string{SmsType, EmailType, TotpType} {
mfaProps = append(mfaProps, user.GetMfaProps(mfaType, masked))
}
return mfaProps
}
func (user *User) GetMfaProps(mfaType string, masked bool) *MfaProps {
mfaProps := &MfaProps{}
if mfaType == SmsType {
if !user.MfaPhoneEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{
Enabled: user.MfaPhoneEnabled,
MfaType: mfaType,
CountryCode: user.CountryCode,
}
if masked {
mfaProps.Secret = util.GetMaskedPhone(user.Phone)
} else {
mfaProps.Secret = user.Phone
}
} else if mfaType == EmailType {
if !user.MfaEmailEnabled {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{
Enabled: user.MfaEmailEnabled,
MfaType: mfaType,
}
if masked {
mfaProps.Secret = util.GetMaskedEmail(user.Email)
} else {
mfaProps.Secret = user.Email
}
} else if mfaType == TotpType {
if user.TotpSecret == "" {
return &MfaProps{
Enabled: false,
MfaType: mfaType,
}
}
mfaProps = &MfaProps{
Enabled: true,
MfaType: mfaType,
}
if masked {
mfaProps.Secret = ""
} else {
mfaProps.Secret = user.TotpSecret
}
}
if user.PreferredMfaType == mfaType {
mfaProps.IsPreferred = true
}
return mfaProps
}
func DisabledMultiFactorAuth(user *User) error {
user.PreferredMfaType = ""
user.RecoveryCodes = []string{}
user.MfaPhoneEnabled = false
user.MfaEmailEnabled = false
user.TotpSecret = ""
_, err := updateUser(user.GetId(), user, []string{"preferred_mfa_type", "recovery_codes", "mfa_phone_enabled", "mfa_email_enabled", "totp_secret"})
if err != nil {
return err
}
return nil
}
func GetMaskedProps(props *MfaProps) *MfaProps {
maskedProps := &MfaProps{
AuthType: props.AuthType,
Id: props.Id,
IsPreferred: props.IsPreferred,
}
func SetPreferredMultiFactorAuth(user *User, mfaType string) error {
user.PreferredMfaType = mfaType
if props.AuthType == SmsType {
if !util.IsEmailValid(props.Secret) {
maskedProps.Secret = util.GetMaskedPhone(props.Secret)
} else {
maskedProps.Secret = util.GetMaskedEmail(props.Secret)
_, err := UpdateUser(user.GetId(), user, []string{"preferred_mfa_type"}, user.IsAdminUser())
if err != nil {
return err
}
}
return maskedProps
return nil
}

View File

@ -18,22 +18,35 @@ import (
"errors"
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/beego/beego/context"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
)
const (
MfaSmsCountryCodeSession = "mfa_country_code"
MfaSmsDestSession = "mfa_dest"
MfaSmsRecoveryCodesSession = "mfa_recovery_codes"
)
type SmsMfa struct {
Config *MfaProps
}
func (mfa *SmsMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
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},
}
return &mfaProps, nil
}
func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
dest := ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
countryCode := ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string)
@ -47,6 +60,45 @@ func (mfa *SmsMfa) SetupVerify(ctx *context.Context, passCode string) error {
return nil
}
func (mfa *SmsMfa) 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")
}
columns := []string{"recovery_codes", "preferred_mfa_type"}
user.RecoveryCodes = append(user.RecoveryCodes, recoveryCodes...)
if user.PreferredMfaType == "" {
user.PreferredMfaType = mfa.Config.MfaType
}
if mfa.Config.MfaType == SmsType {
user.MfaPhoneEnabled = true
columns = append(columns, "mfa_phone_enabled")
if user.Phone == "" {
user.Phone = ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
user.CountryCode = ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string)
columns = append(columns, "phone", "country_code")
}
} else if mfa.Config.MfaType == EmailType {
user.MfaEmailEnabled = true
columns = append(columns, "mfa_email_enabled")
if user.Email == "" {
user.Email = ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
columns = append(columns, "email")
}
}
_, err := UpdateUser(user.GetId(), user, columns, false)
if err != nil {
return err
}
return nil
}
func (mfa *SmsMfa) Verify(passCode string) error {
if !util.IsEmailValid(mfa.Config.Secret) {
mfa.Config.Secret, _ = util.GetE164Number(mfa.Config.Secret, mfa.Config.CountryCode)
@ -57,65 +109,21 @@ func (mfa *SmsMfa) Verify(passCode string) error {
return nil
}
func (mfa *SmsMfa) Initiate(ctx *context.Context, name string, secret string) (*MfaProps, error) {
recoveryCode, err := uuid.NewRandom()
if err != nil {
return nil, err
}
err = ctx.Input.CruSession.Set(MfaSmsRecoveryCodesSession, []string{recoveryCode.String()})
if err != nil {
return nil, err
}
mfaProps := MfaProps{
AuthType: SmsType,
RecoveryCodes: []string{recoveryCode.String()},
}
return &mfaProps, nil
}
func (mfa *SmsMfa) Enable(ctx *context.Context, user *User) error {
dest := ctx.Input.CruSession.Get(MfaSmsDestSession).(string)
recoveryCodes := ctx.Input.CruSession.Get(MfaSmsRecoveryCodesSession).([]string)
countryCode := ctx.Input.CruSession.Get(MfaSmsCountryCodeSession).(string)
if dest == "" || len(recoveryCodes) == 0 {
return fmt.Errorf("MFA dest or recovery codes is empty")
}
if !util.IsEmailValid(dest) {
mfa.Config.CountryCode = countryCode
}
mfa.Config.AuthType = SmsType
mfa.Config.Id = uuid.NewString()
mfa.Config.Secret = dest
mfa.Config.RecoveryCodes = recoveryCodes
for i, mfaProp := range user.MultiFactorAuths {
if mfaProp.Secret == mfa.Config.Secret {
user.MultiFactorAuths = append(user.MultiFactorAuths[:i], user.MultiFactorAuths[i+1:]...)
}
}
user.MultiFactorAuths = append(user.MultiFactorAuths, mfa.Config)
affected, err := UpdateUser(user.GetId(), user, []string{"multi_factor_auths"}, user.IsAdminUser())
if err != nil {
return err
}
if !affected {
return fmt.Errorf("failed to enable two factor authentication")
}
return nil
}
func NewSmsTwoFactor(config *MfaProps) *SmsMfa {
func NewSmsMfaUtil(config *MfaProps) *SmsMfa {
if config == nil {
config = &MfaProps{
AuthType: SmsType,
MfaType: SmsType,
}
}
return &SmsMfa{
Config: config,
}
}
func NewEmailMfaUtil(config *MfaProps) *SmsMfa {
if config == nil {
config = &MfaProps{
MfaType: EmailType,
}
}
return &SmsMfa{

133
object/mfa_totp.go Normal file
View File

@ -0,0 +1,133 @@
// 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).(string)
result := totp.Validate(passcode, secret)
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
}
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

@ -56,6 +56,7 @@ type Organization struct {
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
@ -103,10 +104,15 @@ func GetOrganizationsByFields(owner string, fields ...string) ([]*Organization,
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{}
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 {
return nil, err
}
@ -401,7 +407,6 @@ func organizationChangeTrigger(oldName string, newName string) error {
casbinAdapter := new(CasbinAdapter)
casbinAdapter.Owner = newName
casbinAdapter.Organization = newName
_, err = session.Where("owner=?", oldName).Update(casbinAdapter)
if err != nil {
return err

View File

@ -264,18 +264,48 @@ func DeletePermission(permission *Permission) (bool, error) {
return affected != 0, nil
}
func GetPermissionsByUser(userId string) ([]*Permission, error) {
func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
permissions := []*Permission{}
err := adapter.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
return nil, nil, err
}
for i := range permissions {
permissions[i].Users = nil
existedPerms := map[string]struct{}{}
for _, perm := range permissions {
perm.Users = nil
if _, ok := existedPerms[perm.Name]; !ok {
existedPerms[perm.Name] = struct{}{}
}
}
return permissions, nil
permFromRoles := []*Permission{}
roles, err := GetRolesByUser(userId)
if err != nil {
return nil, nil, err
}
for _, role := range roles {
perms := []*Permission{}
err := adapter.Engine.Where("roles like ?", "%"+role.Name+"\"%").Find(&perms)
if err != nil {
return nil, nil, err
}
permFromRoles = append(permFromRoles, perms...)
}
for _, perm := range permFromRoles {
perm.Users = nil
if _, ok := existedPerms[perm.Name]; !ok {
existedPerms[perm.Name] = struct{}{}
permissions = append(permissions, perm)
}
}
return permissions, roles, nil
}
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
@ -340,3 +370,24 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
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"
)
func getEnforcer(permission *Permission) *casbin.Enforcer {
func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforcer {
tableName := "permission_rule"
if len(permission.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter)
@ -77,8 +77,13 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
enforcer.SetAdapter(adapter)
policyFilterV5 := []string{permission.GetId()}
if len(permissionIDs) != 0 {
policyFilterV5 = permissionIDs
}
policyFilter := xormadapter.Filter{
V5: []string{permission.GetId()},
V5: policyFilterV5,
}
if !HasRoleDefinition(m) {
@ -241,33 +246,18 @@ func removePolicies(permission *Permission) {
type CasbinRequest = []interface{}
func Enforce(permissionId string, request *CasbinRequest) (bool, error) {
permission, err := GetPermission(permissionId)
if err != nil {
return false, err
}
enforcer := getEnforcer(permission)
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
enforcer := getEnforcer(permission, permissionIds...)
return enforcer.Enforce(*request...)
}
func BatchEnforce(permissionId string, requests *[]CasbinRequest) ([]bool, error) {
permission, err := GetPermission(permissionId)
if err != nil {
res := []bool{}
for i := 0; i < len(*requests); i++ {
res = append(res, false)
}
return res, err
}
enforcer := getEnforcer(permission)
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
enforcer := getEnforcer(permission, permissionIds...)
return enforcer.BatchEnforce(*requests)
}
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
permissions, err := GetPermissionsByUser(userId)
permissions, _, err := GetPermissionsAndRolesByUser(userId)
if err != nil {
panic(err)
}

View File

@ -43,6 +43,7 @@ func UploadPermissions(owner string, fileId string) (bool, error) {
newPermissions := []*Permission{}
for index, line := range table {
line := line
if index == 0 || parseLineItem(&line, 0) == "" {
continue
}

View File

@ -259,11 +259,22 @@ func GetRolesByUser(userId string) ([]*Role, error) {
return roles, err
}
for i := range roles {
roles[i].Users = nil
allRolesIds := make([]string, 0, len(roles))
for _, role := range roles {
allRolesIds = append(allRolesIds, role.GetId())
}
return roles, nil
allRoles, err := GetAncestorRoles(allRolesIds...)
if err != nil {
return nil, err
}
for i := range allRoles {
allRoles[i].Users = nil
}
return allRoles, nil
}
func roleChangeTrigger(oldName string, newName string) error {
@ -335,14 +346,22 @@ func GetRolesByNamePrefix(owner string, prefix string) ([]*Role, error) {
return roles, nil
}
func GetAncestorRoles(roleId string) ([]*Role, error) {
// GetAncestorRoles returns a list of roles that contain the given roleIds
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
var (
result []*Role
result = []*Role{}
roleMap = make(map[string]*Role)
visited = make(map[string]bool)
)
if len(roleIds) == 0 {
return result, nil
}
owner, _ := util.GetOwnerAndNameFromIdNoCheck(roleId)
for _, roleId := range roleIds {
visited[roleId] = true
}
owner, _ := util.GetOwnerAndNameFromIdNoCheck(roleIds[0])
allRoles, err := GetRoles(owner)
if err != nil {
@ -360,7 +379,7 @@ func GetAncestorRoles(roleId string) ([]*Role, error) {
result = append(result, r)
} else if !ok {
rId := r.GetId()
visited[rId] = containsRole(r, roleId, roleMap, visited)
visited[rId] = containsRole(r, roleMap, visited, roleIds...)
if visited[rId] {
result = append(result, r)
}
@ -370,19 +389,19 @@ func GetAncestorRoles(roleId string) ([]*Role, error) {
return result, nil
}
// containsRole is a helper function to check if a slice of roles contains a specific roleId
func containsRole(role *Role, roleId string, roleMap map[string]*Role, visited map[string]bool) bool {
// containsRole is a helper function to check if a roles is related to any role in the given list roles
func containsRole(role *Role, roleMap map[string]*Role, visited map[string]bool, roleIds ...string) bool {
if isContain, ok := visited[role.GetId()]; ok {
return isContain
}
for _, subRole := range role.Roles {
if subRole == roleId {
if util.HasString(roleIds, subRole) {
return true
}
r, ok := roleMap[subRole]
if ok && containsRole(r, roleId, roleMap, visited) {
if ok && containsRole(r, roleMap, visited, roleIds...) {
return true
}
}

View File

@ -43,6 +43,7 @@ func UploadRoles(owner string, fileId string) (bool, error) {
newRoles := []*Role{}
for index, line := range table {
line := line
if index == 0 || parseLineItem(&line, 0) == "" {
continue
}

View File

@ -260,10 +260,17 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
// decompress
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
_, err = io.Copy(&buffer, rdr)
for {
_, err := io.CopyN(&buffer, rdr, 1024)
if err != nil {
if err == io.EOF {
break
}
return "", "", "", err
}
}
var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {

View File

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

View File

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

View File

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

View File

@ -794,7 +794,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
ErrorDescription: "the wechat mini program session is invalid",
}, nil
}
user, err := getUserByWechatId(openId, unionId)
user, err := getUserByWechatId(application.Organization, openId, unionId)
if err != nil {
return nil, nil, err
}

View File

@ -216,6 +216,9 @@ func refineUser(user *User) *User {
if user.Permissions == nil {
user.Permissions = []*Permission{}
}
if user.Groups == nil {
user.Groups = []string{}
}
return user
}

View File

@ -15,12 +15,10 @@
package object
import (
"bytes"
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/xorm-io/core"
@ -46,6 +44,7 @@ type User struct {
FirstName string `xorm:"varchar(100)" json:"firstName"`
LastName string `xorm:"varchar(100)" json:"lastName"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
AvatarType string `xorm:"varchar(100)" json:"avatarType"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
@ -77,9 +76,8 @@ type User struct {
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
Groups []string `xorm:"varchar(1000)" json:"groups"`
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
@ -161,13 +159,19 @@ type User struct {
Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
MultiFactorAuths []*MfaProps `json:"multiFactorAuths"`
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes,omitempty"`
TotpSecret string `xorm:"varchar(100)" json:"totpSecret,omitempty"`
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
@ -221,11 +225,11 @@ func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOr
return users, nil
}
func GetUserCount(owner, field, value string, groupId string) (int64, error) {
func GetUserCount(owner, field, value string, groupName string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
if groupId != "" {
return GetGroupUserCount(groupId, field, value)
if groupName != "" {
return GetGroupUserCount(groupName, field, value)
}
return session.Count(&User{})
@ -265,11 +269,11 @@ func GetSortedUsers(owner string, sorter string, limit int) ([]*User, error) {
return users, nil
}
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string, groupId string) ([]*User, error) {
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string, groupName string) ([]*User, error) {
users := []*User{}
if groupId != "" {
return GetPaginationGroupUsers(groupId, offset, limit, field, value, sortField, sortOrder)
if groupName != "" {
return GetPaginationGroupUsers(groupName, offset, limit, field, value, sortField, sortOrder)
}
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
@ -316,12 +320,12 @@ func getUserById(owner string, id string) (*User, error) {
}
}
func getUserByWechatId(wechatOpenId string, wechatUnionId string) (*User, error) {
func getUserByWechatId(owner string, wechatOpenId string, wechatUnionId string) (*User, error) {
if wechatUnionId == "" {
wechatUnionId = wechatOpenId
}
user := &User{}
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ?", wechatOpenId, wechatUnionId).Get(user)
existed, err := adapter.Engine.Where("owner = ?", owner).Where("wechat = ? OR wechat = ?", wechatOpenId, wechatUnionId).Get(user)
if err != nil {
return nil, err
}
@ -426,18 +430,22 @@ func GetMaskedUser(user *User, errs ...error) (*User, error) {
if user.Password != "" {
user.Password = "***"
}
if user.AccessSecret != "" {
user.AccessSecret = "***"
}
if user.ManagedAccounts != nil {
for _, manageAccount := range user.ManagedAccounts {
manageAccount.Password = "***"
}
}
if user.MultiFactorAuths != nil {
for i, props := range user.MultiFactorAuths {
user.MultiFactorAuths[i] = GetMaskedProps(props)
if user.TotpSecret != "" {
user.TotpSecret = ""
}
if user.RecoveryCodes != nil {
user.RecoveryCodes = nil
}
return user, nil
}
@ -484,17 +492,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false, nil
return false, err
}
}
if user.Password == "***" {
user.Password = oldUser.Password
}
err = user.UpdateUserHash()
if err != nil {
panic(err)
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
@ -508,7 +512,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"owner", "display_name", "avatar",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "secret_key",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
@ -522,7 +526,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "name", "email", "phone", "country_code")
}
affected, err := updateUser(oldUser, user, columns)
affected, err := updateUser(id, user, columns)
if err != nil {
return false, err
}
@ -530,32 +534,17 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
return affected != 0, nil
}
func updateUser(oldUser, user *User, columns []string) (int64, error) {
session := adapter.Engine.NewSession()
defer session.Close()
session.Begin()
if util.ContainsString(columns, "groups") {
affected, err := updateUserGroupRelation(session, user)
func updateUser(id string, user *User, columns []string) (int64, error) {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
err := user.UpdateUserHash()
if err != nil {
session.Rollback()
return affected, err
}
}
affected, err := session.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
if err != nil {
session.Rollback()
return affected, err
}
err = session.Commit()
if err != nil {
session.Rollback()
return 0, err
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
if err != nil {
return 0, err
}
return affected, nil
}
@ -726,11 +715,6 @@ func DeleteUser(user *User) (bool, error) {
return false, err
}
affected, err = deleteRelationByUser(user.Id)
if err != nil {
return false, err
}
return affected != 0, nil
}
@ -746,7 +730,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
resp.Name = user.Name
resp.DisplayName = user.DisplayName
resp.Avatar = user.Avatar
resp.Groups = []string{user.Owner}
resp.Groups = user.Groups
}
if strings.Contains(scope, "email") {
resp.Email = user.Email
@ -777,12 +761,15 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return
}
user.Roles, err = GetRolesByUser(user.GetId())
user.Permissions, user.Roles, err = GetPermissionsAndRolesByUser(user.GetId())
if err != nil {
return
return err
}
if user.Groups == nil {
user.Groups = []string{}
}
user.Permissions, err = GetPermissionsByUser(user.GetId())
return
}
@ -844,76 +831,15 @@ func userChangeTrigger(oldName string, newName string) error {
return session.Commit()
}
func (user *User) refreshAvatar() (bool, error) {
var err error
var fileBuffer *bytes.Buffer
var ext string
// Gravatar + Identicon
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
client := proxy.ProxyHttpClient
has, err := hasGravatar(client, user.Email)
if err != nil {
return false, err
}
if has {
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
if err != nil {
return false, err
}
}
}
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
if err != nil {
return false, err
}
}
if fileBuffer != nil {
avatarUrl, err := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
if err != nil {
return false, err
}
user.Avatar = avatarUrl
return true, nil
}
return false, nil
}
func (user *User) IsMfaEnabled() bool {
return len(user.MultiFactorAuths) > 0
return user.PreferredMfaType != ""
}
func (user *User) GetPreferMfa(masked bool) *MfaProps {
if len(user.MultiFactorAuths) == 0 {
func (user *User) GetPreferredMfaProps(masked bool) *MfaProps {
if user.PreferredMfaType == "" {
return nil
}
if masked {
if len(user.MultiFactorAuths) == 1 {
return GetMaskedProps(user.MultiFactorAuths[0])
}
for _, v := range user.MultiFactorAuths {
if v.IsPreferred {
return GetMaskedProps(v)
}
}
return GetMaskedProps(user.MultiFactorAuths[0])
} else {
if len(user.MultiFactorAuths) == 1 {
return user.MultiFactorAuths[0]
}
for _, v := range user.MultiFactorAuths {
if v.IsPreferred {
return v
}
}
return user.MultiFactorAuths[0]
}
return user.GetMfaProps(user.PreferredMfaType, masked)
}
func AddUserkeys(user *User, isAdmin bool) (bool, error) {
@ -922,7 +848,7 @@ func AddUserkeys(user *User, isAdmin bool) (bool, error) {
}
user.AccessKey = util.GenerateId()
user.SecretKey = util.GenerateId()
user.AccessSecret = util.GenerateId()
return UpdateUser(user.GetId(), user, []string{}, isAdmin)
}

149
object/user_avatar.go Normal file
View File

@ -0,0 +1,149 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
"github.com/casdoor/casdoor/proxy"
)
func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, error) {
// Download the image
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, "", err
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") {
return nil, "", nil
} else {
return nil, "", err
}
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, resp.Status)
if resp.StatusCode == 404 {
return nil, "", nil
} else {
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
}
}
// Get the content type and determine the file extension
contentType := resp.Header.Get("Content-Type")
fileExtension := ""
if strings.Contains(contentType, "text/html") {
fileExtension = ".html"
} else {
switch contentType {
case "image/jpeg":
fileExtension = ".jpg"
case "image/png":
fileExtension = ".png"
case "image/gif":
fileExtension = ".gif"
case "image/vnd.microsoft.icon":
fileExtension = ".ico"
case "image/x-icon":
fileExtension = ".ico"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}
}
// Save the image to a bytes.Buffer
buffer := &bytes.Buffer{}
_, err = io.Copy(buffer, resp.Body)
if err != nil {
return nil, "", err
}
return buffer, fileExtension, nil
}
func (user *User) refreshAvatar() (bool, error) {
var err error
var fileBuffer *bytes.Buffer
var ext string
// Gravatar
if (user.AvatarType == "Auto" || user.AvatarType == "Gravatar") && user.Email != "" {
client := proxy.ProxyHttpClient
has, err := hasGravatar(client, user.Email)
if err != nil {
return false, err
}
if has {
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
if err != nil {
return false, err
}
if fileBuffer != nil {
user.AvatarType = "Gravatar"
}
}
}
// Favicon
if fileBuffer == nil && (user.AvatarType == "Auto" || user.AvatarType == "Favicon") {
client := proxy.ProxyHttpClient
fileBuffer, ext, err = getFaviconFileBuffer(client, user.Email)
if err != nil {
return false, err
}
if fileBuffer != nil {
user.AvatarType = "Favicon"
}
}
// Identicon
if fileBuffer == nil && (user.AvatarType == "Auto" || user.AvatarType == "Identicon") {
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
if err != nil {
return false, err
}
if fileBuffer != nil {
user.AvatarType = "Identicon"
}
}
if fileBuffer != nil {
avatarUrl, err := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
if err != nil {
return false, err
}
user.Avatar = avatarUrl
return true, nil
}
return false, nil
}

View File

@ -0,0 +1,218 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"fmt"
"net/http"
"strings"
"github.com/casdoor/casdoor/util"
"golang.org/x/net/html"
)
type Link struct {
Rel string
Sizes string
Href string
}
func GetFaviconUrl(htmlStr string) (string, error) {
doc, err := html.Parse(strings.NewReader(htmlStr))
if err != nil {
return "", err
}
var links []Link
findLinks(doc, &links)
if len(links) == 0 {
return "", fmt.Errorf("no Favicon links found")
}
chosenLink := chooseFaviconLink(links)
if chosenLink == nil {
return "", fmt.Errorf("unable to determine favicon URL")
}
return chosenLink.Href, nil
}
func findLinks(n *html.Node, links *[]Link) {
if n.Type == html.ElementNode && n.Data == "link" {
link := parseLink(n)
if link != nil {
*links = append(*links, *link)
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
findLinks(c, links)
}
}
func parseLink(n *html.Node) *Link {
var link Link
for _, attr := range n.Attr {
switch attr.Key {
case "rel":
link.Rel = attr.Val
case "sizes":
link.Sizes = attr.Val
case "href":
link.Href = attr.Val
}
}
if link.Href != "" {
return &link
}
return nil
}
func chooseFaviconLink(links []Link) *Link {
var appleTouchLinks []Link
var shortcutLinks []Link
var iconLinks []Link
for _, link := range links {
switch link.Rel {
case "apple-touch-icon":
appleTouchLinks = append(appleTouchLinks, link)
case "shortcut icon":
shortcutLinks = append(shortcutLinks, link)
case "icon":
iconLinks = append(iconLinks, link)
}
}
if len(appleTouchLinks) > 0 {
return chooseFaviconLinkBySizes(appleTouchLinks)
}
if len(shortcutLinks) > 0 {
return chooseFaviconLinkBySizes(shortcutLinks)
}
if len(iconLinks) > 0 {
return chooseFaviconLinkBySizes(iconLinks)
}
return nil
}
func chooseFaviconLinkBySizes(links []Link) *Link {
if len(links) == 1 {
return &links[0]
}
var chosenLink *Link
for _, link := range links {
link := link
if chosenLink == nil || compareSizes(link.Sizes, chosenLink.Sizes) > 0 {
chosenLink = &link
}
}
return chosenLink
}
func compareSizes(sizes1, sizes2 string) int {
if sizes1 == sizes2 {
return 0
}
size1 := parseSize(sizes1)
size2 := parseSize(sizes2)
if size1 == nil {
return -1
}
if size2 == nil {
return 1
}
if size1[0] == size2[0] {
return size1[1] - size2[1]
}
return size1[0] - size2[0]
}
func parseSize(sizes string) []int {
size := strings.Split(sizes, "x")
if len(size) != 2 {
return nil
}
var result []int
for _, s := range size {
val := strings.TrimSpace(s)
if len(val) > 0 {
num := 0
for i := 0; i < len(val); i++ {
if val[i] >= '0' && val[i] <= '9' {
num = num*10 + int(val[i]-'0')
} else {
break
}
}
result = append(result, num)
}
}
if len(result) == 2 {
return result
}
return nil
}
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
tokens := strings.Split(email, "@")
domain := tokens[1]
if domain == "gmail.com" || domain == "163.com" || domain == "qq.com" {
return nil, "", nil
}
htmlUrl := fmt.Sprintf("https://%s", domain)
buffer, _, err := downloadImage(client, htmlUrl)
if err != nil {
return nil, "", err
}
faviconUrl := ""
if buffer != nil {
faviconUrl, err = GetFaviconUrl(buffer.String())
if err != nil {
return nil, "", err
}
if !strings.HasPrefix(faviconUrl, "http") {
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
}
}
if faviconUrl == "" {
faviconUrl = fmt.Sprintf("https://%s/favicon.ico", domain)
}
return downloadImage(client, faviconUrl)
}

View File

@ -0,0 +1,76 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"crypto/md5"
"fmt"
"io"
"net/http"
"strings"
)
func hasGravatar(client *http.Client, email string) (bool, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
io.WriteString(hash, email)
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL with d=404 parameter
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
// Send a request to Gravatar
req, err := http.NewRequest("GET", gravatarURL, nil)
if err != nil {
return false, err
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
// Check if the user has a custom Gravatar image
if resp.StatusCode == http.StatusOK {
return true, nil
} else if resp.StatusCode == http.StatusNotFound {
return false, nil
} else {
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
}
}
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
// Clean and lowercase the email
email = strings.TrimSpace(strings.ToLower(email))
// Generate MD5 hash of the email
hash := md5.New()
_, err := io.WriteString(hash, email)
if err != nil {
return nil, "", err
}
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
// Create Gravatar URL
gravatarUrl := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
return downloadImage(client, gravatarUrl)
}

View File

@ -0,0 +1,80 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/color"
"image/png"
"io"
"strings"
"github.com/fogleman/gg"
)
func getColor(data []byte) color.RGBA {
r := int(data[0]) % 256
g := int(data[1]) % 256
b := int(data[2]) % 256
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
username = strings.TrimSpace(strings.ToLower(username))
hash := md5.New()
io.WriteString(hash, username)
hashedUsername := hash.Sum(nil)
// Define the size of the image
const imageSize = 420
const cellSize = imageSize / 7
// Create a new image
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
// Create a context
dc := gg.NewContextForRGBA(img)
// Set a background color
dc.SetColor(color.RGBA{240, 240, 240, 255})
dc.Clear()
// Get avatar color
avatarColor := getColor(hashedUsername)
// Draw cells
for i := 0; i < 7; i++ {
for j := 0; j < 7; j++ {
if (hashedUsername[i] >> uint(j) & 1) == 1 {
dc.SetColor(avatarColor)
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
dc.Fill()
}
}
}
// Save image to a bytes.Buffer
buffer := &bytes.Buffer{}
err := png.Encode(buffer, img)
if err != nil {
return nil, "", fmt.Errorf("failed to save image: %w", err)
}
return buffer, ".png", nil
}

View File

@ -16,7 +16,6 @@ package object
import (
"fmt"
"strings"
"testing"
"github.com/casdoor/casdoor/proxy"
@ -58,7 +57,11 @@ func TestUpdateAvatars(t *testing.T) {
}
for _, user := range users {
if strings.HasPrefix(user.Avatar, "http") {
//if strings.HasPrefix(user.Avatar, "http") {
// continue
//}
if user.AvatarType != "Auto" {
continue
}
@ -69,7 +72,7 @@ func TestUpdateAvatars(t *testing.T) {
if updated {
user.PermanentAvatar = "*"
_, err = UpdateUser(user.GetId(), user, []string{"avatar"}, true)
_, err = UpdateUser(user.GetId(), user, []string{"avatar", "avatar_type"}, true)
if err != nil {
panic(err)
}

View File

@ -1,157 +0,0 @@
package object
import (
"errors"
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
"github.com/xorm-io/xorm"
)
type UserGroupRelation struct {
UserId string `xorm:"varchar(100) notnull pk" json:"userId"`
GroupId string `xorm:"varchar(100) notnull pk" json:"groupId"`
CreatedTime string `xorm:"created" json:"createdTime"`
UpdatedTime string `xorm:"updated" json:"updatedTime"`
}
func updateUserGroupRelation(session *xorm.Session, user *User) (int64, error) {
physicalGroupCount, err := session.Where("type = ?", "Physical").In("id", user.Groups).Count(Group{})
if err != nil {
return 0, err
}
if physicalGroupCount > 1 {
return 0, errors.New("user can only be in one physical group")
}
groups := []*Group{}
err = session.In("id", user.Groups).Find(&groups)
if err != nil {
return 0, err
}
if len(groups) != len(user.Groups) {
return 0, errors.New("group not found")
}
_, err = session.Delete(&UserGroupRelation{UserId: user.Id})
if err != nil {
return 0, err
}
relations := []*UserGroupRelation{}
for _, group := range groups {
relations = append(relations, &UserGroupRelation{UserId: user.Id, GroupId: group.Id})
}
if len(relations) == 0 {
return 1, nil
}
_, err = session.Insert(relations)
if err != nil {
return 0, err
}
return 1, nil
}
func RemoveUserFromGroup(owner, name, groupId string) (bool, error) {
user, err := getUser(owner, name)
if err != nil {
return false, err
}
groups := []string{}
for _, group := range user.Groups {
if group != groupId {
groups = append(groups, group)
}
}
user.Groups = groups
_, err = UpdateUser(util.GetId(owner, name), user, []string{"groups"}, false)
if err != nil {
return false, err
}
return true, nil
}
func deleteUserGroupRelation(session *xorm.Session, userId, groupId string) (int64, error) {
affected, err := session.ID(core.PK{userId, groupId}).Delete(&UserGroupRelation{})
return affected, err
}
func deleteRelationByUser(id string) (int64, error) {
affected, err := adapter.Engine.Delete(&UserGroupRelation{UserId: id})
return affected, err
}
func GetGroupUserCount(id string, field, value string) (int64, error) {
group, err := GetGroup(id)
if group == nil || err != nil {
return 0, err
}
if field == "" && value == "" {
return adapter.Engine.Count(UserGroupRelation{GroupId: group.Id})
} else {
return adapter.Engine.Table("user").
Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id").
Where("r.group_id = ?", group.Id).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
func GetPaginationGroupUsers(id string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
group, err := GetGroup(id)
if group == nil || err != nil {
return nil, err
}
users := []*User{}
session := adapter.Engine.Table("user").
Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id").
Where("r.group_id = ?", group.Id)
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if field != "" && value != "" {
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%")
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
} else {
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
}
err = session.Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
func GetGroupUsers(id string) ([]*User, error) {
group, err := GetGroup(id)
if group == nil || err != nil {
return []*User{}, err
}
users := []*User{}
err = adapter.Engine.Table("user_group_relation").Join("INNER", []string{"user", "u"}, "user_group_relation.user_id = u.id").
Where("user_group_relation.group_id = ?", group.Id).
Find(&users)
if err != nil {
return nil, err
}
return users, nil
}

View File

@ -83,6 +83,7 @@ func UploadUsers(owner string, fileId string) (bool, error) {
newUsers := []*User{}
for index, line := range table {
line := line
if index == 0 || parseLineItem(&line, 0) == "" {
continue
}

View File

@ -288,9 +288,7 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
itemsChanged = append(itemsChanged, item)
}
oldUserTwoFactorAuthJson, _ := json.Marshal(oldUser.MultiFactorAuths)
newUserTwoFactorAuthJson, _ := json.Marshal(newUser.MultiFactorAuths)
if string(oldUserTwoFactorAuthJson) != string(newUserTwoFactorAuthJson) {
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
item := GetAccountItemByName("Multi-factor authentication", organization)
itemsChanged = append(itemsChanged, item)
}

View File

@ -15,6 +15,7 @@
package proxy
import (
"crypto/tls"
"fmt"
"net"
"net/http"
@ -71,7 +72,7 @@ func getProxyHttpClient() *http.Client {
panic(err)
}
tr := &http.Transport{Dial: dialer.Dial}
tr := &http.Transport{Dial: dialer.Dial, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
return &http.Client{
Transport: tr,
}

View File

@ -29,7 +29,7 @@ type Object struct {
Owner string `json:"owner"`
Name string `json:"name"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
AccessSecret string `json:"accessSecret"`
}
func getUsername(ctx *context.Context) (username string) {
@ -107,14 +107,14 @@ func getKeys(ctx *context.Context) (string, string) {
method := ctx.Request.Method
if method == http.MethodGet {
accessKey := ctx.Input.Query("accesskey")
secretKey := ctx.Input.Query("secretkey")
return accessKey, secretKey
accessKey := ctx.Input.Query("accessKey")
accessSecret := ctx.Input.Query("accessSecret")
return accessKey, accessSecret
} else {
body := ctx.Input.RequestBody
if len(body) == 0 {
return ctx.Request.Form.Get("accesskey"), ctx.Request.Form.Get("secretkey")
return ctx.Request.Form.Get("accessKey"), ctx.Request.Form.Get("accessSecret")
}
var obj Object
@ -123,7 +123,7 @@ func getKeys(ctx *context.Context) (string, string) {
return "", ""
}
return obj.AccessKey, obj.SecretKey
return obj.AccessKey, obj.AccessSecret
}
}

View File

@ -85,12 +85,13 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
}
func getUsernameByKeys(ctx *context.Context) string {
accessKey, secretKey := getKeys(ctx)
accessKey, accessSecret := getKeys(ctx)
user, err := object.GetUserByAccessKey(accessKey)
if err != nil {
panic(err)
}
if user != nil && secretKey == user.SecretKey {
if user != nil && accessSecret == user.AccessSecret {
return user.GetId()
}
return ""

View File

@ -19,6 +19,7 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@ -72,7 +73,7 @@ func StaticFilter(ctx *context.Context) {
}
func serveFileWithReplace(w http.ResponseWriter, r *http.Request, name string, old string, new string) {
f, err := os.Open(name)
f, err := os.Open(filepath.Clean(name))
if err != nil {
panic(err)
}

View File

@ -70,7 +70,7 @@ func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, er
return nil, err
}
dst, err := os.Create(fullPath)
dst, err := os.Create(filepath.Clean(fullPath))
if err == nil {
if seeker, ok := reader.(io.ReadSeeker); ok {

View File

@ -29,7 +29,7 @@ func NewMinIOS3StorageProvider(clientId string, clientSecret string, region stri
Endpoint: endpoint,
S3Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
S3ForcePathStyle: false,
S3ForcePathStyle: true,
})
return sp

View File

@ -43,6 +43,34 @@
}
}
},
"/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": {
"post": {
"tags": [
@ -127,6 +155,34 @@
}
}
},
"/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": {
"post": {
"tags": [
@ -599,6 +655,14 @@
}
}
},
"/api/add-user-keys": {
"post": {
"tags": [
"User API"
],
"operationId": "ApiController.AddUserkeys"
}
},
"/api/add-webhook": {
"post": {
"tags": [
@ -782,13 +846,13 @@
"tags": [
"Enforce API"
],
"description": "perform enforce",
"description": "Call Casbin BatchEnforce API",
"operationId": "ApiController.BatchEnforce",
"parameters": [
{
"in": "body",
"name": "body",
"description": "casbin request array",
"description": "array of casbin requests",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
@ -858,6 +922,34 @@
"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": {
"post": {
"tags": [
@ -942,6 +1034,34 @@
}
}
},
"/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": {
"post": {
"tags": [
@ -1429,13 +1549,13 @@
"tags": [
"Enforce API"
],
"description": "perform enforce",
"description": "Call Casbin Enforce API",
"operationId": "ApiController.Enforce",
"parameters": [
{
"in": "body",
"name": "body",
"description": "casbin request",
"description": "Casbin request",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
@ -1487,6 +1607,61 @@
}
}
},
"/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": {
"get": {
"tags": [
@ -1825,6 +2000,61 @@
}
}
},
"/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": {
"get": {
"tags": [
@ -2045,7 +2275,7 @@
"tags": [
"Organization API"
],
"description": "get all organization names",
"description": "get all organization name and displayName",
"operationId": "ApiController.GetOrganizationNames",
"parameters": [
{
@ -2338,7 +2568,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.pricing"
"$ref": "#/definitions/object.Pricing"
}
}
}
@ -2753,7 +2983,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.subscription"
"$ref": "#/definitions/object.Subscription"
}
}
}
@ -3328,7 +3558,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -3598,9 +3828,9 @@
"operationId": "ApiController.MfaSetupInitiate",
"responses": {
"200": {
"description": "Response object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/The"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -3799,6 +4029,41 @@
]
}
},
"/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": {
"post": {
"tags": [
@ -3904,6 +4169,41 @@
}
}
},
"/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": {
"post": {
"tags": [
@ -4579,7 +4879,7 @@
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -4624,7 +4924,7 @@
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -4632,14 +4932,6 @@
}
},
"definitions": {
"1225.0xc0002e2ae0.false": {
"title": "false",
"type": "object"
},
"1260.0xc0002e2b10.false": {
"title": "false",
"type": "object"
},
"LaravelResponse": {
"title": "LaravelResponse",
"type": "object"
@ -4648,10 +4940,6 @@
"title": "Response",
"type": "object"
},
"The": {
"title": "The",
"type": "object"
},
"controllers.AuthForm": {
"title": "AuthForm",
"type": "object"
@ -4685,10 +4973,16 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/1225.0xc0002e2ae0.false"
"additionalProperties": {
"description": "support string | class | List\u003cclass\u003e and os on",
"type": "string"
}
},
"data2": {
"$ref": "#/definitions/1260.0xc0002e2b10.false"
"additionalProperties": {
"description": "support string | class | List\u003cclass\u003e and os on",
"type": "string"
}
},
"msg": {
"type": "string"
@ -4726,8 +5020,8 @@
"title": "JSONWebKey",
"type": "object"
},
"object.\u0026{179844 0xc000a02f90 false}": {
"title": "\u0026{179844 0xc000a02f90 false}",
"object": {
"title": "object",
"type": "object"
},
"object.AccountItem": {
@ -4917,7 +5211,7 @@
"title": "CasbinRequest",
"type": "array",
"items": {
"$ref": "#/definitions/object.\u0026{179844 0xc000a02f90 false}"
"$ref": "#/definitions/object.CasbinRequest"
}
},
"object.Cert": {
@ -5029,6 +5323,63 @@
}
}
},
"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": {
"title": "Header",
"type": "object",
@ -5175,12 +5526,15 @@
"countryCode": {
"type": "string"
},
"id": {
"type": "string"
"enabled": {
"type": "boolean"
},
"isPreferred": {
"type": "boolean"
},
"mfaType": {
"type": "string"
},
"recoveryCodes": {
"type": "array",
"items": {
@ -5190,9 +5544,6 @@
"secret": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string"
}
@ -5205,6 +5556,9 @@
"createdTime": {
"type": "string"
},
"description": {
"type": "string"
},
"displayName": {
"type": "string"
},
@ -5362,6 +5716,12 @@
"owner": {
"type": "string"
},
"passwordOptions": {
"type": "array",
"items": {
"type": "string"
}
},
"passwordSalt": {
"type": "string"
},
@ -5492,6 +5852,9 @@
"createdTime": {
"type": "string"
},
"description": {
"type": "string"
},
"displayName": {
"type": "string"
},
@ -5611,9 +5974,6 @@
"displayName": {
"type": "string"
},
"hasTrial": {
"type": "boolean"
},
"isEnabled": {
"type": "boolean"
},
@ -5933,6 +6293,9 @@
"createdTime": {
"type": "string"
},
"description": {
"type": "string"
},
"displayName": {
"type": "string"
},
@ -6068,6 +6431,9 @@
"isEnabled": {
"type": "boolean"
},
"isReadOnly": {
"type": "boolean"
},
"name": {
"type": "string"
},
@ -6248,6 +6614,12 @@
"title": "User",
"type": "object",
"properties": {
"accessKey": {
"type": "string"
},
"accessSecret": {
"type": "string"
},
"address": {
"type": "array",
"items": {
@ -6275,6 +6647,9 @@
"avatar": {
"type": "string"
},
"avatarType": {
"type": "string"
},
"azuread": {
"type": "string"
},
@ -6380,6 +6755,12 @@
"google": {
"type": "string"
},
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"hash": {
"type": "string"
},
@ -6480,6 +6861,12 @@
"meetup": {
"type": "string"
},
"mfaEmailEnabled": {
"type": "boolean"
},
"mfaPhoneEnabled": {
"type": "boolean"
},
"microsoftonline": {
"type": "string"
},
@ -6540,6 +6927,9 @@
"preHash": {
"type": "string"
},
"preferredMfaType": {
"type": "string"
},
"properties": {
"additionalProperties": {
"type": "string"
@ -6552,6 +6942,12 @@
"type": "integer",
"format": "int64"
},
"recoveryCodes": {
"type": "array",
"items": {
"type": "string"
}
},
"region": {
"type": "string"
},
@ -6605,6 +7001,9 @@
"title": {
"type": "string"
},
"totpSecret": {
"type": "string"
},
"tumblr": {
"type": "string"
},
@ -6677,15 +7076,18 @@
"email": {
"type": "string"
},
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"iss": {
"type": "string"
},
"name": {
"type": "string"
},
"organization": {
"type": "string"
},
"phone": {
"type": "string"
},
@ -6745,14 +7147,6 @@
}
}
},
"object.pricing": {
"title": "pricing",
"type": "object"
},
"object.subscription": {
"title": "subscription",
"type": "object"
},
"protocol.CredentialAssertion": {
"title": "CredentialAssertion",
"type": "object"

View File

@ -28,6 +28,24 @@ paths:
description: ""
schema:
$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:
post:
tags:
@ -82,6 +100,24 @@ paths:
description: The Response object
schema:
$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:
post:
tags:
@ -386,6 +422,11 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-user-keys:
post:
tags:
- User API
operationId: ApiController.AddUserkeys
/api/add-webhook:
post:
tags:
@ -506,12 +547,12 @@ paths:
post:
tags:
- Enforce API
description: perform enforce
description: Call Casbin BatchEnforce API
operationId: ApiController.BatchEnforce
parameters:
- in: body
name: body
description: casbin request array
description: array of casbin requests
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
@ -555,6 +596,24 @@ paths:
tags:
- User API
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:
post:
tags:
@ -609,6 +668,24 @@ paths:
description: The Response object
schema:
$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:
post:
tags:
@ -923,12 +1000,12 @@ paths:
post:
tags:
- Enforce API
description: perform enforce
description: Call Casbin Enforce API
operationId: ApiController.Enforce
parameters:
- in: body
name: body
description: casbin request
description: Casbin request
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
@ -960,6 +1037,42 @@ paths:
description: The Response object
schema:
$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:
get:
tags:
@ -1183,6 +1296,42 @@ paths:
type: array
items:
$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:
get:
tags:
@ -1327,7 +1476,7 @@ paths:
get:
tags:
- Organization API
description: get all organization names
description: get all organization name and displayName
operationId: ApiController.GetOrganizationNames
parameters:
- in: query
@ -1521,7 +1670,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.pricing'
$ref: '#/definitions/object.Pricing'
/api/get-pricings:
get:
tags:
@ -1793,7 +1942,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.subscription'
$ref: '#/definitions/object.Subscription'
/api/get-subscriptions:
get:
tags:
@ -2172,7 +2321,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/login/oauth/access_token:
post:
tags:
@ -2351,9 +2500,9 @@ paths:
operationId: ApiController.MfaSetupInitiate
responses:
"200":
description: Response object
description: The Response object
schema:
$ref: '#/definitions/The'
$ref: '#/definitions/controllers.Response'
/api/mfa/setup/verify:
post:
tags:
@ -2480,6 +2629,29 @@ paths:
post:
tags:
- 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:
post:
tags:
@ -2549,6 +2721,29 @@ paths:
description: The Response object
schema:
$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:
post:
tags:
@ -2994,7 +3189,7 @@ paths:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/webauthn/signup/begin:
get:
tags:
@ -3023,23 +3218,14 @@ paths:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
definitions:
1225.0xc0002e2ae0.false:
title: "false"
type: object
1260.0xc0002e2b10.false:
title: "false"
type: object
LaravelResponse:
title: LaravelResponse
type: object
Response:
title: Response
type: object
The:
title: The
type: object
controllers.AuthForm:
title: AuthForm
type: object
@ -3064,9 +3250,13 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/1225.0xc0002e2ae0.false'
additionalProperties:
description: support string | class | List<class> and os on
type: string
data2:
$ref: '#/definitions/1260.0xc0002e2b10.false'
additionalProperties:
description: support string | class | List<class> and os on
type: string
msg:
type: string
name:
@ -3090,8 +3280,8 @@ definitions:
jose.JSONWebKey:
title: JSONWebKey
type: object
object.&{179844 0xc000a02f90 false}:
title: '&{179844 0xc000a02f90 false}'
object:
title: object
type: object
object.AccountItem:
title: AccountItem
@ -3220,7 +3410,7 @@ definitions:
title: CasbinRequest
type: array
items:
$ref: '#/definitions/object.&{179844 0xc000a02f90 false}'
$ref: '#/definitions/object.CasbinRequest'
object.Cert:
title: Cert
type: object
@ -3295,6 +3485,44 @@ definitions:
throughput:
type: number
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:
title: Header
type: object
@ -3395,18 +3623,18 @@ definitions:
properties:
countryCode:
type: string
id:
type: string
enabled:
type: boolean
isPreferred:
type: boolean
mfaType:
type: string
recoveryCodes:
type: array
items:
type: string
secret:
type: string
type:
type: string
url:
type: string
object.Model:
@ -3415,6 +3643,8 @@ definitions:
properties:
createdTime:
type: string
description:
type: string
displayName:
type: string
isEnabled:
@ -3520,6 +3750,10 @@ definitions:
type: string
owner:
type: string
passwordOptions:
type: array
items:
type: string
passwordSalt:
type: string
passwordType:
@ -3607,6 +3841,8 @@ definitions:
type: string
createdTime:
type: string
description:
type: string
displayName:
type: string
domains:
@ -3687,8 +3923,6 @@ definitions:
type: string
displayName:
type: string
hasTrial:
type: boolean
isEnabled:
type: boolean
name:
@ -3904,6 +4138,8 @@ definitions:
properties:
createdTime:
type: string
description:
type: string
displayName:
type: string
domains:
@ -3995,6 +4231,8 @@ definitions:
type: string
isEnabled:
type: boolean
isReadOnly:
type: boolean
name:
type: string
organization:
@ -4117,6 +4355,10 @@ definitions:
title: User
type: object
properties:
accessKey:
type: string
accessSecret:
type: string
address:
type: array
items:
@ -4135,6 +4377,8 @@ definitions:
type: string
avatar:
type: string
avatarType:
type: string
azuread:
type: string
baidu:
@ -4205,6 +4449,10 @@ definitions:
type: string
google:
type: string
groups:
type: array
items:
type: string
hash:
type: string
heroku:
@ -4272,6 +4520,10 @@ definitions:
$ref: '#/definitions/object.ManagedAccount'
meetup:
type: string
mfaEmailEnabled:
type: boolean
mfaPhoneEnabled:
type: boolean
microsoftonline:
type: string
multiFactorAuths:
@ -4312,6 +4564,8 @@ definitions:
type: string
preHash:
type: string
preferredMfaType:
type: string
properties:
additionalProperties:
type: string
@ -4320,6 +4574,10 @@ definitions:
ranking:
type: integer
format: int64
recoveryCodes:
type: array
items:
type: string
region:
type: string
roles:
@ -4356,6 +4614,8 @@ definitions:
type: string
title:
type: string
totpSecret:
type: string
tumblr:
type: string
twitch:
@ -4404,12 +4664,14 @@ definitions:
type: string
email:
type: string
groups:
type: array
items:
type: string
iss:
type: string
name:
type: string
organization:
type: string
phone:
type: string
picture:
@ -4448,12 +4710,6 @@ definitions:
type: string
url:
type: string
object.pricing:
title: pricing
type: object
object.subscription:
title: subscription
type: object
protocol.CredentialAssertion:
title: CredentialAssertion
type: object

View File

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

View File

@ -26,11 +26,32 @@ func DeleteVal(values []string, val string) []string {
return newValues
}
func ReplaceVal(values []string, oldVal string, newVal string) []string {
newValues := []string{}
for _, v := range values {
if v == oldVal {
newValues = append(newValues, newVal)
} else {
newValues = append(newValues, v)
}
}
return newValues
}
func ContainsString(values []string, val string) bool {
sort.Strings(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 {
for _, str := range strs {
if str != "" {

View File

@ -22,6 +22,7 @@ import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@ -201,7 +202,7 @@ func GetMinLenStr(strs ...string) string {
}
func ReadStringFromPath(path string) string {
data, err := os.ReadFile(path)
data, err := os.ReadFile(filepath.Clean(path))
if err != nil {
panic(err)
}
@ -278,3 +279,13 @@ func GetEndPoint(endpoint string) string {
}
return endpoint
}
// HasString reports if slice has input string.
func HasString(strs []string, str string) bool {
for _, i := range strs {
if i == str {
return true
}
}
return false
}

View File

@ -18,6 +18,7 @@ import (
"bufio"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
@ -155,7 +156,7 @@ func GetVersionInfoFromFile() (*VersionInfo, error) {
_, filename, _, _ := runtime.Caller(0)
rootPath := path.Dir(path.Dir(filename))
file, err := os.Open(path.Join(rootPath, "version_info.txt"))
file, err := os.Open(filepath.Clean(path.Join(rootPath, "version_info.txt")))
if err != nil {
return res, err
}

View File

@ -22,10 +22,18 @@ import (
"github.com/nyaruka/phonenumbers"
)
var rePhone *regexp.Regexp
var (
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
)
func init() {
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 {
@ -70,3 +78,7 @@ func GetCountryCode(prefix string, phone string) (string, error) {
return countryCode, nil
}
func FilterField(field string) bool {
return ReFieldWhiteList.MatchString(field)
}

View File

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

View File

@ -32,7 +32,7 @@ class AdapterEditPage extends React.Component {
super(props);
this.state = {
classes: props,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
adapterName: props.match.params.adapterName,
adapter: null,
organizations: [],
@ -47,7 +47,7 @@ class AdapterEditPage extends React.Component {
}
getAdapter() {
AdapterBackend.getAdapter("admin", this.state.adapterName)
AdapterBackend.getAdapter(this.state.organizationName, this.state.adapterName)
.then((res) => {
if (res.status === "ok") {
if (res.data === null) {
@ -59,13 +59,13 @@ class AdapterEditPage extends React.Component {
adapter: res.data,
});
this.getModels(this.state.owner);
this.getModels(this.state.organizationName);
}
});
}
getOrganizations() {
OrganizationBackend.getOrganizations(this.state.organizationName)
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
@ -76,6 +76,10 @@ class AdapterEditPage extends React.Component {
getModels(organizationName) {
ModelBackend.getModels(organizationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
models: res,
});
@ -114,9 +118,8 @@ class AdapterEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.adapter.organization} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.adapter.owner} onChange={(value => {
this.getModels(value);
this.updateAdapterField("organization", value);
this.updateAdapterField("owner", value);
})}>
{
@ -253,7 +256,7 @@ class AdapterEditPage extends React.Component {
{Setting.getLabel(i18next.t("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} :
</Col>
<Col span={22}>
<PolicyTable owner={this.state.owner} name={this.state.adapterName} mode={this.state.mode} />
<PolicyTable owner={this.state.organizationName} name={this.state.adapterName} mode={this.state.mode} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
@ -272,7 +275,7 @@ class AdapterEditPage extends React.Component {
submitAdapterEdit(willExist) {
const adapter = Setting.deepCopy(this.state.adapter);
AdapterBackend.updateAdapter(this.state.owner, this.state.adapterName, adapter)
AdapterBackend.updateAdapter(this.state.organizationName, this.state.adapterName, adapter)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
@ -283,7 +286,7 @@ class AdapterEditPage extends React.Component {
if (willExist) {
this.props.history.push("/adapters");
} else {
this.props.history.push(`/adapters/${this.state.owner}/${this.state.adapter.name}`);
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);

View File

@ -25,11 +25,11 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
class AdapterListPage extends BaseListPage {
newAdapter() {
const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return {
owner: "admin",
owner: owner,
name: `adapter_${randomName}`,
createdTime: moment().format(),
organization: this.props.account.owner,
type: "Database",
host: "localhost",
port: 3306,
@ -47,7 +47,7 @@ class AdapterListPage extends BaseListPage {
AdapterBackend.addAdapter(newAdapter)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/adapters/${newAdapter.organization}/${newAdapter.name}`, mode: "add"});
this.props.history.push({pathname: `/adapters/${newAdapter.owner}/${newAdapter.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
@ -88,7 +88,7 @@ class AdapterListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/adapters/${record.organization}/${text}`}>
<Link to={`/adapters/${record.owner}/${text}`}>
{text}
</Link>
);
@ -96,11 +96,11 @@ class AdapterListPage extends BaseListPage {
},
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
dataIndex: "owner",
key: "owner",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
@ -247,7 +247,7 @@ class AdapterListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
AdapterBackend.getAdapters("admin", 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) => {
this.setState({
loading: false,

View File

@ -86,6 +86,7 @@ import {withTranslation} from "react-i18next";
import ThemeSelect from "./common/select/ThemeSelect";
import SessionListPage from "./SessionListPage";
import MfaSetupPage from "./auth/MfaSetupPage";
import OrganizationSelect from "./common/select/OrganizationSelect";
const {Header, Footer, Content} = Layout;
@ -106,7 +107,7 @@ class App extends Component {
Setting.initServerUrl();
Auth.initAuthWithConfig({
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: "/"
});
}
@ -343,9 +344,11 @@ class App extends Component {
items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>,
"/account"
));
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")}</>,
"/logout"));
@ -396,6 +399,17 @@ class App extends Component {
});
}} />
<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>
);
}
@ -411,6 +425,14 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
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>,
"/organizations"));
@ -449,6 +471,7 @@ class App extends Component {
"/providers"
));
if (Conf.EnableChatPages) {
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
"/chats"
));
@ -456,6 +479,7 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
"/messages"
));
}
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
"/resources"
@ -476,7 +500,6 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
"/subscriptions"
));
}
if (Setting.isLocalAdminUser(this.state.account)) {
@ -501,7 +524,6 @@ class App extends Component {
));
if (Conf.EnableExtraPages) {
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
"/products"
));
@ -510,8 +532,8 @@ class App extends Component {
"/payments"
));
}
}
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo"
@ -601,7 +623,7 @@ class App extends Component {
<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="/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="/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} />)} />
@ -641,6 +663,7 @@ class App extends Component {
this.props.history.push(key);
}
};
const menuStyleRight = Setting.isAdminUser(this.state.account) && !Setting.isMobile() ? "calc(180px + 260px)" : "260px";
return (
<Layout id="parent-area">
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}>
@ -670,7 +693,7 @@ class App extends Component {
items={this.getMenuItems()}
mode={"horizontal"}
selectedKeys={[this.state.selectedMenuKey]}
style={{position: "absolute", left: "145px", right: "260px"}}
style={{position: "absolute", left: "145px", right: menuStyleRight}}
/>
}
{

View File

@ -118,20 +118,30 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => {
if (application === null) {
.then((res) => {
if (res === null) {
this.props.history.push("/404");
return;
}
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"];
if (res.status === "error") {
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({
application: application,
application: res,
});
this.getCerts(application.organization);
this.getCerts(res.organization);
});
}
@ -307,6 +317,18 @@ class ApplicationEditPage extends React.Component {
</Select>
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{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);
}
componentDidMount() {
this.setState({
organizationName: this.props.account.owner,
});
}
newApplication() {
const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return {
owner: "admin", // this.props.account.applicationName,
name: `application_${randomName}`,
organization: this.state.organizationName,
organization: organizationName,
createdTime: moment().format(),
displayName: `New Application - ${randomName}`,
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
@ -273,8 +268,8 @@ class ApplicationListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
(Setting.isAdminUser(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))
(Setting.isDefaultOrganizationSelected(this.props.account) ? ApplicationBackend.getApplications("admin", 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) => {
this.setState({
loading: false,

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