Compare commits

...

20 Commits

Author SHA1 Message Date
67a5adf585 feat: replace panic with details json error payload. (#1039)
Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>
2022-08-20 21:09:32 +08:00
08a1e7ae32 fix: keep phone/email unique. (#1038)
Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>
2022-08-20 12:14:08 +08:00
7d979cbaf0 feat(storage): add support for min.io storage (#1037)
* feat(storage): add support for min.io storage

* fix(minio): use doublequote

* fix(storage): change storage name to MinIO
2022-08-20 11:30:13 +08:00
80c0940e30 feat: initialize the default permission (#1029)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-19 11:59:35 +08:00
a4fe2a6485 Add check for submitPermissionEdit() 2022-08-19 01:52:29 +08:00
8e9ed1205b feat: support RBAC with domains model and add adapter to specify the table name for policy storage (#1020)
* feat: support RBAC with domains model and add adapter to specify the table name for policy storage

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix some bugs

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* add i18n

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-18 11:49:32 +08:00
a341c65bb1 fix: third-party user may login to the built-in organization (#1024)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-17 23:18:38 +08:00
91fa024f0b feat: Mock SMS (#1009)
1. Update go-sms-sender to v0.3.0.
2. Fix: avoid page crash if not found provider info.

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>
2022-08-17 22:02:45 +08:00
aedef1eea1 feat(login): add login limit (#1023)
* feat(login): add login limit

* chore: rename vars

* chore: use `string`

* fix: clear the signin error times after succeessfull login

* chore: modify code position
2022-08-17 01:39:53 +08:00
70f2988f09 feat: revert to the original behavior for wrapActionResponse() (#1021)
Revert: 340fbe135d

see: https://github.com/casdoor/casdoor-go-sdk/pull/36.
2022-08-16 00:20:37 +08:00
2dcdfbe6d3 fix: error login logic of mobile phone login (#1017)
* fix: #1016

1. Limit username cannot be digital.
2. Check avoid repeat register with same phone or email.

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>

* Update check.go

Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>
Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-16 00:14:26 +08:00
c92d34e27c Add GetPermissionsBySubmitter() 2022-08-15 14:09:12 +08:00
dfbf7753c3 feat: support RBAC model in permission (#1006)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-15 10:24:26 +08:00
ba732b3075 feat: use staticBaseUrl for all static resources (#1015)
* feat: modify system image link

* Update App.less

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-15 09:18:21 +08:00
ca13247572 chore(style): use eqeqeq (#1013) 2022-08-13 11:23:16 +08:00
108fdc174f chore(ci): add linter the check go code style (#991)
* feat(ci): auto format go code

* fix: fix #997

* chore(ci): add go code style linter

* fix: fix cmd error

* chore: add `linter` of needs

* chore: modiy commnet style
2022-08-13 10:57:13 +08:00
a741c5179a chore(style): modify eslint rules (#1011)
* chore(style): use strict rules

* chore: modify position

* chore(style): warn about `console.log` and `==`

* fix: fix `console.log` error

* Update CropperDiv.js

* Update HomePage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-13 00:04:18 +08:00
6676cc8ff3 fix: add JTI name to JWT token (#989)
* feat: add jti to jwt

* fix

* fix
2022-08-11 14:32:47 +08:00
13de019d08 chore(ci): use cache to accelerate ci (#1004)
* chore(ci): use cache to accelerate ci

* chore: comment
2022-08-11 10:20:53 +08:00
53ad454962 feat: responsive footer (#1003) 2022-08-10 20:31:42 +08:00
70 changed files with 878 additions and 218 deletions

View File

@ -35,6 +35,10 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: '14.17.0'
# cache
- uses: c-hive/gha-yarn-cache@v2
with:
directory: ./web
- run: yarn install && CI=false yarn run build
working-directory: ./web
@ -53,11 +57,30 @@ jobs:
go build -race -ldflags "-extldflags '-static'"
working-directory: ./
linter:
name: Go-Linter
runs-on: ubuntu-latest
needs: [ go-tests ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.16.5'
# gen a dummy config file
- run: touch dummy.yml
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --disable-all -c dummy.yml -E=gofumpt --max-same-issues=0 --timeout 5m --modules-download-mode=mod
release-and-push:
name: Release And Push
runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
needs: [ frontend, backend ]
needs: [ frontend, backend, linter ]
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@ -31,7 +31,7 @@ run:
- api
# skip-files:
# - ".*_test\\.go$"
modules-download-mode: vendor
modules-download-mode: mod
# all available settings of specific linters
linters-settings:
lll:

View File

@ -15,4 +15,5 @@ socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true
origin =
origin =
staticBaseUrl = "https://cdn.casbin.org"

View File

@ -11,6 +11,7 @@
// 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 conf
import (

View File

@ -105,7 +105,8 @@ func (c *ApiController) Signup() {
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
@ -156,6 +157,12 @@ func (c *ApiController) Signup() {
username = id
}
initScore, err := getInitScore()
if err != nil {
c.ResponseError(fmt.Errorf("get init score failed, error: %w", err).Error())
return
}
user := &object.User{
Owner: form.Organization,
Name: username,
@ -171,7 +178,7 @@ func (c *ApiController) Signup() {
Affiliation: form.Affiliation,
IdCard: form.IdCard,
Region: form.Region,
Score: getInitScore(),
Score: initScore,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,

View File

@ -111,8 +111,7 @@ func (c *ApiController) GetOrganizationApplications() {
return
}
var applications []*object.Application
applications = object.GetApplicationsByOrganizationName(owner, organization)
applications := object.GetApplicationsByOrganizationName(owner, organization)
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
}
@ -131,7 +130,8 @@ func (c *ApiController) UpdateApplication() {
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
@ -149,7 +149,8 @@ func (c *ApiController) AddApplication() {
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
@ -167,7 +168,8 @@ func (c *ApiController) DeleteApplication() {
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))

View File

@ -344,7 +344,7 @@ func (c *ApiController) Login() {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
}
if user != nil && user.IsDeleted == false {
if user != nil && !user.IsDeleted {
// Sign in via OAuth (want to sign up but already have account)
if user.IsForbidden {
@ -384,6 +384,12 @@ func (c *ApiController) Login() {
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
initScore, err := getInitScore()
if err != nil {
c.ResponseError(fmt.Errorf("get init score failed, error: %w", err).Error())
return
}
user = &object.User{
Owner: application.Organization,
Name: userInfo.Username,
@ -394,7 +400,7 @@ func (c *ApiController) Login() {
Avatar: userInfo.AvatarUrl,
Address: []string{},
Email: userInfo.Email,
Score: getInitScore(),
Score: initScore,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,

View File

@ -19,6 +19,7 @@ import (
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -58,6 +59,7 @@ func (c *ApiController) IsGlobalAdmin() bool {
func (c *ApiController) GetSessionUsername() string {
// check if user session expired
sessionData := c.GetSessionData()
if sessionData != nil &&
sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() {
@ -120,7 +122,8 @@ func (c *ApiController) GetSessionData() *SessionData {
sessionData := &SessionData{}
err := util.JsonToStruct(session.(string), sessionData)
if err != nil {
panic(err)
logs.Error("GetSessionData failed, error: %s", err)
return nil
}
return sessionData
@ -138,9 +141,9 @@ func (c *ApiController) SetSessionData(s *SessionData) {
func wrapActionResponse(affected bool) *Response {
if affected {
return &Response{Status: "ok", Msg: ""}
return &Response{Status: "ok", Msg: "", Data: "Affected"}
} else {
return &Response{Status: "error", Msg: "this operation has no effect"}
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
}
}

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateCert() {
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
@ -94,7 +95,8 @@ func (c *ApiController) AddCert() {
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteCert() {
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))

View File

@ -30,7 +30,8 @@ func (c *ApiController) Enforce() {
var permissionRule object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.Enforce(userId, &permissionRule)
@ -47,7 +48,8 @@ func (c *ApiController) BatchEnforce() {
var permissionRules []object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.BatchEnforce(userId, permissionRules)

View File

@ -199,7 +199,8 @@ func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
@ -217,7 +218,8 @@ func (c *ApiController) SyncLdapUsers() {
var users []object.LdapRespUser
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
object.UpdateLdapSyncTime(ldapId)
@ -239,7 +241,8 @@ func (c *ApiController) CheckLdapUsersExist() {
var uuids []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
exist := object.CheckLdapUuidExist(owner, uuids)

View File

@ -37,7 +37,8 @@ func (c *ApiController) Unlink() {
var form LinkForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
providerType := form.ProviderType

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateModel() {
var model object.Model
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
@ -94,7 +95,8 @@ func (c *ApiController) AddModel() {
var model object.Model
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddModel(&model))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteModel() {
var model object.Model
err := json.Unmarshal(c.Ctx.Input.RequestBody, &model)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteModel(&model))

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateOrganization() {
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
@ -94,7 +95,8 @@ func (c *ApiController) AddOrganization() {
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteOrganization() {
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteOrganization(&organization))

View File

@ -95,7 +95,8 @@ func (c *ApiController) UpdatePayment() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdatePayment(id, &payment))
@ -113,7 +114,8 @@ func (c *ApiController) AddPayment() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddPayment(&payment))
@ -131,7 +133,8 @@ func (c *ApiController) DeletePayment() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
@ -157,7 +160,8 @@ func (c *ApiController) NotifyPayment() {
if ok {
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))

View File

@ -48,6 +48,24 @@ func (c *ApiController) GetPermissions() {
}
}
// GetPermissionsBySubmitter
// @Title GetPermissionsBySubmitter
// @Tag Permission API
// @Description get permissions by submitter
// @Success 200 {array} object.Permission The Response object
// @router /get-permissions-by-submitter [get]
func (c *ApiController) GetPermissionsBySubmitter() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
owner, username := util.GetOwnerAndNameFromId(userId)
permissions := object.GetPermissionsBySubmitter(owner, username)
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermission
// @Title GetPermission
// @Tag Permission API
@ -76,7 +94,8 @@ func (c *ApiController) UpdatePermission() {
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdatePermission(id, &permission))
@ -94,7 +113,8 @@ func (c *ApiController) AddPermission() {
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddPermission(&permission))
@ -112,7 +132,8 @@ func (c *ApiController) DeletePermission() {
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeletePermission(&permission))

View File

@ -80,7 +80,8 @@ func (c *ApiController) UpdateProduct() {
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateProduct(id, &product))
@ -98,7 +99,8 @@ func (c *ApiController) AddProduct() {
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddProduct(&product))
@ -116,7 +118,8 @@ func (c *ApiController) DeleteProduct() {
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateProvider() {
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
@ -94,7 +95,8 @@ func (c *ApiController) AddProvider() {
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteProvider() {
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))

View File

@ -59,7 +59,8 @@ func (c *ApiController) GetRecordsByFilter() {
record := &object.Record{}
err := util.JsonToStruct(body, record)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetRecordsByField(record)

View File

@ -72,7 +72,8 @@ func (c *ApiController) UpdateResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
@ -87,7 +88,8 @@ func (c *ApiController) AddResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
@ -102,7 +104,8 @@ func (c *ApiController) DeleteResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
provider, _, ok := c.GetProviderFromContext("Storage")

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateRole() {
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
@ -94,7 +95,8 @@ func (c *ApiController) AddRole() {
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddRole(&role))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteRole() {
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateSyncer() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateSyncer(id, &syncer))
@ -94,7 +95,8 @@ func (c *ApiController) AddSyncer() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddSyncer(&syncer))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteSyncer() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))

View File

@ -79,7 +79,8 @@ func (c *ApiController) UpdateToken() {
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token))
@ -97,7 +98,8 @@ func (c *ApiController) AddToken() {
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddToken(&token))
@ -115,7 +117,8 @@ func (c *ApiController) DeleteToken() {
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteToken(&token))
@ -269,10 +272,11 @@ func (c *ApiController) TokenLogout() {
// IntrospectToken
// @Title IntrospectToken
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
// parameter representing an OAuth 2.0 token and returns a JSON document
// representing the meta information surrounding the
// token, including whether this token is currently active.
// This endpoint only support Basic Authorization.
// parameter representing an OAuth 2.0 token and returns a JSON document
// representing the meta information surrounding the
// token, including whether this token is currently active.
// This endpoint only support Basic Authorization.
//
// @Param token formData string true "access_token's value or refresh_token's value"
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
// @Success 200 {object} object.IntrospectionResponse The Response object

View File

@ -149,7 +149,8 @@ func (c *ApiController) UpdateUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
if user.DisplayName == "" {
@ -183,7 +184,8 @@ func (c *ApiController) AddUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
@ -201,7 +203,8 @@ func (c *ApiController) DeleteUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
@ -220,7 +223,8 @@ func (c *ApiController) GetEmailAndPhone() {
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
user := object.GetUserByFields(form.Organization, form.Username)
@ -306,7 +310,8 @@ func (c *ApiController) CheckUserPassword() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password)

View File

@ -24,17 +24,18 @@ import (
"github.com/casdoor/casdoor/util"
)
func saveFile(path string, file *multipart.File) {
func saveFile(path string, file *multipart.File) (err error) {
f, err := os.Create(path)
if err != nil {
panic(err)
return err
}
defer f.Close()
_, err = io.Copy(f, *file)
if err != nil {
panic(err)
return err
}
return nil
}
func (c *ApiController) UploadUsers() {
@ -43,13 +44,18 @@ func (c *ApiController) UploadUsers() {
file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
saveFile(path, &file)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected := object.UploadUsers(owner, fileId)
if affected {

View File

@ -23,9 +23,8 @@ import (
"github.com/casdoor/casdoor/util"
)
// ResponseOk ...
func (c *ApiController) ResponseOk(data ...interface{}) {
resp := Response{Status: "ok"}
// ResponseJsonData ...
func (c *ApiController) ResponseJsonData(resp *Response, data ...interface{}) {
switch len(data) {
case 2:
resp.Data2 = data[1]
@ -37,18 +36,16 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
c.ServeJSON()
}
// ResponseOk ...
func (c *ApiController) ResponseOk(data ...interface{}) {
resp := &Response{Status: "ok"}
c.ResponseJsonData(resp, data...)
}
// ResponseError ...
func (c *ApiController) ResponseError(error string, data ...interface{}) {
resp := Response{Status: "error", Msg: error}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
c.Data["json"] = resp
c.ServeJSON()
resp := &Response{Status: "error", Msg: error}
c.ResponseJsonData(resp, data...)
}
// SetTokenErrorHttpStatus ...
@ -78,13 +75,8 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
return userId, true
}
func getInitScore() int {
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
if err != nil {
panic(err)
}
return score
func getInitScore() (int, error) {
return strconv.Atoi(conf.GetConfigString("initScore"))
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {

View File

@ -76,7 +76,8 @@ func (c *ApiController) UpdateWebhook() {
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateWebhook(id, &webhook))
@ -94,7 +95,8 @@ func (c *ApiController) AddWebhook() {
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddWebhook(&webhook))
@ -112,7 +114,8 @@ func (c *ApiController) DeleteWebhook() {
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteWebhook(&webhook))

2
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.2.0
github.com/casdoor/go-sms-sender v0.3.0
github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f

4
go.sum
View File

@ -98,8 +98,8 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/go-sms-sender v0.3.0 h1:c4bWVcKZhO2L3Xu1oy7aeVkCK6HRJkW/b5K1xU9mV60=
github.com/casdoor/go-sms-sender v0.3.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=

View File

@ -18,6 +18,8 @@ import (
"fmt"
"regexp"
"strings"
"time"
"unicode"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
@ -29,6 +31,11 @@ var (
reFieldWhiteList *regexp.Regexp
)
const (
SigninWrongTimesLimit = 5
LastSignWrongTimeDuration = time.Minute * 15
)
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
@ -42,11 +49,25 @@ func CheckUserSignup(application *Application, organization *Organization, usern
if application.IsSignupItemVisible("Username") {
if len(username) <= 1 {
return "username must have at least 2 characters"
} else if reWhiteSpace.MatchString(username) {
}
if unicode.IsDigit(rune(username[0])) {
return "username cannot start with a digit"
}
if util.IsEmailValid(username) {
return "username cannot be an email address"
}
if reWhiteSpace.MatchString(username) {
return "username cannot contain white spaces"
} else if HasUserByField(organization.Name, "name", username) {
}
if HasUserByField(organization.Name, "name", username) {
return "username already exists"
}
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
}
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
}
}
if len(password) <= 5 {
@ -112,7 +133,32 @@ func CheckUserSignup(application *Application, organization *Organization, usern
return ""
}
func checkSigninErrorTimes(user *User) string {
if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
seconds := int(LastSignWrongTimeDuration.Seconds() - passedTime.Seconds())
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if seconds > 0 {
return fmt.Sprintf("You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again", seconds/60, seconds%60)
}
// reset the error times
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, user.IsGlobalAdmin)
}
return ""
}
func CheckPassword(user *User, password string) string {
// check the login error times
if msg := checkSigninErrorTimes(user); msg != "" {
return msg
}
organization := GetOrganizationByUser(user)
if organization == nil {
return "organization does not exist"
@ -122,14 +168,17 @@ func CheckPassword(user *User, password string) string {
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
}
}
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
}
return "password incorrect"
return recordSigninErrorInfo(user)
} else {
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
}

View File

@ -14,7 +14,11 @@
package object
import "regexp"
import (
"fmt"
"regexp"
"time"
)
var reRealName *regexp.Regexp
@ -29,3 +33,32 @@ func init() {
func isValidRealName(s string) bool {
return reRealName.MatchString(s)
}
func resetUserSigninErrorTimes(user *User) {
// if the password is correct and wrong times is not zero, reset the error times
if user.SigninWrongTimes == 0 {
return
}
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
}
func recordSigninErrorInfo(user *User) string {
// increase failed login count
user.SigninWrongTimes++
if user.SigninWrongTimes >= SigninWrongTimesLimit {
// record the latest failed login time
user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339)
}
// update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances > 0 {
return fmt.Sprintf("password is incorrect, you have %d remaining chances", leftChances)
}
// don't show the chance error message if the user has no chance left
return fmt.Sprintf("You have entered the wrong password too many times, please wait for %d minutes and try again", int(LastSignWrongTimeDuration.Minutes()))
}

View File

@ -16,8 +16,10 @@ package object
import (
"encoding/gob"
"fmt"
"os"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
)
@ -36,6 +38,8 @@ func InitDb() {
initWebAuthn()
}
var staticBaseUrl = beego.AppConfig.String("staticBaseUrl")
func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in")
if organization != nil {
@ -48,10 +52,10 @@ func initBuiltInOrganization() bool {
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: "https://cdn.casbin.com/static/favicon.ico",
Favicon: fmt.Sprintf("%s/img/casbin/favicon.ico", staticBaseUrl),
PasswordType: "plain",
PhonePrefix: "86",
DefaultAvatar: "https://casbin.org/img/casbin.svg",
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", staticBaseUrl),
Tags: []string{},
AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
@ -100,7 +104,7 @@ func initBuiltInUser() {
Type: "normal-user",
Password: "123",
DisplayName: "Admin",
Avatar: "https://casbin.org/img/casbin.svg",
Avatar: fmt.Sprintf("%s/img/casbin.svg", staticBaseUrl),
Email: "admin@example.com",
Phone: "12345678910",
Address: []string{},
@ -130,7 +134,7 @@ func initBuiltInApplication() {
Name: "app-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Casdoor",
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
Logo: fmt.Sprintf("%s/img/casdoor-logo_1185x256.png", staticBaseUrl),
HomepageUrl: "https://casdoor.org",
Organization: "built-in",
Cert: "cert-built-in",
@ -247,6 +251,7 @@ func initBuiltInPermission() {
DisplayName: "Built-in Permission",
Users: []string{"built-in/admin"},
Roles: []string{},
Domains: []string{},
ResourceType: "Application",
Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"},

View File

@ -27,16 +27,22 @@ type Permission struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
Domains []string `xorm:"mediumtext" json:"domains"`
Model string `xorm:"varchar(100)" json:"model"`
Adapter string `xorm:"varchar(100)" json:"adapter"`
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
Resources []string `xorm:"mediumtext" json:"resources"`
Actions []string `xorm:"mediumtext" json:"actions"`
Effect string `xorm:"varchar(100)" json:"effect"`
IsEnabled bool `json:"isEnabled"`
IsEnabled bool `json:"isEnabled"`
Submitter string `xorm:"varchar(100)" json:"submitter"`
Approver string `xorm:"varchar(100)" json:"approver"`
ApproveTime string `xorm:"varchar(100)" json:"approveTime"`
State string `xorm:"varchar(100)" json:"state"`
}
type PermissionRule struct {
@ -47,6 +53,7 @@ type PermissionRule struct {
V3 string `xorm:"varchar(100) index not null default ''" json:"v3"`
V4 string `xorm:"varchar(100) index not null default ''" json:"v4"`
V5 string `xorm:"varchar(100) index not null default ''" json:"v5"`
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
}
func GetPermissionCount(owner, field, value string) int {
@ -117,6 +124,15 @@ func UpdatePermission(id string, permission *Permission) bool {
if affected != 0 {
removePolicies(oldPermission)
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
isEmpty, _ := adapter.Engine.IsTableEmpty(oldPermission.Adapter)
if isEmpty {
err = adapter.Engine.DropTables(oldPermission.Adapter)
if err != nil {
panic(err)
}
}
}
addPolicies(permission)
}
@ -144,6 +160,15 @@ func DeletePermission(permission *Permission) bool {
if affected != 0 {
removePolicies(permission)
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
isEmpty, _ := adapter.Engine.IsTableEmpty(permission.Adapter)
if isEmpty {
err = adapter.Engine.DropTables(permission.Adapter)
if err != nil {
panic(err)
}
}
}
}
return affected != 0
@ -162,3 +187,23 @@ func GetPermissionsByUser(userId string) []*Permission {
return permissions
}
func GetPermissionsByRole(roleId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("roles like ?", "%"+roleId+"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}
func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
if err != nil {
panic(err)
}
return permissions
}

View File

@ -24,8 +24,12 @@ import (
)
func getEnforcer(permission *Permission) *casbin.Enforcer {
tableName := "permission_rule"
if len(permission.Adapter) != 0 {
tableName = permission.Adapter
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "permission_rule", tableNamePrefix, true)
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), tableName, tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -35,13 +39,16 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
r = sub, obj, act
[policy_definition]
p = permission, sub, obj, act
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
permissionModel := getModel(permission.Owner, permission.Model)
if permissionModel != nil {
modelText = permissionModel.ModelText
@ -56,36 +63,71 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`
panic(err)
}
err = enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}})
if err != nil {
panic(err)
}
return enforcer
}
func getPolicies(permission *Permission) [][]string {
func getPolicies(permission *Permission) ([][]string, [][]string) {
var policies [][]string
var groupingPolicies [][]string
domainExist := len(permission.Domains) > 0
for _, user := range permission.Users {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
policies = append(policies, []string{permission.GetId(), user, resource, strings.ToLower(action)})
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{user, domain, resource, strings.ToLower(action)})
}
} else {
policies = append(policies, []string{user, resource, strings.ToLower(action)})
}
}
}
}
for _, role := range permission.Roles {
roleObj := GetRole(role)
for _, subUser := range roleObj.Users {
if domainExist {
for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role})
}
} else {
groupingPolicies = append(groupingPolicies, []string{subUser, role})
}
}
for _, subRole := range roleObj.Roles {
if domainExist {
for _, domain := range permission.Domains {
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role})
}
} else {
groupingPolicies = append(groupingPolicies, []string{subRole, role})
}
}
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
policies = append(policies, []string{permission.GetId(), role, resource, strings.ToLower(action)})
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{role, domain, resource, strings.ToLower(action)})
}
} else {
policies = append(policies, []string{role, resource, strings.ToLower(action)})
}
}
}
}
return policies
return policies, groupingPolicies
}
func addPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
policies := getPolicies(permission)
policies, groupingPolicies := getPolicies(permission)
if len(groupingPolicies) > 0 {
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {
panic(err)
}
}
_, err := enforcer.AddPolicies(policies)
if err != nil {
@ -95,17 +137,25 @@ func addPolicies(permission *Permission) {
func removePolicies(permission *Permission) {
enforcer := getEnforcer(permission)
policies, groupingPolicies := getPolicies(permission)
_, err := enforcer.RemoveFilteredPolicy(0, permission.GetId())
if len(groupingPolicies) > 0 {
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
if err != nil {
panic(err)
}
}
_, err := enforcer.RemovePolicies(policies)
if err != nil {
panic(err)
}
}
func Enforce(userId string, permissionRule *PermissionRule) bool {
permission := GetPermission(permissionRule.V0)
permission := GetPermission(permissionRule.Id)
enforcer := getEnforcer(permission)
allow, err := enforcer.Enforce(userId, permissionRule.V2, permissionRule.V3)
allow, err := enforcer.Enforce(userId, permissionRule.V1, permissionRule.V2)
if err != nil {
panic(err)
}
@ -115,9 +165,9 @@ func Enforce(userId string, permissionRule *PermissionRule) bool {
func BatchEnforce(userId string, permissionRules []PermissionRule) []bool {
var requests [][]interface{}
for _, permissionRule := range permissionRules {
requests = append(requests, []interface{}{userId, permissionRule.V2, permissionRule.V3})
requests = append(requests, []interface{}{userId, permissionRule.V1, permissionRule.V2})
}
permission := GetPermission(permissionRules[0].V0)
permission := GetPermission(permissionRules[0].Id)
enforcer := getEnforcer(permission)
allow, err := enforcer.BatchEnforce(requests)
if err != nil {
@ -126,30 +176,30 @@ func BatchEnforce(userId string, permissionRules []PermissionRule) []bool {
return allow
}
func getAllValues(userId string, sec string, fieldIndex int) []string {
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
permissions := GetPermissionsByUser(userId)
for _, role := range GetAllRoles(userId) {
permissions = append(permissions, GetPermissionsByRole(role)...)
}
var values []string
for _, permission := range permissions {
enforcer := getEnforcer(permission)
enforcer.ClearPolicy()
err := enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}, V1: []string{userId}})
if err != nil {
return nil
}
for _, value := range enforcer.GetModel().GetValuesForFieldInPolicyAllTypes(sec, fieldIndex) {
values = append(values, value)
}
values = append(values, fn(enforcer)...)
}
return values
}
func GetAllObjects(userId string) []string {
return getAllValues(userId, "p", 2)
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllObjects()
})
}
func GetAllActions(userId string) []string {
return getAllValues(userId, "p", 3)
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllActions()
})
}
func GetAllRoles(userId string) []string {

View File

@ -29,6 +29,7 @@ type Role struct {
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
Domains []string `xorm:"mediumtext" json:"domains"`
IsEnabled bool `json:"isEnabled"`
}
@ -88,7 +89,8 @@ func GetRole(id string) *Role {
func UpdateRole(id string, role *Role) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getRole(owner, name) == nil {
oldRole := getRole(owner, name)
if oldRole == nil {
return false
}

View File

@ -287,7 +287,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
panic(err)
}
@ -298,7 +298,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
@ -420,7 +420,8 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return &TokenError{
Error: EndpointError,
@ -430,7 +431,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
newToken := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
@ -569,7 +570,8 @@ func GetPasswordToken(application *Application, username string, password string
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@ -578,7 +580,7 @@ func GetPasswordToken(application *Application, username string, password string
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
@ -609,7 +611,8 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
Id: application.GetId(),
Name: fmt.Sprintf("app/%s", application.Name),
}
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@ -618,7 +621,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: application.Organization,
@ -637,13 +640,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
@ -723,7 +726,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
AddUser(user)
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@ -733,7 +736,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
Name: tokenName,
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,

View File

@ -19,6 +19,7 @@ import (
"time"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/golang-jwt/jwt/v4"
)
@ -60,7 +61,7 @@ func getShortClaims(claims Claims) ClaimsShort {
return res
}
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, error) {
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
@ -72,6 +73,9 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
originBackend = origin
}
name := util.GenerateId()
jti := fmt.Sprintf("%s/%s", application.Owner, name)
claims := Claims{
User: user,
Nonce: nonce,
@ -85,7 +89,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
ExpiresAt: jwt.NewNumericDate(expireTime),
NotBefore: jwt.NewNumericDate(nowTime),
IssuedAt: jwt.NewNumericDate(nowTime),
ID: "",
ID: jti,
},
}
@ -110,17 +114,17 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
// RSA private key
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
if err != nil {
return "", "", err
return "", "", "", err
}
token.Header["kid"] = cert.Name
tokenString, err := token.SignedString(key)
if err != nil {
return "", "", err
return "", "", "", err
}
refreshTokenString, err := refreshToken.SignedString(key)
return tokenString, refreshTokenString, err
return tokenString, refreshTokenString, name, err
}
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {

View File

@ -111,6 +111,9 @@ type User struct {
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
}
type Userinfo struct {
@ -376,6 +379,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials",
"signin_wrong_times", "last_signin_wrong_time",
}
}
if isGlobalAdmin {

View File

@ -78,6 +78,7 @@ func initAPI() {
beego.Router("/api/delete-role", &controllers.ApiController{}, "POST:DeleteRole")
beego.Router("/api/get-permissions", &controllers.ApiController{}, "GET:GetPermissions")
beego.Router("/api/get-permissions-by-submitter", &controllers.ApiController{}, "GET:GetPermissionsBySubmitter")
beego.Router("/api/get-permission", &controllers.ApiController{}, "GET:GetPermission")
beego.Router("/api/update-permission", &controllers.ApiController{}, "POST:UpdatePermission")
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")

36
storage/minio_s3.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/casdoor/oss"
"github.com/casdoor/oss/s3"
)
func NewMinIOS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := s3.New(&s3.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
S3Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
S3ForcePathStyle: true,
})
return sp
}

View File

@ -22,6 +22,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "MinIO":
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Tencent Cloud COS":

View File

@ -20,7 +20,6 @@
"plugins": ["unused-imports"],
"extends": ["eslint:recommended", "plugin:react/recommended"],
"rules": {
// "eqeqeq": "error",
"semi": ["error", "always"],
"indent": ["error", 2],
// follow antd's style guide
@ -87,15 +86,15 @@
"argsIgnorePattern": "^_"
}
],
"no-unused-vars": "off",
"react/no-deprecated": "error",
"react/jsx-key": "error",
"no-console": "error",
"eqeqeq": "error",
"react/prop-types": "off",
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
// don't use strict mod now, otherwise there are a lot of errors in the codebase
"no-unused-vars": "off",
"react/no-deprecated": "warn",
"no-case-declarations": "off",
"react/jsx-key": "warn"
"no-case-declarations": "off"
}
}

View File

@ -385,13 +385,17 @@ class App extends Component {
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/permissions">
<Link to="/permissions">
{i18next.t("general:Permissions")}
</Link>
</Menu.Item>
);
}
res.push(
<Menu.Item key="/permissions">
<Link to="/permissions">
{i18next.t("general:Permissions")}
</Link>
</Menu.Item>
);
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/models">
<Link to="/models">
@ -669,26 +673,28 @@ class App extends Component {
if (this.isDoorPages()) {
return (
<div style={{position: "relative", minHeight: "100vh"}}>
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
<div id="content-wrap" style={{flexDirection: "column"}}>
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</div>
{
this.renderFooter()
}

View File

@ -1,5 +1,7 @@
@import '~antd/dist/antd.less';
@StaticBaseUrl:"https://cdn.casbin.org";
.App {
text-align: center;
}
@ -46,7 +48,7 @@
}
.language_box {
background: url("https://cdn.casbin.org/img/muti_language.svg");
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center;
background-repeat: no-repeat;

View File

@ -30,7 +30,7 @@ class ApplicationListPage extends BaseListPage {
name: `application_${randomName}`,
createdTime: moment().format(),
displayName: `New Application - ${randomName}`,
logo: "https://cdn.casdoor.com/logo/casdoor-logo_1185x256.png",
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
enablePassword: true,
enableSignUp: true,
enableSigninSession: false,

View File

@ -81,7 +81,6 @@ export const CropperDiv = (props) => {
};
const handleCancel = () => {
console.log("Clicked cancel button");
setVisible(false);
};

View File

@ -30,11 +30,11 @@ class OrganizationListPage extends BaseListPage {
createdTime: moment().format(),
displayName: `New Organization - ${randomName}`,
websiteUrl: "https://door.casdoor.com",
favicon: "https://cdn.casdoor.com/static/favicon.png",
favicon: `${Setting.StaticBaseUrl}/img/favicon.png`,
passwordType: "plain",
PasswordSalt: "",
phonePrefix: "86",
defaultAvatar: "https://casbin.org/img/casbin.svg",
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
tags: [],
masterPassword: "",
enableSoftDeletion: false,

View File

@ -22,6 +22,7 @@ import i18next from "i18next";
import * as RoleBackend from "./backend/RoleBackend";
import * as ModelBackend from "./backend/ModelBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import moment from "moment/moment";
const {Option} = Select;
@ -186,6 +187,16 @@ class PermissionEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Adapter"), i18next.t("general:Adapter - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.permission.adapter} onChange={e => {
this.updatePermissionField("adapter", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
@ -210,6 +221,20 @@ class PermissionEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub domains"), i18next.t("role:Sub domains - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.domains} onChange={(value => {
this.updatePermissionField("domains", value);
})}>
{
this.state.permission.domains.map((domain, index) => <Option key={index} value={domain}>{domain}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Resource type"), i18next.t("permission:Resource type - Tooltip"))} :
@ -283,11 +308,89 @@ class PermissionEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Submitter"), i18next.t("permission:Submitter - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.permission.submitter} onChange={e => {
this.updatePermissionField("submitter", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Approver"), i18next.t("permission:Approver - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.permission.approver} onChange={e => {
this.updatePermissionField("approver", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Approve time"), i18next.t("permission:Approve time - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={Setting.getFormattedDate(this.state.permission.approveTime)} onChange={e => {
this.updatePermissionField("approveTime", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:State"), i18next.t("permission:State - Tooltip"))} :
</Col>
<Col span={22} >
<Select disabled={!Setting.isLocalAdminUser(this.props.account)} virtual={false} style={{width: "100%"}} value={this.state.permission.state} onChange={(value => {
if (this.state.permission.state !== value) {
if (value === "Approved") {
this.updatePermissionField("approver", this.props.account.name);
this.updatePermissionField("approveTime", moment().format());
} else {
this.updatePermissionField("approver", "");
this.updatePermissionField("approveTime", "");
}
}
this.updatePermissionField("state", value);
})}>
{
[
{id: "Approved", name: "Approved"},
{id: "Pending", name: "Pending"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
</Card>
);
}
submitPermissionEdit(willExist) {
if (this.state.permission.users.length === 0 && this.state.permission.roles.length === 0) {
Setting.showMessage("error", "The users and roles cannot be empty at the same time");
return;
}
if (this.state.permission.domains.length === 0) {
Setting.showMessage("error", "The domains cannot be empty");
return;
}
if (this.state.permission.resources.length === 0) {
Setting.showMessage("error", "The resources cannot be empty");
return;
}
if (this.state.permission.actions.length === 0) {
Setting.showMessage("error", "The actions cannot be empty");
return;
}
if (!Setting.isLocalAdminUser(this.props.account) && this.state.permission.submitter !== this.props.account.name) {
Setting.showMessage("error", "A normal user can only modify the permission submitted by itself");
return;
}
const permission = Setting.deepCopy(this.state.permission);
PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission)
.then((res) => {

View File

@ -25,17 +25,22 @@ class PermissionListPage extends BaseListPage {
newPermission() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
owner: this.props.account.owner,
name: `permission_${randomName}`,
createdTime: moment().format(),
displayName: `New Permission - ${randomName}`,
users: [],
users: [this.props.account.name],
roles: [],
domains: [],
resourceType: "Application",
resources: ["app-built-in"],
actions: ["Read"],
effect: "Allow",
isEnabled: true,
submitter: this.props.account.name,
approver: "",
approveTime: "",
state: "Pending",
};
}
@ -43,6 +48,10 @@ class PermissionListPage extends BaseListPage {
const newPermission = this.newPermission();
PermissionBackend.addPermission(newPermission)
.then((res) => {
if (res.msg !== "") {
Setting.showMessage("error", res.msg);
return;
}
this.props.history.push({pathname: `/permissions/${newPermission.owner}/${newPermission.name}`, mode: "add"});
}
)
@ -139,6 +148,16 @@ class PermissionListPage extends BaseListPage {
return Setting.getTags(text);
},
},
{
title: i18next.t("role:Sub domains"),
dataIndex: "domains",
key: "domains",
sorter: true,
...this.getColumnSearchProps("domains"),
render: (text, record, index) => {
return Setting.getTags(text);
},
},
{
title: i18next.t("permission:Resource type"),
dataIndex: "resourceType",
@ -196,6 +215,45 @@ class PermissionListPage extends BaseListPage {
);
},
},
{
title: i18next.t("permission:Submitter"),
dataIndex: "submitter",
key: "submitter",
filterMultiple: false,
width: "120px",
sorter: true,
},
{
title: i18next.t("permission:Approver"),
dataIndex: "approver",
key: "approver",
filterMultiple: false,
width: "120px",
sorter: true,
},
{
title: i18next.t("permission:Approve time"),
dataIndex: "approveTime",
key: "approveTime",
filterMultiple: false,
width: "120px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("permission:State"),
dataIndex: "state",
key: "state",
filterMultiple: false,
filters: [
{text: "Approved", value: "Approved"},
{text: "Pending", value: "Pending"},
],
width: "120px",
sorter: true,
},
{
title: i18next.t("general:Action"),
dataIndex: "",
@ -249,7 +307,9 @@ class PermissionListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
PermissionBackend.getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -30,7 +30,7 @@ class ProductListPage extends BaseListPage {
name: `product_${randomName}`,
createdTime: moment().format(),
displayName: `New Product - ${randomName}`,
image: "https://cdn.casdoor.com/logo/casdoor-logo_1185x256.png",
image: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
tag: "Casdoor Summit 2022",
currency: "USD",
price: 300,

View File

@ -474,7 +474,7 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
{this.state.provider.type === "AWS S3" || this.state.provider.type === "Tencent Cloud COS" ? (
{["AWS S3", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :

View File

@ -164,6 +164,20 @@ class RoleEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub domains"), i18next.t("role:Sub domains - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.role.domains} onChange={(value => {
this.updateRoleField("domains", value);
})}>
{
this.state.role.domains.map((domain, index) => <Option key={index} value={domain}>{domain}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :

View File

@ -31,6 +31,7 @@ class RoleListPage extends BaseListPage {
displayName: `New Role - ${randomName}`,
users: [],
roles: [],
domains: [],
isEnabled: true,
};
}
@ -135,6 +136,16 @@ class RoleListPage extends BaseListPage {
return Setting.getTags(text);
},
},
{
title: i18next.t("role:Sub domains"),
dataIndex: "domains",
key: "domains",
sorter: true,
...this.getColumnSearchProps("domains"),
render: (text, record, index) => {
return Setting.getTags(text);
},
},
{
title: i18next.t("general:Is enabled"),
dataIndex: "isEnabled",

View File

@ -49,6 +49,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html",
},
"Mock SMS": {
logo: `${StaticBaseUrl}/img/social_default.png`,
url: "",
},
},
Email: {
"Default": {
@ -65,6 +69,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_aws.png`,
url: "https://aws.amazon.com/s3",
},
"MinIO": {
logo: "https://min.io/resources/img/logo.svg",
url: "https://min.io/",
},
"Aliyun OSS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/oss",
@ -373,6 +381,13 @@ export function isAdminUser(account) {
return account.owner === "built-in" || account.isGlobalAdmin === true;
}
export function isLocalAdminUser(account) {
if (account === undefined || account === null) {
return false;
}
return account.isAdmin === true || isAdminUser(account);
}
export function deepCopy(obj) {
return Object.assign({}, obj);
}
@ -532,7 +547,12 @@ export function getProviderLogoURL(provider) {
}
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else {
return OtherProviderInfo[provider.category][provider.type].logo;
const info = OtherProviderInfo[provider.category][provider.type];
// avoid crash when provider is not found
if (info) {
return info.logo;
}
return "";
}
}
@ -596,6 +616,7 @@ export function getProviderTypeOptions(category) {
[
{id: "Local File System", name: "Local File System"},
{id: "AWS S3", name: "AWS S3"},
{id: "MinIO", name: "MinIO"},
{id: "Aliyun OSS", name: "Aliyun OSS"},
{id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: "Azure Blob", name: "Azure Blob"},

View File

@ -49,7 +49,7 @@ class UserListPage extends BaseListPage {
password: "123",
passwordSalt: "",
displayName: `New User - ${randomName}`,
avatar: "https://casbin.org/img/casbin.svg",
avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
email: `${randomName}@example.com`,
phone: Setting.getRandomNumber(),
address: [],

View File

@ -207,6 +207,7 @@ class ForgetPage extends React.Component {
hidden={this.state.current !== 0}
ref={this.form}
name="step1"
// eslint-disable-next-line no-console
onFinishFailed={(errorInfo) => console.log(errorInfo)}
initialValues={{
application: application.name,

View File

@ -164,7 +164,7 @@ class LoginPage extends React.Component {
} else {
// OAuth
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
if (oAuthParams !== null && oAuthParams.responseType !== null && oAuthParams.responseType !== "") {
values["type"] = oAuthParams.responseType;
} else {
values["type"] = this.state.type;
@ -175,11 +175,11 @@ class LoginPage extends React.Component {
values["samlRequest"] = oAuthParams.samlRequest;
}
if (values["samlRequest"] != null && values["samlRequest"] !== "") {
if (values["samlRequest"] !== null && values["samlRequest"] !== "" && values["samlRequest"] !== undefined) {
values["type"] = "saml";
}
if (this.state.owner != null) {
if (this.state.owner !== null && this.state.owner !== undefined) {
values["organization"] = this.state.owner;
}

View File

@ -137,7 +137,12 @@ export function getProviderUrl(provider) {
return `${urlObj.protocol}//${host}`;
} else {
return Setting.OtherProviderInfo[provider.category][provider.type].url;
const info = Setting.OtherProviderInfo[provider.category][provider.type];
// avoid crash when provider is not found
if (info) {
return info.url;
}
return "";
}
}

View File

@ -582,7 +582,7 @@ class SignupPage extends React.Component {
&nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp;
<a onClick={() => {
const linkInStorage = sessionStorage.getItem("signinUrl");
if(linkInStorage != null) {
if(linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
}else{
Setting.goToLogin(this, application);

View File

@ -21,6 +21,13 @@ export function getPermissions(owner, page = "", pageSize = "", field = "", valu
}).then(res => res.json());
}
export function getPermissionsBySubmitter() {
return fetch(`${Setting.ServerUrl}/api/get-permissions-by-submitter`, {
method: "GET",
credentials: "include",
}).then(res => res.json());
}
export function getPermission(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-permission?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",

View File

@ -56,12 +56,11 @@ class HomePage extends React.Component {
if (filename === "/account") {
filename = "/users";
}
items[i].logo = `https://cdn.casbin.com/static/img${filename}.png`;
items[i].logo = `${Setting.StaticBaseUrl}/img${filename}.png`;
items[i].createdTime = "";
}
} else {
this.state.applications.forEach(application => {
console.log(application);
items.push({
link: application.homepageUrl, name: application.displayName, organizer: application.description, logo: application.logo, createdTime: "",
});

View File

@ -35,7 +35,7 @@ const resources = {
function initLanguage() {
let language = localStorage.getItem("language");
if (language === undefined || language == null) {
if (language === undefined || language === null || language === "") {
if (Conf.ForceLanguage !== "") {
language = Conf.ForceLanguage;
} else {

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "Aktion",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "Neu",
"Affiliation URL": "Affiliation-URL",
"Affiliation URL - Tooltip": "Unique string-style identifier",
@ -343,6 +345,10 @@
"permission": {
"Actions": "Aktionen",
"Actions - Tooltip": "Aktionen - Tooltip",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "Berechtigung bearbeiten",
"Effect": "Effekt",
"Effect - Tooltip": "Effekt - Tooltip",
@ -350,7 +356,11 @@
"Resource type": "Ressourcentyp",
"Resource type - Tooltip": "Ressourcentyp - Tooltip",
"Resources": "Ressourcen",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "Rolle bearbeiten",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "Unterrollen",
"Sub roles - Tooltip": "Unterrollen - Tooltip",
"Sub users": "Unternutzer",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "Action",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "Add",
"Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "Affiliation URL - Tooltip",
@ -343,6 +345,10 @@
"permission": {
"Actions": "Actions",
"Actions - Tooltip": "Actions - Tooltip",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "Edit Permission",
"Effect": "Effect",
"Effect - Tooltip": "Effect - Tooltip",
@ -350,7 +356,11 @@
"Resource type": "Resource type",
"Resource type - Tooltip": "Resource type - Tooltip",
"Resources": "Resources",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "Edit Role",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "Sub roles",
"Sub roles - Tooltip": "Sub roles - Tooltip",
"Sub users": "Sub users",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "Action",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "Ajouter",
"Affiliation URL": "URL d'affiliation",
"Affiliation URL - Tooltip": "Unique string-style identifier",
@ -343,6 +345,10 @@
"permission": {
"Actions": "Actions",
"Actions - Tooltip": "Actions - Info-bulle",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "Autorisation d'édition",
"Effect": "Effet",
"Effect - Tooltip": "Effet - Infobulle",
@ -350,7 +356,11 @@
"Resource type": "Type de ressource",
"Resource type - Tooltip": "Type de ressource - infobulle",
"Resources": "Ressource",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "Modifier le rôle",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "Sous-rôles",
"Sub roles - Tooltip": "Sous-rôles - infobulle",
"Sub users": "Sous-utilisateurs",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "アクション",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "追加",
"Affiliation URL": "アフィリエイトURL",
"Affiliation URL - Tooltip": "Unique string-style identifier",
@ -343,6 +345,10 @@
"permission": {
"Actions": "アクション",
"Actions - Tooltip": "アクション → ツールチップ",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "権限を編集",
"Effect": "効果",
"Effect - Tooltip": "エフェクト - ツールチップ",
@ -350,7 +356,11 @@
"Resource type": "リソースタイプ",
"Resource type - Tooltip": "リソースタイプ - ツールチップ",
"Resources": "リソース",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "役割を編集",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "サブロール",
"Sub roles - Tooltip": "Sub roles - Tooltip",
"Sub users": "サブユーザー",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "Action",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "Add",
"Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "Unique string-style identifier",
@ -343,6 +345,10 @@
"permission": {
"Actions": "Actions",
"Actions - Tooltip": "Actions - Tooltip",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "Edit Permission",
"Effect": "Effect",
"Effect - Tooltip": "Effect - Tooltip",
@ -350,7 +356,11 @@
"Resource type": "Resource type",
"Resource type - Tooltip": "Resource type - Tooltip",
"Resources": "Resources",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "Edit Role",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "Sub roles",
"Sub roles - Tooltip": "Sub roles - Tooltip",
"Sub users": "Sub users",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "Действие",
"Adapter": "Adapter",
"Adapter - Tooltip": "Adapter - Tooltip",
"Add": "Добавить",
"Affiliation URL": "URL-адрес партнёра",
"Affiliation URL - Tooltip": "Unique string-style identifier",
@ -343,6 +345,10 @@
"permission": {
"Actions": "Действия",
"Actions - Tooltip": "Действия - Подсказка",
"Approve time": "Approve time",
"Approve time - Tooltip": "Approve time - Tooltip",
"Approver": "Approver",
"Approver - Tooltip": "Approver - Tooltip",
"Edit Permission": "Изменить права доступа",
"Effect": "Эффект",
"Effect - Tooltip": "Эффект - Подсказка",
@ -350,7 +356,11 @@
"Resource type": "Тип ресурса",
"Resource type - Tooltip": "Тип ресурса - Подсказка",
"Resources": "Ресурсы",
"Resources - Tooltip": "Resources - Tooltip"
"Resources - Tooltip": "Resources - Tooltip",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Submitter": "Submitter",
"Submitter - Tooltip": "Submitter - Tooltip"
},
"product": {
"Alipay": "Alipay",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "Изменить роль",
"New Role": "New Role",
"Sub domains": "Sub domains",
"Sub domains - Tooltip": "Sub domains - Tooltip",
"Sub roles": "Суб роли",
"Sub roles - Tooltip": "Суб роли - Tooltip",
"Sub users": "Субпользователи",

View File

@ -98,6 +98,8 @@
},
"general": {
"Action": "操作",
"Adapter": "适配器",
"Adapter - Tooltip": "策略存储的表名",
"Add": "添加",
"Affiliation URL": "工作单位URL",
"Affiliation URL - Tooltip": "工作单位URL",
@ -343,6 +345,10 @@
"permission": {
"Actions": "动作",
"Actions - Tooltip": "授权的动作",
"Approve time": "审批时间",
"Approve time - Tooltip": "该授权被审批通过的时间",
"Approver": "审批者",
"Approver - Tooltip": "审批通过该授权的人",
"Edit Permission": "编辑权限",
"Effect": "效果",
"Effect - Tooltip": "允许还是拒绝",
@ -350,7 +356,11 @@
"Resource type": "资源类型",
"Resource type - Tooltip": "授权资源的类型",
"Resources": "资源",
"Resources - Tooltip": "被授权的资源"
"Resources - Tooltip": "被授权的资源",
"State": "审批状态",
"State - Tooltip": "该授权现在的状态",
"Submitter": "申请者",
"Submitter - Tooltip": "申请该授权的人"
},
"product": {
"Alipay": "支付宝",
@ -519,6 +529,8 @@
"role": {
"Edit Role": "编辑角色",
"New Role": "添加角色",
"Sub domains": "包含域",
"Sub domains - Tooltip": "当前角色所包含的子域",
"Sub roles": "包含角色",
"Sub roles - Tooltip": "当前角色所包含的子角色",
"Sub users": "包含用户",

View File

@ -55,6 +55,7 @@ export function register(config) {
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
// eslint-disable-next-line no-console
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://bit.ly/CRA-PWA"
@ -74,7 +75,7 @@ function registerValidSW(swUrl, config) {
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
if (installingWorker === null) {
return;
}
installingWorker.onstatechange = () => {
@ -83,6 +84,7 @@ function registerValidSW(swUrl, config) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
// eslint-disable-next-line no-console
console.log(
"New content is available and will be used when all " +
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
@ -96,6 +98,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
// eslint-disable-next-line no-console
console.log("Content is cached for offline use.");
// Execute callback
@ -108,6 +111,7 @@ function registerValidSW(swUrl, config) {
};
})
.catch(error => {
// eslint-disable-next-line no-console
console.error("Error during service worker registration:", error);
});
}
@ -122,7 +126,7 @@ function checkValidServiceWorker(swUrl, config) {
const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
(contentType != null && contentType.indexOf("javascript") === -1)
(contentType !== null && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
@ -136,6 +140,7 @@ function checkValidServiceWorker(swUrl, config) {
}
})
.catch(() => {
// eslint-disable-next-line no-console
console.log(
"No internet connection found. App is running in offline mode."
);
@ -149,6 +154,7 @@ export function unregister() {
registration.unregister();
})
.catch(error => {
// eslint-disable-next-line no-console
console.error(error.message);
});
}