mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-20 15:03:50 +08:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
9092cad631 | |||
0b5ecca5c8 | |||
3d9b305bbb | |||
0217e359e7 | |||
695a612e77 | |||
645d53e2c6 | |||
73b9d73f64 | |||
c6675ee4e6 | |||
6f0b7f3f24 | |||
776a682fae | |||
96a3db21a1 | |||
c33d537ac1 | |||
5214d48486 | |||
e360b06d12 | |||
3c871c38df |
@ -1,11 +1,11 @@
|
||||
FROM node:16.13.0 AS FRONT
|
||||
FROM node:16.18.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn config set registry https://registry.npmmirror.com
|
||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||
|
||||
|
||||
FROM golang:1.17.5 AS BACK
|
||||
FROM golang:1.19.9 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh
|
||||
|
10
ai/ai.go
10
ai/ai.go
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -78,7 +79,10 @@ func QueryAnswerStream(authToken string, question string, writer io.Writer, buil
|
||||
client := getProxyClientFromToken(authToken)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
flusher, ok := writer.(http.Flusher)
|
||||
if !ok {
|
||||
return fmt.Errorf("writer does not implement http.Flusher")
|
||||
}
|
||||
// https://platform.openai.com/tokenizer
|
||||
// https://github.com/pkoukk/tiktoken-go#available-encodings
|
||||
promptTokens, err := getTokenSize(openai.GPT3TextDavinci003, question)
|
||||
@ -122,11 +126,13 @@ func QueryAnswerStream(authToken string, question string, writer io.Writer, buil
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s", data)
|
||||
|
||||
// Write the streamed data as Server-Sent Events
|
||||
if _, err = fmt.Fprintf(writer, "data: %s\n\n", data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flusher.Flush()
|
||||
// Append the response to the strings.Builder
|
||||
builder.WriteString(data)
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, GET, /api/health, *, *
|
||||
p, *, *, POST, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, GET, /api/get-captcha-status, *, *
|
||||
|
@ -312,6 +312,11 @@ func (c *ApiController) Login() {
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() {
|
||||
resp.Msg = object.RequiredMfa
|
||||
}
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
// @router /get-chats [get]
|
||||
func (c *ApiController) GetChats() {
|
||||
owner := c.Input().Get("owner")
|
||||
owner = "admin"
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
|
@ -18,30 +18,61 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) Enforce() {
|
||||
var permissionRule object.PermissionRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
|
||||
var request object.CasbinRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.Enforce(&permissionRule)
|
||||
c.ServeJSON()
|
||||
if permissionId != "" {
|
||||
c.Data["json"] = object.Enforce(permissionId, &request)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions := object.GetPermissionsByModel(owner, modelName)
|
||||
|
||||
res := []bool{}
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.Enforce(permission.GetId(), &request))
|
||||
}
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) BatchEnforce() {
|
||||
var permissionRules []object.PermissionRule
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
|
||||
var requests []object.CasbinRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.BatchEnforce(permissionRules)
|
||||
c.ServeJSON()
|
||||
if permissionId != "" {
|
||||
c.Data["json"] = object.BatchEnforce(permissionId, &requests)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions := object.GetPermissionsByModel(owner, modelName)
|
||||
|
||||
res := [][]bool{}
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.BatchEnforce(permission.GetId(), &requests))
|
||||
}
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
|
@ -107,9 +107,9 @@ func (c *ApiController) GetMessageAnswer() {
|
||||
return
|
||||
}
|
||||
|
||||
chatId := util.GetId(message.Owner, message.Chat)
|
||||
chatId := util.GetId("admin", message.Chat)
|
||||
chat := object.GetChat(chatId)
|
||||
if chat == nil {
|
||||
if chat == nil || chat.Organization != message.Organization {
|
||||
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
|
||||
return
|
||||
}
|
||||
@ -144,12 +144,18 @@ func (c *ApiController) GetMessageAnswer() {
|
||||
authToken := provider.ClientSecret
|
||||
question := questionMessage.Text
|
||||
var stringBuilder strings.Builder
|
||||
|
||||
fmt.Printf("Question: [%s]\n", questionMessage.Text)
|
||||
fmt.Printf("Answer: [")
|
||||
|
||||
err := ai.QueryAnswerStream(authToken, question, c.Ctx.ResponseWriter, &stringBuilder)
|
||||
if err != nil {
|
||||
c.ResponseErrorStream(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("]\n")
|
||||
|
||||
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
|
||||
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
|
||||
if err != nil {
|
||||
@ -158,9 +164,6 @@ func (c *ApiController) GetMessageAnswer() {
|
||||
|
||||
answer := stringBuilder.String()
|
||||
|
||||
fmt.Printf("Question: [%s]\n", questionMessage.Text)
|
||||
fmt.Printf("Answer: [%s]\n", answer)
|
||||
|
||||
message.Text = answer
|
||||
object.UpdateMessage(message.GetId(), message)
|
||||
}
|
||||
@ -202,10 +205,18 @@ func (c *ApiController) AddMessage() {
|
||||
return
|
||||
}
|
||||
|
||||
var chat *object.Chat
|
||||
if message.Chat != "" {
|
||||
chatId := util.GetId("admin", message.Chat)
|
||||
chat = object.GetChat(chatId)
|
||||
if chat == nil || chat.Organization != message.Organization {
|
||||
c.ResponseError(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
affected := object.AddMessage(&message)
|
||||
if affected {
|
||||
chatId := util.GetId(message.Owner, message.Chat)
|
||||
chat := object.GetChat(chatId)
|
||||
if chat != nil && chat.Type == "AI" {
|
||||
answerMessage := &object.Message{
|
||||
Owner: message.Owner,
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -235,10 +236,21 @@ func (c *ApiController) UploadResource() {
|
||||
user.Avatar = fileUrl
|
||||
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||
case "termsOfUse":
|
||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||
app := object.GetApplication(applicationId)
|
||||
app.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, app)
|
||||
user := object.GetUserNoCheck(util.GetId(owner, username))
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsAdminUser() {
|
||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||
return
|
||||
}
|
||||
|
||||
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimRight(fullFilePath, ".html"))
|
||||
applicationObj := object.GetApplication(applicationId)
|
||||
applicationObj.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, applicationObj)
|
||||
}
|
||||
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
|
@ -59,3 +59,13 @@ func (c *ApiController) GetVersionInfo() {
|
||||
|
||||
c.ResponseOk(versionInfo)
|
||||
}
|
||||
|
||||
// Health
|
||||
// @Title Health
|
||||
// @Tag System API
|
||||
// @Description check if the system is live
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /health [get]
|
||||
func (c *ApiController) Health() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@ -113,6 +113,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
|
||||
for _, attr := range r.Attributes() {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "cn" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
|
38
ldap/util.go
38
ldap/util.go
@ -74,6 +74,15 @@ func getUsername(filter string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func stringInSlice(value string, list []string) bool {
|
||||
for _, item := range list {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
r := m.GetSearchRequest()
|
||||
|
||||
@ -94,13 +103,32 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
} else {
|
||||
hasPermission, err := object.CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), true, "en")
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
userId := util.GetId(org, name)
|
||||
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
user := object.GetUser(util.GetId(org, name))
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user != nil {
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(util.GetId("admin", org))
|
||||
if organization == nil {
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
if !stringInSlice(name, organization.Tags) {
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
users := object.GetUsersByTag(org, name)
|
||||
filteredUsers = append(filteredUsers, users...)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
}
|
||||
@ -130,12 +158,16 @@ func getAttribute(attributeName string, user *object.User) message.AttributeValu
|
||||
return message.AttributeValue(user.Name)
|
||||
case "uid":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "displayname":
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
case "email":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mail":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mobile":
|
||||
return message.AttributeValue(user.Phone)
|
||||
case "title":
|
||||
return message.AttributeValue(user.Tag)
|
||||
case "userPassword":
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
default:
|
||||
|
@ -72,6 +72,7 @@ type Application struct {
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
FormCss string `xorm:"text" json:"formCss"`
|
||||
FormCssMobile string `xorm:"text" json:"formCssMobile"`
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
@ -325,6 +326,12 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
}
|
||||
|
||||
func AddApplication(application *Application) bool {
|
||||
if application.Owner == "" {
|
||||
application.Owner = "admin"
|
||||
}
|
||||
if application.Organization == "" {
|
||||
application.Organization = "built-in"
|
||||
}
|
||||
if application.ClientId == "" {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
}
|
||||
|
@ -175,7 +175,11 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
}
|
||||
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
passwordType := user.PasswordType
|
||||
if passwordType == "" {
|
||||
passwordType = organization.PasswordType
|
||||
}
|
||||
credManager := cred.GetCredManager(passwordType)
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
@ -282,6 +286,10 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
||||
if userId != "" {
|
||||
targetUser := GetUser(userId)
|
||||
if targetUser == nil {
|
||||
if strings.HasPrefix(requestUserId, "built-in/") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), userId)
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ const (
|
||||
const (
|
||||
MfaSessionUserId = "MfaSessionUserId"
|
||||
NextMfa = "NextMfa"
|
||||
RequiredMfa = "RequiredMfa"
|
||||
)
|
||||
|
||||
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
||||
|
@ -26,6 +26,7 @@ func DoMigration() {
|
||||
&Migrator_1_101_0_PR_1083{},
|
||||
&Migrator_1_235_0_PR_1530{},
|
||||
&Migrator_1_240_0_PR_1539{},
|
||||
&Migrator_1_314_0_PR_1841{},
|
||||
// more migrators add here in chronological order...
|
||||
}
|
||||
|
||||
|
93
object/migrator_1_314_0_PR_1841.go
Normal file
93
object/migrator_1_314_0_PR_1841.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/xorm-io/core"
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_314_0_PR_1841 struct{}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
|
||||
users := []*User{}
|
||||
|
||||
err := adapter.Engine.Table("user").Find(&users)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if u.PasswordType != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230515MigrateUser--Create a new field 'passwordType' for table `user`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := []*User{}
|
||||
organizations := []*Organization{}
|
||||
|
||||
err = tx.Table("user").Find(&users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Table("organization").Find(&organizations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordTypes := make(map[string]string)
|
||||
for _, org := range organizations {
|
||||
passwordTypes[org.Name] = org.PasswordType
|
||||
}
|
||||
|
||||
columns := []string{
|
||||
"password_type",
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
u.PasswordType = passwordTypes[u.Owner]
|
||||
|
||||
_, err := tx.ID(core.PK{u.Owner, u.Name}).Cols(columns...).Update(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -38,6 +38,11 @@ type ThemeData struct {
|
||||
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
||||
}
|
||||
|
||||
type MfaItem struct {
|
||||
Name string `json:"name"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -59,6 +64,7 @@ type Organization struct {
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
||||
}
|
||||
|
||||
@ -408,3 +414,12 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (org *Organization) HasRequiredMfa() bool {
|
||||
for _, item := range org.MfaItems {
|
||||
if item.Rule == "Required" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
@ -65,30 +63,6 @@ func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *PermissionRule) GetRequest(adapterName string, permissionId string) ([]interface{}, error) {
|
||||
request := []interface{}{p.V0, p.V1, p.V2}
|
||||
|
||||
if p.V3 != "" {
|
||||
request = append(request, p.V3)
|
||||
}
|
||||
|
||||
if p.V4 != "" {
|
||||
request = append(request, p.V4)
|
||||
}
|
||||
|
||||
if adapterName == builtInAdapter {
|
||||
if p.V5 != "" {
|
||||
return nil, fmt.Errorf("too many parameters. The maximum parameter number cannot exceed %d", builtInAvailableField)
|
||||
}
|
||||
return request, nil
|
||||
} else {
|
||||
if p.V5 != "" {
|
||||
request = append(request, p.V5)
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetPermissionCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Permission{})
|
||||
@ -271,6 +245,16 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
||||
return permissions
|
||||
}
|
||||
|
||||
func GetPermissionsByModel(owner string, model string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Model: model})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func ContainsAsterisk(userId string, users []string) bool {
|
||||
containsAsterisk := false
|
||||
group, _ := util.GetOwnerAndNameFromId(userId)
|
||||
|
@ -62,7 +62,11 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enforcer.InitWithModelAndAdapter(m, nil)
|
||||
err = enforcer.InitWithModelAndAdapter(m, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enforcer.SetAdapter(adapter)
|
||||
|
||||
policyFilter := xormadapter.Filter{
|
||||
@ -216,28 +220,23 @@ func removePolicies(permission *Permission) {
|
||||
}
|
||||
}
|
||||
|
||||
func Enforce(permissionRule *PermissionRule) bool {
|
||||
permission := GetPermission(permissionRule.Id)
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
func Enforce(permissionId string, request *CasbinRequest) bool {
|
||||
permission := GetPermission(permissionId)
|
||||
enforcer := getEnforcer(permission)
|
||||
|
||||
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
|
||||
|
||||
allow, err := enforcer.Enforce(request...)
|
||||
allow, err := enforcer.Enforce(*request...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return allow
|
||||
}
|
||||
|
||||
func BatchEnforce(permissionRules []PermissionRule) []bool {
|
||||
var requests [][]interface{}
|
||||
for _, permissionRule := range permissionRules {
|
||||
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
|
||||
requests = append(requests, request)
|
||||
}
|
||||
permission := GetPermission(permissionRules[0].Id)
|
||||
func BatchEnforce(permissionId string, requests *[]CasbinRequest) []bool {
|
||||
permission := GetPermission(permissionId)
|
||||
enforcer := getEnforcer(permission)
|
||||
allow, err := enforcer.BatchEnforce(requests)
|
||||
allow, err := enforcer.BatchEnforce(*requests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ type User struct {
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||
@ -250,6 +251,16 @@ func GetUsers(owner string) []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
func GetUsersByTag(owner string, tag string) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func GetSortedUsers(owner string, sorter string, limit int) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &User{Owner: owner})
|
||||
|
@ -35,5 +35,6 @@ func (user *User) UpdateUserPassword(organization *Organization) {
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = hashedPassword
|
||||
user.PasswordType = organization.PasswordType
|
||||
}
|
||||
}
|
||||
|
@ -77,13 +77,17 @@ func GetUserByFields(organization string, field string) *User {
|
||||
}
|
||||
|
||||
func SetUserField(user *User, field string, value string) bool {
|
||||
bean := make(map[string]interface{})
|
||||
if field == "password" {
|
||||
organization := GetOrganizationByUser(user)
|
||||
user.UpdateUserPassword(organization)
|
||||
value = user.Password
|
||||
bean[strings.ToLower(field)] = user.Password
|
||||
bean["password_type"] = user.PasswordType
|
||||
} else {
|
||||
bean[strings.ToLower(field)] = value
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(map[string]interface{}{strings.ToLower(field): value})
|
||||
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(bean)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -247,6 +247,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
|
||||
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||
|
||||
beego.Handler("/api/metrics", promhttp.Handler())
|
||||
|
@ -651,7 +651,13 @@ class App extends Component {
|
||||
textAlign: "center",
|
||||
}
|
||||
}>
|
||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
||||
{
|
||||
Conf.CustomFooter !== null ? Conf.CustomFooter : (
|
||||
<React.Fragment>
|
||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</Footer>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -671,6 +671,27 @@ class ApplicationEditPage extends React.Component {
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Form CSS Mobile"), i18next.t("application:Form CSS Mobile - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Popover placement="right" content={
|
||||
<div style={{width: "900px", height: "300px"}} >
|
||||
<CodeMirror value={this.state.application.formCssMobile === "" ? template : this.state.application.formCssMobile}
|
||||
options={{mode: "css", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateApplicationField("formCssMobile", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
} title={i18next.t("application:Form CSS Mobile - Edit")} trigger="click">
|
||||
<Input value={this.state.application.formCssMobile} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("formCssMobile", e.target.value);
|
||||
}} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Form position"), i18next.t("application:Form position - Tooltip"))} :
|
||||
@ -769,7 +790,7 @@ class ApplicationEditPage extends React.Component {
|
||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||
|
||||
let redirectUri;
|
||||
if (this.state.application.redirectUris.length !== 0) {
|
||||
if (this.state.application.redirectUris?.length > 0) {
|
||||
redirectUri = this.state.application.redirectUris[0];
|
||||
} else {
|
||||
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
||||
|
@ -175,7 +175,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
// width: '600px',
|
||||
render: (text, record, index) => {
|
||||
const providers = text;
|
||||
if (providers.length === 0) {
|
||||
if (providers === null || providers.length === 0) {
|
||||
return `(${i18next.t("general:empty")})`;
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ class CertEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.cert.owner} onChange={(value => {this.updateCertField("owner", value);})}>
|
||||
{Setting.isAdminUser(this.props.account) ? <Option key={"admin"} value={"admin"}>{i18next.t("cert:admin (Shared)")}</Option> : null}
|
||||
{Setting.isAdminUser(this.props.account) ? <Option key={"admin"} value={"admin"}>{i18next.t("provider:admin (Shared)")}</Option> : null}
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class ChatPage extends BaseListPage {
|
||||
newChat(chat) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: this.props.account.owner, // this.props.account.applicationName,
|
||||
owner: "admin", // this.props.account.applicationName,
|
||||
name: `chat_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
|
@ -28,3 +28,5 @@ export const ThemeDefault = {
|
||||
borderRadius: 6,
|
||||
isCompact: false,
|
||||
};
|
||||
|
||||
export const CustomFooter = null;
|
||||
|
@ -24,6 +24,7 @@ import {LinkOutlined} from "@ant-design/icons";
|
||||
import LdapTable from "./table/LdapTable";
|
||||
import AccountTable from "./table/AccountTable";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
import MfaTable from "./table/MfaTable";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -316,6 +317,18 @@ class OrganizationEditPage extends React.Component {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<MfaTable
|
||||
title={i18next.t("general:MFA items")}
|
||||
table={this.state.organization.mfaItems ?? []}
|
||||
onUpdateTable={(value) => {this.updateOrganizationField("mfaItems", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
||||
|
@ -325,19 +325,19 @@ export function isSignupItemPrompted(signupItem) {
|
||||
}
|
||||
|
||||
export function getAllPromptedProviderItems(application) {
|
||||
return application.providers.filter(providerItem => isProviderPrompted(providerItem));
|
||||
return application.providers?.filter(providerItem => isProviderPrompted(providerItem));
|
||||
}
|
||||
|
||||
export function getAllPromptedSignupItems(application) {
|
||||
return application.signupItems.filter(signupItem => isSignupItemPrompted(signupItem));
|
||||
return application.signupItems?.filter(signupItem => isSignupItemPrompted(signupItem));
|
||||
}
|
||||
|
||||
export function getSignupItem(application, itemName) {
|
||||
const signupItems = application.signupItems?.filter(signupItem => signupItem.name === itemName);
|
||||
if (signupItems.length === 0) {
|
||||
return null;
|
||||
if (signupItems?.length > 0) {
|
||||
return signupItems[0];
|
||||
}
|
||||
return signupItems[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isValidPersonName(personName) {
|
||||
@ -409,12 +409,12 @@ export function isAffiliationPrompted(application) {
|
||||
|
||||
export function hasPromptPage(application) {
|
||||
const providerItems = getAllPromptedProviderItems(application);
|
||||
if (providerItems.length !== 0) {
|
||||
if (providerItems?.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const signupItems = getAllPromptedSignupItems(application);
|
||||
if (signupItems.length !== 0) {
|
||||
if (signupItems?.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
import {authConfig} from "./Auth";
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getAccount(query) {
|
||||
export function getAccount(query = "") {
|
||||
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
|
@ -33,7 +33,7 @@ import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
import {MfaAuthVerifyForm, NextMfa} from "./MfaAuthVerifyForm";
|
||||
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./MfaAuthVerifyForm";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -224,23 +224,26 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
postCodeLoginAction(res) {
|
||||
postCodeLoginAction(resp) {
|
||||
const application = this.getApplicationObj();
|
||||
const ths = this;
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
const code = res.data;
|
||||
const code = resp.data;
|
||||
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
||||
const noRedirect = oAuthParams.noRedirect;
|
||||
if (Setting.hasPromptPage(application)) {
|
||||
AuthBackend.getAccount("")
|
||||
.then((res) => {
|
||||
let account = null;
|
||||
if (res.status === "ok") {
|
||||
account = res.data;
|
||||
account.organization = res.data2;
|
||||
|
||||
if (Setting.hasPromptPage(application) || resp.msg === RequiredMfa) {
|
||||
AuthBackend.getAccount()
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const account = res.data;
|
||||
account.organization = res.data2;
|
||||
this.onUpdateAccount(account);
|
||||
|
||||
if (resp.msg === RequiredMfa) {
|
||||
Setting.goToLink(`/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}&promptType=mfa`);
|
||||
}
|
||||
|
||||
if (Setting.isPromptAnswered(account, application)) {
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
} else {
|
||||
@ -328,10 +331,20 @@ class LoginPage extends React.Component {
|
||||
const responseType = values["type"];
|
||||
|
||||
if (responseType === "login") {
|
||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||
|
||||
const link = Setting.getFromLink();
|
||||
Setting.goToLink(link);
|
||||
if (res.msg === RequiredMfa) {
|
||||
AuthBackend.getAccount().then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const account = res.data;
|
||||
account.organization = res.data2;
|
||||
this.onUpdateAccount(account);
|
||||
}
|
||||
});
|
||||
Setting.goToLink(`/prompt/${this.getApplicationObj().name}?promptType=mfa`);
|
||||
} else {
|
||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||
const link = Setting.getFromLink();
|
||||
Setting.goToLink(link);
|
||||
}
|
||||
} else if (responseType === "code") {
|
||||
this.postCodeLoginAction(res);
|
||||
} else if (responseType === "token" || responseType === "id_token") {
|
||||
@ -352,6 +365,7 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (res.status === "ok") {
|
||||
callback(res);
|
||||
} else if (res.status === NextMfa) {
|
||||
@ -559,7 +573,7 @@ class LoginPage extends React.Component {
|
||||
</div>
|
||||
<br />
|
||||
{
|
||||
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||
application.providers?.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
|
||||
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
|
||||
})
|
||||
}
|
||||
@ -818,7 +832,7 @@ class LoginPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||
const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||
return (
|
||||
@ -833,6 +847,7 @@ class LoginPage extends React.Component {
|
||||
<CustomGithubCorner />
|
||||
<div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
{Setting.inIframe() || !Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
|
||||
<div className="login-panel">
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
|
@ -20,6 +20,7 @@ import {SmsMfaType} from "./MfaSetupPage";
|
||||
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
|
||||
|
||||
export const NextMfa = "NextMfa";
|
||||
export const RequiredMfa = "RequiredMfa";
|
||||
|
||||
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
||||
formValues.password = "";
|
||||
|
@ -97,9 +97,9 @@ export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail})
|
||||
});
|
||||
};
|
||||
|
||||
if (mfaProps.type === SmsMfaType) {
|
||||
if (mfaProps?.type === SmsMfaType) {
|
||||
return <MfaSmsVerifyForm onFinish={onFinish} application={application} />;
|
||||
} else if (mfaProps.type === TotpMfaType) {
|
||||
} else if (mfaProps?.type === TotpMfaType) {
|
||||
return <MfaTotpVerifyForm onFinish={onFinish} mfaProps={mfaProps} />;
|
||||
} else {
|
||||
return <div></div>;
|
||||
@ -145,7 +145,11 @@ class MfaSetupPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
account: props.account,
|
||||
current: 0,
|
||||
applicationName: (props.applicationName ?? props.account?.signupApplication) ?? "",
|
||||
isAuthenticated: props.isAuthenticated ?? false,
|
||||
isPromptPage: props.isPromptPage,
|
||||
redirectUri: props.redirectUri,
|
||||
current: props.current ?? 0,
|
||||
type: props.type ?? SmsMfaType,
|
||||
mfaProps: null,
|
||||
};
|
||||
@ -155,8 +159,25 @@ class MfaSetupPage extends React.Component {
|
||||
this.getApplication();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (prevState.isAuthenticated === true && this.state.mfaProps === null) {
|
||||
MfaBackend.MfaSetupInitiate({
|
||||
type: this.state.type,
|
||||
...this.getUser(),
|
||||
}).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
mfaProps: res.data,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
ApplicationBackend.getApplication("admin", this.state.account.signupApplication)
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
if (application !== null) {
|
||||
this.setState({
|
||||
@ -181,18 +202,9 @@ class MfaSetupPage extends React.Component {
|
||||
return <CheckPasswordForm
|
||||
user={this.getUser()}
|
||||
onSuccess={() => {
|
||||
MfaBackend.MfaSetupInitiate({
|
||||
type: this.state.type,
|
||||
...this.getUser(),
|
||||
}).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
current: this.state.current + 1,
|
||||
mfaProps: res.data,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||
}
|
||||
this.setState({
|
||||
current: this.state.current + 1,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
}}
|
||||
onFail={(res) => {
|
||||
@ -200,8 +212,12 @@ class MfaSetupPage extends React.Component {
|
||||
}}
|
||||
/>;
|
||||
case 1:
|
||||
if (!this.state.isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <MfaVerifyForm
|
||||
mfaProps={{...this.state.mfaProps}}
|
||||
mfaProps={this.state.mfaProps}
|
||||
application={this.state.application}
|
||||
user={this.getUser()}
|
||||
onSuccess={() => {
|
||||
@ -214,10 +230,18 @@ class MfaSetupPage extends React.Component {
|
||||
}}
|
||||
/>;
|
||||
case 2:
|
||||
if (!this.state.isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
|
||||
onSuccess={() => {
|
||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||
Setting.goToLinkSoft(this, "/account");
|
||||
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||
Setting.goToLink(this.state.redirectUri);
|
||||
} else {
|
||||
Setting.goToLink("/account");
|
||||
}
|
||||
}}
|
||||
onFail={(res) => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||
@ -265,7 +289,9 @@ class MfaSetupPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||
<div style={{marginTop: "10px", textAlign: "center"}}>{this.renderStep()}</div>
|
||||
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||
{this.renderStep()}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -23,16 +23,19 @@ import AffiliationSelect from "../common/select/AffiliationSelect";
|
||||
import OAuthWidget from "../common/OAuthWidget";
|
||||
import RegionSelect from "../common/select/RegionSelect";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import MfaSetupPage from "./MfaSetupPage";
|
||||
|
||||
class PromptPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
this.state = {
|
||||
classes: props,
|
||||
type: props.type,
|
||||
applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
|
||||
application: null,
|
||||
user: null,
|
||||
promptType: params.get("promptType"),
|
||||
};
|
||||
}
|
||||
|
||||
@ -225,6 +228,26 @@ class PromptPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderPromptProvider(application) {
|
||||
return <>
|
||||
{this.renderContent(application)}
|
||||
<div style={{marginTop: "50px"}}>
|
||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
|
||||
</div>;
|
||||
</>;
|
||||
}
|
||||
|
||||
renderPromptMfa() {
|
||||
return <MfaSetupPage
|
||||
applicationName={this.getApplicationObj().name}
|
||||
account={this.props.account}
|
||||
current={1}
|
||||
isAuthenticated={true}
|
||||
isPromptPage={true}
|
||||
redirectUri={this.getRedirectUrl()}
|
||||
/>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const application = this.getApplicationObj();
|
||||
if (application === null) {
|
||||
@ -259,12 +282,7 @@ class PromptPage extends React.Component {
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
{
|
||||
this.renderContent(application)
|
||||
}
|
||||
<div style={{marginTop: "50px"}}>
|
||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
|
||||
</div>
|
||||
{this.state.promptType !== "mfa" ? this.renderPromptProvider(application) : this.renderPromptMfa(application)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
@ -594,6 +594,7 @@ class SignupPage extends React.Component {
|
||||
<CustomGithubCorner />
|
||||
<div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
{Setting.inIframe() || !Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
|
||||
<div className="login-panel" >
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Bearbeiten",
|
||||
"Form CSS - Tooltip": "CSS-Styling der Anmelde-, Registrierungs- und Passwort-vergessen-Seite (z. B. Hinzufügen von Rahmen und Schatten)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Formposition",
|
||||
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
|
||||
"Grant types": "Grant-Typen",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
"Form CSS - Tooltip": "CSS styling of the signup, signin and forget password forms (e.g. adding borders and shadows)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Form position",
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Formulario CSS",
|
||||
"Form CSS - Edit": "Formulario CSS - Editar",
|
||||
"Form CSS - Tooltip": "Estilo CSS de los formularios de registro, inicio de sesión y olvido de contraseña (por ejemplo, agregar bordes y sombras)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Posición de la Forma",
|
||||
"Form position - Tooltip": "Ubicación de los formularios de registro, inicio de sesión y olvido de contraseña",
|
||||
"Grant types": "Tipos de subvenciones",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Formulaire CSS",
|
||||
"Form CSS - Edit": "Form CSS - Modifier",
|
||||
"Form CSS - Tooltip": "Mise en forme CSS des formulaires d'inscription, de connexion et de récupération de mot de passe (par exemple, en ajoutant des bordures et des ombres)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Position de formulaire",
|
||||
"Form position - Tooltip": "Emplacement des formulaires d'inscription, de connexion et de récupération de mot de passe",
|
||||
"Grant types": "Types de subventions",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Formulir CSS",
|
||||
"Form CSS - Edit": "Formulir CSS - Edit",
|
||||
"Form CSS - Tooltip": "Pengaturan CSS dari formulir pendaftaran, masuk, dan lupa kata sandi (misalnya menambahkan batas dan bayangan)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Posisi formulir",
|
||||
"Form position - Tooltip": "Tempat pendaftaran, masuk, dan lupa kata sandi",
|
||||
"Grant types": "Jenis-jenis hibah",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "フォームCSS",
|
||||
"Form CSS - Edit": "フォームのCSS - 編集",
|
||||
"Form CSS - Tooltip": "サインアップ、サインイン、パスワード忘れのフォームのCSSスタイリング(例:境界線や影の追加)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "フォームのポジション",
|
||||
"Form position - Tooltip": "登録、ログイン、パスワード忘れフォームの位置",
|
||||
"Grant types": "グラント種類",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "CSS 양식",
|
||||
"Form CSS - Edit": "폼 CSS - 편집",
|
||||
"Form CSS - Tooltip": "가입, 로그인 및 비밀번호를 잊어버린 양식의 CSS 스타일링 (예 : 테두리와 그림자 추가)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "양식 위치",
|
||||
"Form position - Tooltip": "가입, 로그인 및 비밀번호 재설정 양식의 위치",
|
||||
"Grant types": "Grant types: 부여 유형",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Форма CSS",
|
||||
"Form CSS - Edit": "Форма CSS - Редактирование",
|
||||
"Form CSS - Tooltip": "CSS-оформление форм регистрации, входа и восстановления пароля (например, добавление границ и теней)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Позиция формы",
|
||||
"Form position - Tooltip": "Местоположение форм регистрации, входа и восстановления пароля",
|
||||
"Grant types": "Типы грантов",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "Mẫu CSS",
|
||||
"Form CSS - Edit": "Biểu mẫu CSS - Chỉnh sửa",
|
||||
"Form CSS - Tooltip": "Phong cách CSS của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu (ví dụ: thêm đường viền và bóng)",
|
||||
"Form CSS Mobile": "Form CSS Mobile",
|
||||
"Form CSS Mobile - Edit": "Form CSS Mobile - Edit",
|
||||
"Form CSS Mobile - Tooltip": "Form CSS Mobile - Tooltip",
|
||||
"Form position": "Vị trí của hình thức",
|
||||
"Form position - Tooltip": "Vị trí của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu",
|
||||
"Grant types": "Loại hỗ trợ",
|
||||
|
@ -48,6 +48,9 @@
|
||||
"Form CSS": "表单CSS",
|
||||
"Form CSS - Edit": "编辑表单CSS",
|
||||
"Form CSS - Tooltip": "注册、登录、忘记密码等表单的CSS样式(如增加边框和阴影)",
|
||||
"Form CSS Mobile": "表单CSS(移动端)",
|
||||
"Form CSS Mobile - Edit": "编辑表单CSS(移动端)",
|
||||
"Form CSS Mobile - Tooltip": "注册、登录、忘记密码等表单的CSS样式(如增加边框和阴影)(移动端)",
|
||||
"Form position": "表单位置",
|
||||
"Form position - Tooltip": "注册、登录、忘记密码等表单的位置",
|
||||
"Grant types": "OAuth授权类型",
|
||||
|
160
web/src/table/MfaTable.js
Normal file
160
web/src/table/MfaTable.js
Normal file
@ -0,0 +1,160 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Row, Select, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
const MfaItems = [
|
||||
{name: "Phone"},
|
||||
{name: "Email"},
|
||||
];
|
||||
|
||||
class MfaTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
};
|
||||
}
|
||||
|
||||
updateTable(table) {
|
||||
this.props.onUpdateTable(table);
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a MFA method"), rule: "Optional"};
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, row);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
upRow(table, i) {
|
||||
table = Setting.swapRow(table, i - 1, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
downRow(table, i) {
|
||||
table = Setting.swapRow(table, i, i + 1);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "name", value);
|
||||
}} >
|
||||
{
|
||||
Setting.getDeduplicatedArray(MfaItems, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("application:Rule"),
|
||||
dataIndex: "rule",
|
||||
key: "rule",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
defaultValue="Optional"
|
||||
options={[
|
||||
{value: "Optional", label: i18next.t("organization:Optional")},
|
||||
{value: "Required", label: i18next.t("organization:Required")}].map((item) =>
|
||||
Setting.getOption(item.label, item.value))
|
||||
}
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "rule", value);
|
||||
}} >
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: "action",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table scroll={{x: "max-content"}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.props.table)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MfaTable;
|
@ -60,6 +60,10 @@ class UrlTable extends React.Component {
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
if (table === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("application:Redirect URL"),
|
||||
|
Reference in New Issue
Block a user