diff --git a/controllers/model.go b/controllers/model.go new file mode 100644 index 00000000..8d0601ef --- /dev/null +++ b/controllers/model.go @@ -0,0 +1,120 @@ +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "encoding/json" + + "github.com/astaxie/beego/utils/pagination" + "github.com/casdoor/casdoor/object" + "github.com/casdoor/casdoor/util" +) + +// GetModels +// @Title GetModels +// @Tag Model API +// @Description get models +// @Param owner query string true "The owner of models" +// @Success 200 {array} object.Model The Response object +// @router /get-models [get] +func (c *ApiController) GetModels() { + owner := c.Input().Get("owner") + limit := c.Input().Get("pageSize") + page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") + if limit == "" || page == "" { + c.Data["json"] = object.GetModels(owner) + c.ServeJSON() + } else { + limit := util.ParseInt(limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value))) + models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) + c.ResponseOk(models, paginator.Nums()) + } +} + +// GetModel +// @Title GetModel +// @Tag Model API +// @Description get model +// @Param id query string true "The id of the model" +// @Success 200 {object} object.Model The Response object +// @router /get-model [get] +func (c *ApiController) GetModel() { + id := c.Input().Get("id") + + c.Data["json"] = object.GetModel(id) + c.ServeJSON() +} + +// UpdateModel +// @Title UpdateModel +// @Tag Model API +// @Description update model +// @Param id query string true "The id of the model" +// @Param body body object.Model true "The details of the model" +// @Success 200 {object} controllers.Response The Response object +// @router /update-model [post] +func (c *ApiController) UpdateModel() { + id := c.Input().Get("id") + + var model object.Model + err := json.Unmarshal(c.Ctx.Input.RequestBody, &model) + if err != nil { + panic(err) + } + + c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model)) + c.ServeJSON() +} + +// AddModel +// @Title AddModel +// @Tag Model API +// @Description add model +// @Param body body object.Model true "The details of the model" +// @Success 200 {object} controllers.Response The Response object +// @router /add-model [post] +func (c *ApiController) AddModel() { + var model object.Model + err := json.Unmarshal(c.Ctx.Input.RequestBody, &model) + if err != nil { + panic(err) + } + + c.Data["json"] = wrapActionResponse(object.AddModel(&model)) + c.ServeJSON() +} + +// DeleteModel +// @Title DeleteModel +// @Tag Model API +// @Description delete model +// @Param body body object.Model true "The details of the model" +// @Success 200 {object} controllers.Response The Response object +// @router /delete-model [post] +func (c *ApiController) DeleteModel() { + var model object.Model + err := json.Unmarshal(c.Ctx.Input.RequestBody, &model) + if err != nil { + panic(err) + } + + c.Data["json"] = wrapActionResponse(object.DeleteModel(&model)) + c.ServeJSON() +} diff --git a/object/adapter.go b/object/adapter.go index 4308ccb4..7b670618 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -138,6 +138,11 @@ func (a *Adapter) createTable() { panic(err) } + err = a.Engine.Sync2(new(Model)) + if err != nil { + panic(err) + } + err = a.Engine.Sync2(new(Provider)) if err != nil { panic(err) diff --git a/object/model.go b/object/model.go new file mode 100644 index 00000000..1a8a9c81 --- /dev/null +++ b/object/model.go @@ -0,0 +1,122 @@ +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +import ( + "fmt" + + "github.com/casdoor/casdoor/util" + "xorm.io/core" +) + +type Model struct { + Owner string `xorm:"varchar(100) notnull pk" json:"owner"` + Name string `xorm:"varchar(100) notnull pk" json:"name"` + CreatedTime string `xorm:"varchar(100)" json:"createdTime"` + DisplayName string `xorm:"varchar(100)" json:"displayName"` + + ModelText string `xorm:"mediumtext" json:"modelText"` + IsEnabled bool `json:"isEnabled"` +} + +func GetModelCount(owner, field, value string) int { + session := GetSession(owner, -1, -1, field, value, "", "") + count, err := session.Count(&Model{}) + if err != nil { + panic(err) + } + + return int(count) +} + +func GetModels(owner string) []*Model { + models := []*Model{} + err := adapter.Engine.Desc("created_time").Find(&models, &Model{Owner: owner}) + if err != nil { + panic(err) + } + + return models +} + +func GetPaginationModels(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Model { + models := []*Model{} + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&models) + if err != nil { + panic(err) + } + + return models +} + +func getModel(owner string, name string) *Model { + if owner == "" || name == "" { + return nil + } + + model := Model{Owner: owner, Name: name} + existed, err := adapter.Engine.Get(&model) + if err != nil { + panic(err) + } + + if existed { + return &model + } else { + return nil + } +} + +func GetModel(id string) *Model { + owner, name := util.GetOwnerAndNameFromId(id) + return getModel(owner, name) +} + +func UpdateModel(id string, model *Model) bool { + owner, name := util.GetOwnerAndNameFromId(id) + if getModel(owner, name) == nil { + return false + } + + affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(model) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func AddModel(model *Model) bool { + affected, err := adapter.Engine.Insert(model) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func DeleteModel(model *Model) bool { + affected, err := adapter.Engine.ID(core.PK{model.Owner, model.Name}).Delete(&Model{}) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func (model *Model) GetId() string { + return fmt.Sprintf("%s/%s", model.Owner, model.Name) +} diff --git a/object/permission.go b/object/permission.go index eda5b7c2..e60c2483 100644 --- a/object/permission.go +++ b/object/permission.go @@ -30,6 +30,7 @@ type Permission struct { Users []string `xorm:"mediumtext" json:"users"` Roles []string `xorm:"mediumtext" json:"roles"` + 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"` diff --git a/routers/router.go b/routers/router.go index 5b49fdbb..e563fe6a 100644 --- a/routers/router.go +++ b/routers/router.go @@ -84,6 +84,12 @@ func initAPI() { beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission") beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission") + beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels") + beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel") + beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel") + beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel") + beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel") + beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword") beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword") beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone") diff --git a/web/src/App.js b/web/src/App.js index 5f45b625..1d8df7d6 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -69,6 +69,8 @@ import PromptPage from "./auth/PromptPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import SamlCallback from './auth/SamlCallback'; import CasLogout from "./auth/CasLogout"; +import ModelListPage from "./ModelListPage"; +import ModelEditPage from "./ModelEditPage"; const { Header, Footer } = Layout; @@ -118,6 +120,8 @@ class App extends Component { this.setState({ selectedMenuKey: '/roles' }); } else if (uri.includes('/permissions')) { this.setState({ selectedMenuKey: '/permissions' }); + } else if (uri.includes('/models')) { + this.setState({ selectedMenuKey: '/models' }); } else if (uri.includes('/providers')) { this.setState({ selectedMenuKey: '/providers' }); } else if (uri.includes('/applications')) { @@ -382,6 +386,13 @@ class App extends Component { ); + res.push( + + + {i18next.t("general:Models")} + + + ); res.push( @@ -514,6 +525,8 @@ class App extends Component { this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> + this.renderLoginIfNotLoggedIn()}/> + this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> diff --git a/web/src/ModelEditPage.js b/web/src/ModelEditPage.js new file mode 100644 index 00000000..b1faef63 --- /dev/null +++ b/web/src/ModelEditPage.js @@ -0,0 +1,208 @@ +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; +import * as ModelBackend from "./backend/ModelBackend"; +import * as OrganizationBackend from "./backend/OrganizationBackend"; +import * as Setting from "./Setting"; +import i18next from "i18next"; +import TextArea from "antd/es/input/TextArea"; + +const { Option } = Select; + +class ModelEditPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName, + modelName: props.match.params.modelName, + model: null, + organizations: [], + users: [], + models: [], + mode: props.location.mode !== undefined ? props.location.mode : "edit", + }; + } + + UNSAFE_componentWillMount() { + this.getModel(); + this.getOrganizations(); + } + + getModel() { + ModelBackend.getModel(this.state.organizationName, this.state.modelName) + .then((model) => { + this.setState({ + model: model, + }); + + this.getModels(model.owner); + }); + } + + getOrganizations() { + OrganizationBackend.getOrganizations("admin") + .then((res) => { + this.setState({ + organizations: (res.msg === undefined) ? res : [], + }); + }); + } + + getModels(organizationName) { + ModelBackend.getModels(organizationName) + .then((res) => { + this.setState({ + models: res, + }); + }); + } + + parseModelField(key, value) { + if ([""].includes(key)) { + value = Setting.myParseInt(value); + } + return value; + } + + updateModelField(key, value) { + value = this.parseModelField(key, value); + + let model = this.state.model; + model[key] = value; + this.setState({ + model: model, + }); + } + + renderModel() { + return ( + + {this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}     + + + {this.state.mode === "add" ? : null} + + } style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> + + + {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : + + + + + + + + {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : + + + { + this.updateModelField('name', e.target.value); + }} /> + + + + + {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} : + + + { + this.updateModelField('displayName', e.target.value); + }} /> + + + + + {Setting.getLabel(i18next.t("model:Model"), i18next.t("model:Model - Tooltip"))} : + + +