mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-29 01:51:38 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aedef1eea1 | ||
![]() |
70f2988f09 | ||
![]() |
2dcdfbe6d3 | ||
![]() |
c92d34e27c | ||
![]() |
dfbf7753c3 | ||
![]() |
ba732b3075 | ||
![]() |
ca13247572 | ||
![]() |
108fdc174f | ||
![]() |
a741c5179a | ||
![]() |
6676cc8ff3 | ||
![]() |
13de019d08 | ||
![]() |
53ad454962 |
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -15,4 +15,5 @@ socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
origin =
|
||||
origin =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
@@ -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 (
|
||||
|
@@ -138,9 +138,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"}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -269,10 +269,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
|
||||
|
@@ -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", username) {
|
||||
return "email already exists"
|
||||
}
|
||||
if HasUserByField(organization.Name, "phone", username) {
|
||||
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)
|
||||
}
|
||||
|
@@ -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()))
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -27,16 +27,21 @@ 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"`
|
||||
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 {
|
||||
@@ -162,3 +167,13 @@ func GetPermissionsByUser(userId string) []*Permission {
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -37,11 +37,14 @@ r = sub, obj, act
|
||||
[policy_definition]
|
||||
p = permission, 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,11 +59,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -102,6 +100,37 @@ func removePolicies(permission *Permission) {
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupingPolicies(role *Role) [][]string {
|
||||
var groupingPolicies [][]string
|
||||
for _, subUser := range role.Users {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role.GetId()})
|
||||
}
|
||||
for _, subRole := range role.Roles {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role.GetId()})
|
||||
}
|
||||
return groupingPolicies
|
||||
}
|
||||
|
||||
func addGroupingPolicies(role *Role) {
|
||||
enforcer := getEnforcer(&Permission{})
|
||||
groupingPolicies := getGroupingPolicies(role)
|
||||
|
||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeGroupingPolicies(role *Role) {
|
||||
enforcer := getEnforcer(&Permission{})
|
||||
groupingPolicies := getGroupingPolicies(role)
|
||||
|
||||
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Enforce(userId string, permissionRule *PermissionRule) bool {
|
||||
permission := GetPermission(permissionRule.V0)
|
||||
enforcer := getEnforcer(permission)
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
@@ -97,6 +99,11 @@ func UpdateRole(id string, role *Role) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(oldRole)
|
||||
addGroupingPolicies(role)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
@@ -106,6 +113,10 @@ func AddRole(role *Role) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
addGroupingPolicies(role)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
@@ -115,6 +126,10 @@ func DeleteRole(role *Role) bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(role)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
@@ -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")
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -81,7 +81,6 @@ export const CropperDiv = (props) => {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
console.log("Clicked cancel button");
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
||||
@@ -210,6 +211,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.updateRoleField("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,6 +298,63 @@ 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>
|
||||
);
|
||||
}
|
||||
|
@@ -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",
|
||||
@@ -249,7 +268,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.isAdminUser(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({
|
||||
|
@@ -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,
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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",
|
||||
|
@@ -373,6 +373,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);
|
||||
}
|
||||
|
@@ -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: [],
|
||||
|
@@ -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,
|
||||
|
@@ -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) {
|
||||
values["organization"] = this.state.owner;
|
||||
}
|
||||
|
||||
|
@@ -582,7 +582,7 @@ class SignupPage extends React.Component {
|
||||
{i18next.t("signup:Have account?")}
|
||||
<a onClick={() => {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if(linkInStorage != null) {
|
||||
if(linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
}else{
|
||||
Setting.goToLogin(this, application);
|
||||
|
@@ -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",
|
||||
|
@@ -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: "",
|
||||
});
|
||||
|
@@ -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 {
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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",
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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",
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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",
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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": "サブユーザー",
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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",
|
||||
|
@@ -343,6 +343,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 +354,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 +527,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": "Субпользователи",
|
||||
|
@@ -343,6 +343,10 @@
|
||||
"permission": {
|
||||
"Actions": "动作",
|
||||
"Actions - Tooltip": "授权的动作",
|
||||
"Approve time": "审批时间",
|
||||
"Approve time - Tooltip": "该授权被审批通过的时间",
|
||||
"Approver": "审批者",
|
||||
"Approver - Tooltip": "审批通过该授权的人",
|
||||
"Edit Permission": "编辑权限",
|
||||
"Effect": "效果",
|
||||
"Effect - Tooltip": "允许还是拒绝",
|
||||
@@ -350,7 +354,11 @@
|
||||
"Resource type": "资源类型",
|
||||
"Resource type - Tooltip": "授权资源的类型",
|
||||
"Resources": "资源",
|
||||
"Resources - Tooltip": "被授权的资源"
|
||||
"Resources - Tooltip": "被授权的资源",
|
||||
"State": "审批状态",
|
||||
"State - Tooltip": "该授权现在的状态",
|
||||
"Submitter": "申请者",
|
||||
"Submitter - Tooltip": "申请该授权的人"
|
||||
},
|
||||
"product": {
|
||||
"Alipay": "支付宝",
|
||||
@@ -519,6 +527,8 @@
|
||||
"role": {
|
||||
"Edit Role": "编辑角色",
|
||||
"New Role": "添加角色",
|
||||
"Sub domains": "包含域",
|
||||
"Sub domains - Tooltip": "当前角色所包含的子域",
|
||||
"Sub roles": "包含角色",
|
||||
"Sub roles - Tooltip": "当前角色所包含的子角色",
|
||||
"Sub users": "包含用户",
|
||||
|
@@ -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);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user