补充鉴权逻辑
This commit is contained in:
parent
9eb977cfbf
commit
298cadf2e2
5
devcloud/mcenter/apps/namespace/const.go
Normal file
5
devcloud/mcenter/apps/namespace/const.go
Normal file
@ -0,0 +1,5 @@
|
||||
package namespace
|
||||
|
||||
const (
|
||||
DEFAULT_NS_NAME = "default"
|
||||
)
|
31
devcloud/mcenter/apps/namespace/impl/impl.go
Normal file
31
devcloud/mcenter/apps/namespace/impl/impl.go
Normal file
@ -0,0 +1,31 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ioc.Controller().Registry(&NameSpaceServiceImpl{})
|
||||
}
|
||||
|
||||
var _ namespace.Service = (*NameSpaceServiceImpl)(nil)
|
||||
|
||||
type NameSpaceServiceImpl struct {
|
||||
ioc.ObjectImpl
|
||||
}
|
||||
|
||||
func (i *NameSpaceServiceImpl) Init() error {
|
||||
if datasource.Get().AutoMigrate {
|
||||
err := datasource.DB().AutoMigrate(&namespace.Namespace{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *NameSpaceServiceImpl) Name() string {
|
||||
return namespace.AppName
|
||||
}
|
18
devcloud/mcenter/apps/namespace/impl/impl_test.go
Normal file
18
devcloud/mcenter/apps/namespace/impl/impl_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package impl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
|
||||
)
|
||||
|
||||
var (
|
||||
impl namespace.Service
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
func init() {
|
||||
test.DevelopmentSetUp()
|
||||
impl = namespace.GetService()
|
||||
}
|
93
devcloud/mcenter/apps/namespace/impl/namespace.go
Normal file
93
devcloud/mcenter/apps/namespace/impl/namespace.go
Normal file
@ -0,0 +1,93 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"github.com/infraboard/mcube/v2/exception"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||
"github.com/infraboard/mcube/v2/types"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 创建空间
|
||||
func (i *NameSpaceServiceImpl) CreateNamespace(ctx context.Context, in *namespace.CreateNamespaceRequest) (*namespace.Namespace, error) {
|
||||
if err := in.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ins := namespace.NewNamespace()
|
||||
ins.CreateNamespaceRequest = *in
|
||||
|
||||
if err := datasource.DBFromCtx(ctx).
|
||||
Create(ins).
|
||||
Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
// 查询空间
|
||||
func (i *NameSpaceServiceImpl) QueryNamespace(ctx context.Context, in *namespace.QueryNamespaceRequest) (*types.Set[*namespace.Namespace], error) {
|
||||
set := types.New[*namespace.Namespace]()
|
||||
|
||||
query := datasource.DBFromCtx(ctx).Model(&namespace.Namespace{})
|
||||
err := query.Count(&set.Total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = query.
|
||||
Order("created_at desc").
|
||||
Offset(int(in.ComputeOffset())).
|
||||
Limit(int(in.PageSize)).
|
||||
Find(&set.Items).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// 查询空间详情
|
||||
func (i *NameSpaceServiceImpl) DescribeNamespace(ctx context.Context, in *namespace.DescribeNamespaceRequest) (*namespace.Namespace, error) {
|
||||
query := datasource.DBFromCtx(ctx)
|
||||
|
||||
ins := &namespace.Namespace{}
|
||||
if err := query.Where("id = ?", in.Id).First(ins).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, exception.NewNotFound("namespace %d not found", in.Id)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
// 更新空间
|
||||
func (i *NameSpaceServiceImpl) UpdateNamespace(ctx context.Context, in *namespace.UpdateNamespaceRequest) (*namespace.Namespace, error) {
|
||||
descReq := namespace.NewDescribeNamespaceRequest()
|
||||
descReq.SetId(in.Id)
|
||||
ins, err := i.DescribeNamespace(ctx, descReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ins.CreateNamespaceRequest = in.CreateNamespaceRequest
|
||||
return ins, datasource.DBFromCtx(ctx).Where("id = ?", in.Id).Updates(ins).Error
|
||||
}
|
||||
|
||||
// 删除空间
|
||||
func (i *NameSpaceServiceImpl) DeleteNamespace(ctx context.Context, in *namespace.DeleteNamespaceRequest) (*namespace.Namespace, error) {
|
||||
descReq := namespace.NewDescribeNamespaceRequest()
|
||||
descReq.SetId(in.Id)
|
||||
ins, err := i.DescribeNamespace(ctx, descReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ins, datasource.DBFromCtx(ctx).
|
||||
Where("id = ?", in.Id).
|
||||
Delete(&namespace.Namespace{}).
|
||||
Error
|
||||
}
|
28
devcloud/mcenter/apps/namespace/impl/namespace_test.go
Normal file
28
devcloud/mcenter/apps/namespace/impl/namespace_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package impl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
)
|
||||
|
||||
func TestQueryNamespace(t *testing.T) {
|
||||
req := namespace.NewQueryNamespaceRequest()
|
||||
set, err := impl.QueryNamespace(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
||||
|
||||
func TestCreateNamespace(t *testing.T) {
|
||||
req := namespace.NewCreateNamespaceRequest()
|
||||
req.Name = namespace.DEFAULT_NS_NAME
|
||||
req.Description = "默认空间"
|
||||
req.OwnerUserId = 1
|
||||
set, err := impl.CreateNamespace(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
90
devcloud/mcenter/apps/namespace/interface.go
Normal file
90
devcloud/mcenter/apps/namespace/interface.go
Normal file
@ -0,0 +1,90 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/infraboard/mcube/v2/http/request"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/types"
|
||||
"github.com/infraboard/modules/iam/apps"
|
||||
)
|
||||
|
||||
const (
|
||||
AppName = "namespace"
|
||||
)
|
||||
|
||||
func GetService() Service {
|
||||
return ioc.Controller().Get(AppName).(Service)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
// 创建空间
|
||||
CreateNamespace(context.Context, *CreateNamespaceRequest) (*Namespace, error)
|
||||
// 查询空间
|
||||
QueryNamespace(context.Context, *QueryNamespaceRequest) (*types.Set[*Namespace], error)
|
||||
// 查询空间详情
|
||||
DescribeNamespace(context.Context, *DescribeNamespaceRequest) (*Namespace, error)
|
||||
// 更新空间
|
||||
UpdateNamespace(context.Context, *UpdateNamespaceRequest) (*Namespace, error)
|
||||
// 删除空间
|
||||
DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*Namespace, error)
|
||||
}
|
||||
|
||||
func NewQueryNamespaceRequest() *QueryNamespaceRequest {
|
||||
return &QueryNamespaceRequest{
|
||||
PageRequest: *request.NewDefaultPageRequest(),
|
||||
NamespaceIds: []uint64{},
|
||||
}
|
||||
}
|
||||
|
||||
type QueryNamespaceRequest struct {
|
||||
request.PageRequest
|
||||
NamespaceIds []uint64 `json:"namespace_ids"`
|
||||
}
|
||||
|
||||
func (r *QueryNamespaceRequest) AddNamespaceIds(ids ...uint64) {
|
||||
for _, id := range ids {
|
||||
if !r.HasNamespaceIds(id) {
|
||||
r.NamespaceIds = append(r.NamespaceIds, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *QueryNamespaceRequest) HasNamespaceIds(namespaceId uint64) bool {
|
||||
for i := range r.NamespaceIds {
|
||||
if r.NamespaceIds[i] == namespaceId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewDescribeNamespaceRequest() *DescribeNamespaceRequest {
|
||||
return &DescribeNamespaceRequest{}
|
||||
}
|
||||
|
||||
type DescribeNamespaceRequest struct {
|
||||
apps.GetRequest
|
||||
}
|
||||
|
||||
func (r *DescribeNamespaceRequest) SetNamespaceId(id uint64) *DescribeNamespaceRequest {
|
||||
r.Id = id
|
||||
return r
|
||||
}
|
||||
|
||||
func NewUpdateNamespaceRequest() *UpdateNamespaceRequest {
|
||||
return &UpdateNamespaceRequest{}
|
||||
}
|
||||
|
||||
type UpdateNamespaceRequest struct {
|
||||
apps.GetRequest
|
||||
CreateNamespaceRequest
|
||||
}
|
||||
|
||||
func NewDeleteNamespaceRequest() *DeleteNamespaceRequest {
|
||||
return &DeleteNamespaceRequest{}
|
||||
}
|
||||
|
||||
type DeleteNamespaceRequest struct {
|
||||
apps.GetRequest
|
||||
}
|
62
devcloud/mcenter/apps/namespace/model.go
Normal file
62
devcloud/mcenter/apps/namespace/model.go
Normal file
@ -0,0 +1,62 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||
"github.com/infraboard/modules/iam/apps"
|
||||
)
|
||||
|
||||
func NewNamespace() *Namespace {
|
||||
return &Namespace{
|
||||
ResourceMeta: *apps.NewResourceMeta(),
|
||||
}
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
// 基础数据
|
||||
apps.ResourceMeta
|
||||
// 空间属性
|
||||
CreateNamespaceRequest
|
||||
}
|
||||
|
||||
func (n *Namespace) IsOwner(ownerUserId uint64) bool {
|
||||
return n.OwnerUserId == ownerUserId
|
||||
}
|
||||
|
||||
func (n *Namespace) TableName() string {
|
||||
return "namespaces"
|
||||
}
|
||||
|
||||
func (n *Namespace) String() string {
|
||||
return pretty.ToJSON(n)
|
||||
}
|
||||
|
||||
func NewCreateNamespaceRequest() *CreateNamespaceRequest {
|
||||
return &CreateNamespaceRequest{
|
||||
Extras: map[string]string{},
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateNamespaceRequest struct {
|
||||
// 父Namespace Id
|
||||
ParentId uint64 `json:"parent_id" bson:"parent_id" gorm:"column:parent_id;type:uint;index" description:"父Namespace Id"`
|
||||
// 全局唯一
|
||||
Name string `json:"name" bson:"name" validate:"required" gorm:"column:name;type:varchar(200);not null;uniqueIndex" description:"空间名称" unique:"true"`
|
||||
// 空间负责人
|
||||
OwnerUserId uint64 `json:"owner_user_id" bson:"owner_user_id" gorm:"column:owner_user_id;type:uint;index;not null" description:" 空间负责人Id"`
|
||||
// 禁用项目, 该项目所有人暂时都无法访问
|
||||
Enabled bool `json:"enabled" bson:"enabled" gorm:"column:enabled;type:tinyint(1)" description:"是否启用"`
|
||||
// 空间描述图片
|
||||
Icon string `json:"icon" bson:"icon" gorm:"column:icon;type:varchar(200)" description:"空间图标"`
|
||||
// 空间描述
|
||||
Description string `json:"description" bson:"description" gorm:"column:description;type:text" description:"空间描述"`
|
||||
// 标签
|
||||
Label string `json:"label" gorm:"column:label;type:varchar(200);index" description:"标签"`
|
||||
// 扩展信息
|
||||
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" description:"扩展信息"`
|
||||
}
|
||||
|
||||
func (r *CreateNamespaceRequest) Validate() error {
|
||||
return validator.Validate(r)
|
||||
}
|
38
devcloud/mcenter/apps/policy/impl/impl.go
Normal file
38
devcloud/mcenter/apps/policy/impl/impl.go
Normal file
@ -0,0 +1,38 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ioc.Controller().Registry(&PolicyServiceImpl{})
|
||||
}
|
||||
|
||||
var _ policy.Service = (*PolicyServiceImpl)(nil)
|
||||
|
||||
type PolicyServiceImpl struct {
|
||||
ioc.ObjectImpl
|
||||
|
||||
namespace namespace.Service
|
||||
role role.Service
|
||||
}
|
||||
|
||||
func (i *PolicyServiceImpl) Init() error {
|
||||
if datasource.Get().AutoMigrate {
|
||||
err := datasource.DB().AutoMigrate(&policy.Policy{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
i.namespace = namespace.GetService()
|
||||
i.role = role.GetService()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *PolicyServiceImpl) Name() string {
|
||||
return policy.AppName
|
||||
}
|
18
devcloud/mcenter/apps/policy/impl/impl_test.go
Normal file
18
devcloud/mcenter/apps/policy/impl/impl_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package impl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
|
||||
)
|
||||
|
||||
var (
|
||||
impl policy.Service
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
func init() {
|
||||
test.DevelopmentSetUp()
|
||||
impl = policy.GetService()
|
||||
}
|
107
devcloud/mcenter/apps/policy/impl/permission.go
Normal file
107
devcloud/mcenter/apps/policy/impl/permission.go
Normal file
@ -0,0 +1,107 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/view"
|
||||
"github.com/infraboard/mcube/v2/types"
|
||||
)
|
||||
|
||||
// 查询用户可以访问的空间
|
||||
func (i *PolicyServiceImpl) QueryNamespace(ctx context.Context, in *policy.QueryNamespaceRequest) (*types.Set[*namespace.Namespace], error) {
|
||||
nsReq := namespace.NewQueryNamespaceRequest()
|
||||
|
||||
policies, err := i.QueryPolicy(ctx,
|
||||
policy.NewQueryPolicyRequest().
|
||||
SetSkipPage(true).
|
||||
SetUserId(in.UserId).
|
||||
SetExpired(false).
|
||||
SetEnabled(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policies.ForEach(func(t *policy.Policy) {
|
||||
if t.NamespaceId != nil {
|
||||
nsReq.AddNamespaceIds(*t.NamespaceId)
|
||||
}
|
||||
})
|
||||
|
||||
return i.namespace.QueryNamespace(ctx, nsReq)
|
||||
}
|
||||
|
||||
// 查询用户可以访问的Api接口
|
||||
// 找到用户可以访问的角色列表,然后在找出角色对应的Api访问权限
|
||||
func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryEndpointRequest) (*types.Set[*endpoint.Endpoint], error) {
|
||||
set := types.New[*endpoint.Endpoint]()
|
||||
policies, err := i.QueryPolicy(ctx,
|
||||
policy.NewQueryPolicyRequest().
|
||||
SetSkipPage(true).
|
||||
SetNamespaceId(in.NamespaceId).
|
||||
SetUserId(in.UserId).
|
||||
SetExpired(false).
|
||||
SetEnabled(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleReq := role.NewQueryMatchedEndpointRequest()
|
||||
policies.ForEach(func(t *policy.Policy) {
|
||||
roleReq.Add(t.RoleId)
|
||||
})
|
||||
|
||||
if policies.Len() > 0 {
|
||||
set, err = role.GetService().QueryMatchedEndpoint(ctx, roleReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// 校验Api接口权限
|
||||
func (i *PolicyServiceImpl) ValidateEndpointPermission(ctx context.Context, in *policy.ValidateEndpointPermissionRequest) (*policy.ValidateEndpointPermissionResponse, error) {
|
||||
resp := policy.NewValidateEndpointPermissionResponse(*in)
|
||||
|
||||
// 空间Owner有所有权限
|
||||
ns, err := namespace.GetService().DescribeNamespace(ctx, namespace.NewDescribeNamespaceRequest().SetNamespaceId(in.NamespaceId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ns.IsOwner(in.UserId) {
|
||||
resp.HasPermission = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// 非空间管理员需要独立鉴权, 查询用户可以访问的API列表
|
||||
endpointReq := policy.NewQueryEndpointRequest()
|
||||
endpointReq.UserId = in.UserId
|
||||
endpointReq.NamespaceId = in.NamespaceId
|
||||
endpointSet, err := i.QueryEndpoint(ctx, endpointReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range endpointSet.Items {
|
||||
if item.IsMatched(in.Service, in.Method, in.Path) {
|
||||
resp.HasPermission = true
|
||||
resp.Endpoint = item
|
||||
break
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// 查询用户可以访问的菜单
|
||||
func (i *PolicyServiceImpl) QueryMenu(ctx context.Context, in *policy.QueryMenuRequest) (*types.Set[*view.Menu], error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 校验Menu视图权限
|
||||
func (i *PolicyServiceImpl) ValidatePagePermission(ctx context.Context, in *policy.ValidatePagePermissionRequest) (*policy.ValidatePagePermissionResponse, error) {
|
||||
return nil, nil
|
||||
}
|
42
devcloud/mcenter/apps/policy/impl/permission_test.go
Normal file
42
devcloud/mcenter/apps/policy/impl/permission_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
package impl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
)
|
||||
|
||||
func TestQueryNamespace(t *testing.T) {
|
||||
req := policy.NewQueryNamespaceRequest()
|
||||
req.UserId = 1
|
||||
set, err := impl.QueryNamespace(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
||||
|
||||
func TestQueryEndpoint(t *testing.T) {
|
||||
req := policy.NewQueryEndpointRequest()
|
||||
req.UserId = 1
|
||||
req.NamespaceId = 1
|
||||
set, err := impl.QueryEndpoint(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
||||
|
||||
func TestValidateEndpointPermission(t *testing.T) {
|
||||
req := policy.NewValidateEndpointPermissionRequest()
|
||||
req.UserId = 1
|
||||
req.NamespaceId = 1
|
||||
req.Service = "devcloud"
|
||||
req.Method = "GET"
|
||||
req.Path = "/api/devcloud/v1/users/"
|
||||
set, err := impl.ValidateEndpointPermission(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
148
devcloud/mcenter/apps/policy/impl/policy.go
Normal file
148
devcloud/mcenter/apps/policy/impl/policy.go
Normal file
@ -0,0 +1,148 @@
|
||||
package impl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/view"
|
||||
"github.com/infraboard/mcube/v2/exception"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||
"github.com/infraboard/mcube/v2/types"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 创建策略
|
||||
func (i *PolicyServiceImpl) CreatePolicy(ctx context.Context, in *policy.CreatePolicyRequest) (*policy.Policy, error) {
|
||||
if err := in.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ins := policy.NewPolicy()
|
||||
ins.CreatePolicyRequest = *in
|
||||
|
||||
if err := datasource.DBFromCtx(ctx).
|
||||
Create(ins).
|
||||
Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
// 查询策略列表
|
||||
func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPolicyRequest) (*types.Set[*policy.Policy], error) {
|
||||
set := types.New[*policy.Policy]()
|
||||
|
||||
query := datasource.DBFromCtx(ctx).Model(&policy.Policy{}).Order("created_at desc")
|
||||
err := query.Count(&set.Total).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !in.SkipPage {
|
||||
query = query.
|
||||
Offset(int(in.ComputeOffset())).
|
||||
Limit(int(in.PageSize))
|
||||
}
|
||||
|
||||
if err = query.Find(&set.Items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if in.WithUser {
|
||||
userReq := user.NewQueryUserRequest()
|
||||
set.ForEach(func(t *policy.Policy) {
|
||||
userReq.AddUser(t.UserId)
|
||||
})
|
||||
userSet, err := user.GetService().QueryUser(ctx, userReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.ForEach(func(p *policy.Policy) {
|
||||
p.User = userSet.Filter(func(t *user.User) bool {
|
||||
return p.UserId == t.Id
|
||||
}).First()
|
||||
})
|
||||
}
|
||||
if in.WithRole {
|
||||
roleReq := role.NewQueryRoleRequest()
|
||||
set.ForEach(func(t *policy.Policy) {
|
||||
roleReq.AddRoleId(t.RoleId)
|
||||
})
|
||||
roleSet, err := role.GetService().QueryRole(ctx, roleReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.ForEach(func(p *policy.Policy) {
|
||||
p.Role = roleSet.Filter(func(t *role.Role) bool {
|
||||
return p.RoleId == t.Id
|
||||
}).First()
|
||||
})
|
||||
}
|
||||
if in.WithNamespace {
|
||||
nsReq := namespace.NewQueryNamespaceRequest()
|
||||
set.ForEach(func(t *policy.Policy) {
|
||||
if t.NamespaceId != nil {
|
||||
nsReq.AddNamespaceIds(*t.NamespaceId)
|
||||
}
|
||||
})
|
||||
nsSet, err := namespace.GetService().QueryNamespace(ctx, nsReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.ForEach(func(p *policy.Policy) {
|
||||
if p.NamespaceId != nil {
|
||||
p.Namespace = nsSet.Filter(func(t *namespace.Namespace) bool {
|
||||
return *p.NamespaceId == t.Id
|
||||
}).First()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// 查询详情
|
||||
func (i *PolicyServiceImpl) DescribePolicy(ctx context.Context, in *policy.DescribePolicyRequest) (*policy.Policy, error) {
|
||||
query := datasource.DBFromCtx(ctx)
|
||||
|
||||
ins := &policy.Policy{}
|
||||
if err := query.Where("id =?", in.Id).First(ins).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, exception.NewNotFound("policy %d not found", in.Id)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
// 更新策略
|
||||
func (i *PolicyServiceImpl) UpdatePolicy(ctx context.Context, in *policy.UpdatePolicyRequest) (*policy.Policy, error) {
|
||||
descReq := policy.NewDescribePolicyRequest()
|
||||
descReq.SetId(in.Id)
|
||||
ins, err := i.DescribePolicy(ctx, descReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ins.CreatePolicyRequest = in.CreatePolicyRequest
|
||||
return ins, datasource.DBFromCtx(ctx).Where("id = ?", in.Id).Updates(ins).Error
|
||||
}
|
||||
|
||||
// 删除策略
|
||||
func (i *PolicyServiceImpl) DeletePolicy(ctx context.Context, in *policy.DeletePolicyRequest) (*policy.Policy, error) {
|
||||
descReq := policy.NewDescribePolicyRequest()
|
||||
descReq.SetId(in.Id)
|
||||
ins, err := i.DescribePolicy(ctx, descReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ins, datasource.DBFromCtx(ctx).
|
||||
Where("id = ?", in.Id).
|
||||
Delete(&view.Menu{}).
|
||||
Error
|
||||
}
|
31
devcloud/mcenter/apps/policy/impl/policy_test.go
Normal file
31
devcloud/mcenter/apps/policy/impl/policy_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package impl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||
)
|
||||
|
||||
func TestQueryPolicy(t *testing.T) {
|
||||
req := policy.NewQueryPolicyRequest()
|
||||
req.WithUser = true
|
||||
req.WithRole = true
|
||||
req.WithNamespace = true
|
||||
set, err := impl.QueryPolicy(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
||||
|
||||
func TestCreatePolicy(t *testing.T) {
|
||||
req := policy.NewCreatePolicyRequest()
|
||||
req.SetNamespaceId(1)
|
||||
req.UserId = 1
|
||||
req.RoleId = 1
|
||||
set, err := impl.CreatePolicy(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(set)
|
||||
}
|
233
devcloud/mcenter/apps/policy/interface.go
Normal file
233
devcloud/mcenter/apps/policy/interface.go
Normal file
@ -0,0 +1,233 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/view"
|
||||
"github.com/infraboard/mcube/v2/http/request"
|
||||
"github.com/infraboard/mcube/v2/ioc"
|
||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||
"github.com/infraboard/mcube/v2/types"
|
||||
"github.com/infraboard/modules/iam/apps"
|
||||
)
|
||||
|
||||
const (
|
||||
AppName = "policy"
|
||||
)
|
||||
|
||||
func GetService() Service {
|
||||
return ioc.Controller().Get(AppName).(Service)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
// 策略管理
|
||||
PolicyService
|
||||
// 权限查询, 整合用户多个角色的权限合集
|
||||
PermissionService
|
||||
}
|
||||
|
||||
type PolicyService interface {
|
||||
// 创建策略
|
||||
CreatePolicy(context.Context, *CreatePolicyRequest) (*Policy, error)
|
||||
// 查询策略列表
|
||||
QueryPolicy(context.Context, *QueryPolicyRequest) (*types.Set[*Policy], error)
|
||||
// 查询详情
|
||||
DescribePolicy(context.Context, *DescribePolicyRequest) (*Policy, error)
|
||||
// 更新策略
|
||||
UpdatePolicy(context.Context, *UpdatePolicyRequest) (*Policy, error)
|
||||
// 删除策略
|
||||
DeletePolicy(context.Context, *DeletePolicyRequest) (*Policy, error)
|
||||
}
|
||||
|
||||
func NewQueryPolicyRequest() *QueryPolicyRequest {
|
||||
return &QueryPolicyRequest{
|
||||
PageRequest: request.NewDefaultPageRequest(),
|
||||
}
|
||||
}
|
||||
|
||||
type QueryPolicyRequest struct {
|
||||
*request.PageRequest
|
||||
// 忽略分页
|
||||
SkipPage bool `json:"skip_page"`
|
||||
// 关联用户Id
|
||||
UserId *uint64 `json:"user_id"`
|
||||
// 关联空间
|
||||
NamespaceId *uint64 `json:"namespace_id"`
|
||||
// 没有过期
|
||||
Expired *bool `json:"expired"`
|
||||
// 有没有启动
|
||||
Enabled *bool `json:"active"`
|
||||
// 关联查询出空间对象
|
||||
WithNamespace bool `json:"with_namespace"`
|
||||
// 关联查询出用户对象
|
||||
WithUser bool `json:"with_user"`
|
||||
// 关联查询角色对象
|
||||
WithRole bool `json:"with_role"`
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetNamespaceId(nsId uint64) *QueryPolicyRequest {
|
||||
r.NamespaceId = &nsId
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetUserId(uid uint64) *QueryPolicyRequest {
|
||||
r.UserId = &uid
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetExpired(v bool) *QueryPolicyRequest {
|
||||
r.Expired = &v
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetEnabled(v bool) *QueryPolicyRequest {
|
||||
r.Enabled = &v
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetSkipPage(v bool) *QueryPolicyRequest {
|
||||
r.SkipPage = v
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryPolicyRequest) SetWithRole(v bool) *QueryPolicyRequest {
|
||||
r.WithRole = v
|
||||
return r
|
||||
}
|
||||
func (r *QueryPolicyRequest) SetWithUsers(v bool) *QueryPolicyRequest {
|
||||
r.WithUser = v
|
||||
return r
|
||||
}
|
||||
func (r *QueryPolicyRequest) SetWithUser(v bool) *QueryPolicyRequest {
|
||||
r.WithNamespace = v
|
||||
return r
|
||||
}
|
||||
|
||||
func NewDescribePolicyRequest() *DescribePolicyRequest {
|
||||
return &DescribePolicyRequest{}
|
||||
}
|
||||
|
||||
type DescribePolicyRequest struct {
|
||||
apps.GetRequest
|
||||
}
|
||||
|
||||
type UpdatePolicyRequest struct {
|
||||
apps.GetRequest
|
||||
CreatePolicyRequest
|
||||
}
|
||||
|
||||
func NewDeletePolicyRequest() *DeletePolicyRequest {
|
||||
return &DeletePolicyRequest{}
|
||||
}
|
||||
|
||||
type DeletePolicyRequest struct {
|
||||
apps.GetRequest
|
||||
}
|
||||
|
||||
type PermissionService interface {
|
||||
// 查询用户可以访问的空间
|
||||
QueryNamespace(context.Context, *QueryNamespaceRequest) (*types.Set[*namespace.Namespace], error)
|
||||
// 查询用户可以访问的菜单
|
||||
QueryMenu(context.Context, *QueryMenuRequest) (*types.Set[*view.Menu], error)
|
||||
// 查询用户可以访问的Api接口
|
||||
QueryEndpoint(context.Context, *QueryEndpointRequest) (*types.Set[*endpoint.Endpoint], error)
|
||||
// 校验页面权限
|
||||
ValidatePagePermission(context.Context, *ValidatePagePermissionRequest) (*ValidatePagePermissionResponse, error)
|
||||
// 校验接口权限
|
||||
ValidateEndpointPermission(context.Context, *ValidateEndpointPermissionRequest) (*ValidateEndpointPermissionResponse, error)
|
||||
}
|
||||
|
||||
type ValidatePagePermissionRequest struct {
|
||||
UserId uint64 `json:"user_id" form:"user_id"`
|
||||
NamespaceId uint64 `json:"namespace_id" form:"namespace_id"`
|
||||
Path string `json:"path" form:"path"`
|
||||
}
|
||||
|
||||
func NewValidatePagePermissionResponse(req ValidatePagePermissionRequest) *ValidatePagePermissionResponse {
|
||||
return &ValidatePagePermissionResponse{
|
||||
ValidatePagePermissionRequest: req,
|
||||
}
|
||||
}
|
||||
|
||||
type ValidatePagePermissionResponse struct {
|
||||
ValidatePagePermissionRequest
|
||||
HasPermission bool `json:"has_permission"`
|
||||
Page *view.Page `json:"page"`
|
||||
}
|
||||
|
||||
func NewValidateEndpointPermissionRequest() *ValidateEndpointPermissionRequest {
|
||||
return &ValidateEndpointPermissionRequest{}
|
||||
}
|
||||
|
||||
type ValidateEndpointPermissionRequest struct {
|
||||
UserId uint64 `json:"user_id" form:"user_id"`
|
||||
NamespaceId uint64 `json:"namespace_id" form:"namespace_id"`
|
||||
Service string `json:"service" form:"service"`
|
||||
Path string `json:"path" form:"path"`
|
||||
Method string `json:"method" form:"method"`
|
||||
}
|
||||
|
||||
func NewValidateEndpointPermissionResponse(req ValidateEndpointPermissionRequest) *ValidateEndpointPermissionResponse {
|
||||
return &ValidateEndpointPermissionResponse{
|
||||
ValidateEndpointPermissionRequest: req,
|
||||
}
|
||||
}
|
||||
|
||||
type ValidateEndpointPermissionResponse struct {
|
||||
ValidateEndpointPermissionRequest
|
||||
HasPermission bool `json:"has_permission"`
|
||||
Endpoint *endpoint.Endpoint `json:"endpoint"`
|
||||
}
|
||||
|
||||
func (r *ValidateEndpointPermissionResponse) String() string {
|
||||
return pretty.ToJSON(r)
|
||||
}
|
||||
|
||||
func NewQueryNamespaceRequest() *QueryNamespaceRequest {
|
||||
return &QueryNamespaceRequest{}
|
||||
}
|
||||
|
||||
type QueryNamespaceRequest struct {
|
||||
UserId uint64 `json:"user_id"`
|
||||
NamespaceId uint64 `json:"namespace_id"`
|
||||
}
|
||||
|
||||
func (r *QueryNamespaceRequest) SetUserId(v uint64) *QueryNamespaceRequest {
|
||||
r.UserId = v
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryNamespaceRequest) SetNamespaceId(v uint64) *QueryNamespaceRequest {
|
||||
r.NamespaceId = v
|
||||
return r
|
||||
}
|
||||
|
||||
func NewQueryMenuRequest() *QueryMenuRequest {
|
||||
return &QueryMenuRequest{}
|
||||
}
|
||||
|
||||
type QueryMenuRequest struct {
|
||||
UserId uint64 `json:"user_id"`
|
||||
NamespaceId uint64 `json:"namespace_id"`
|
||||
}
|
||||
|
||||
func NewQueryEndpointRequest() *QueryEndpointRequest {
|
||||
return &QueryEndpointRequest{}
|
||||
}
|
||||
|
||||
type QueryEndpointRequest struct {
|
||||
UserId uint64 `json:"user_id"`
|
||||
NamespaceId uint64 `json:"namespace_id"`
|
||||
}
|
||||
|
||||
func (r *QueryEndpointRequest) SetUserId(v uint64) *QueryEndpointRequest {
|
||||
r.UserId = v
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *QueryEndpointRequest) SetNamespaceId(v uint64) *QueryEndpointRequest {
|
||||
r.NamespaceId = v
|
||||
return r
|
||||
}
|
80
devcloud/mcenter/apps/policy/model.go
Normal file
80
devcloud/mcenter/apps/policy/model.go
Normal file
@ -0,0 +1,80 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
|
||||
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||
"github.com/infraboard/modules/iam/apps"
|
||||
)
|
||||
|
||||
func NewPolicy() *Policy {
|
||||
return &Policy{
|
||||
ResourceMeta: *apps.NewResourceMeta(),
|
||||
}
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
// 基础数据
|
||||
apps.ResourceMeta
|
||||
// 策略定义
|
||||
CreatePolicyRequest
|
||||
// 关联空间
|
||||
Namespace *namespace.Namespace `json:"namespace,omitempty" gorm:"-"`
|
||||
// 关联用户
|
||||
User *user.User `json:"user,omitempty" gorm:"-"`
|
||||
// 关联角色
|
||||
Role *role.Role `json:"role,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (p *Policy) TableName() string {
|
||||
return "policy"
|
||||
}
|
||||
|
||||
func (p *Policy) String() string {
|
||||
return pretty.ToJSON(p)
|
||||
}
|
||||
|
||||
func NewCreatePolicyRequest() *CreatePolicyRequest {
|
||||
return &CreatePolicyRequest{
|
||||
Extras: map[string]string{},
|
||||
Scope: map[string]string{},
|
||||
Enabled: true,
|
||||
ReadOnly: false,
|
||||
}
|
||||
}
|
||||
|
||||
type CreatePolicyRequest struct {
|
||||
// 创建者
|
||||
CreateBy uint64 `json:"create_by" bson:"create_by" gorm:"column:create_by;type:uint" description:"创建者" optional:"true"`
|
||||
// 空间
|
||||
NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"`
|
||||
// 用户Id
|
||||
UserId uint64 `json:"user_id" bson:"user_id" gorm:"column:user_id;type:uint;not null;index" validate:"required" description:"被授权的用户"`
|
||||
// 角色Id
|
||||
RoleId uint64 `json:"role_id" bson:"role_id" gorm:"column:role_id;type:uint;not null;index" validate:"required" description:"被关联的角色"`
|
||||
// 访问范围, 需要提前定义scope, 比如环境
|
||||
Scope map[string]string `json:"scope" bson:"scope" gorm:"column:scope;serializer:json;type:json" description:"数据访问的范围" optional:"true"`
|
||||
// 策略过期时间
|
||||
ExpiredTime *time.Time `json:"expired_time" bson:"expired_time" gorm:"column:expired_time;type:timestamp;index" description:"策略过期时间" optional:"true"`
|
||||
// 只读策略, 不允许用户修改, 一般用于系统管理
|
||||
ReadOnly bool `json:"read_only" bson:"read_only" gorm:"column:read_only;type:tinyint(1)" description:"只读策略, 不允许用户修改, 一般用于系统管理" optional:"true"`
|
||||
// 该策略是否启用
|
||||
Enabled bool `json:"enabled" bson:"enabled" gorm:"column:enabled;type:tinyint(1)" description:"该策略是否启用" optional:"true"`
|
||||
// 策略标签
|
||||
Label string `json:"label" gorm:"column:label;type:varchar(200);index" description:"策略标签" optional:"true"`
|
||||
// 扩展信息
|
||||
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" description:"扩展信息" optional:"true"`
|
||||
}
|
||||
|
||||
func (r *CreatePolicyRequest) Validate() error {
|
||||
return validator.Validate(r)
|
||||
}
|
||||
|
||||
func (r *CreatePolicyRequest) SetNamespaceId(namespaceId uint64) *CreatePolicyRequest {
|
||||
r.NamespaceId = &namespaceId
|
||||
return r
|
||||
}
|
@ -7,7 +7,11 @@ import (
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/api"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/impl"
|
||||
|
||||
// 鉴权
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy/impl"
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role/impl"
|
||||
|
||||
// 颁发器
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers"
|
||||
|
@ -1,18 +1,42 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="prDIldifm9lRc8bqSxY8" name="第 1 页">
|
||||
<mxGraphModel dx="892" dy="476" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<mxGraphModel dx="892" dy="439" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="Role" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxCell id="2" value="Role" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="210" y="230" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="Api Permission" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxCell id="6" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="3" target="5">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="3" target="2">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="Api Permission<div>mapping</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="390" y="120" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="Api Permission" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxCell id="9" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="4" target="7">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="4" target="2">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="View Permission<div>mapping</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="390" y="340" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="Endpont" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="120" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="8">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Menu" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="340" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="Page" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="770" y="340" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
Loading…
x
Reference in New Issue
Block a user