diff --git a/devcloud/mcenter/apps/namespace/const.go b/devcloud/mcenter/apps/namespace/const.go new file mode 100644 index 0000000..13a0276 --- /dev/null +++ b/devcloud/mcenter/apps/namespace/const.go @@ -0,0 +1,5 @@ +package namespace + +const ( + DEFAULT_NS_NAME = "default" +) diff --git a/devcloud/mcenter/apps/namespace/impl/impl.go b/devcloud/mcenter/apps/namespace/impl/impl.go new file mode 100644 index 0000000..5d5787f --- /dev/null +++ b/devcloud/mcenter/apps/namespace/impl/impl.go @@ -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 +} diff --git a/devcloud/mcenter/apps/namespace/impl/impl_test.go b/devcloud/mcenter/apps/namespace/impl/impl_test.go new file mode 100644 index 0000000..7eb6feb --- /dev/null +++ b/devcloud/mcenter/apps/namespace/impl/impl_test.go @@ -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() +} diff --git a/devcloud/mcenter/apps/namespace/impl/namespace.go b/devcloud/mcenter/apps/namespace/impl/namespace.go new file mode 100644 index 0000000..9bc225b --- /dev/null +++ b/devcloud/mcenter/apps/namespace/impl/namespace.go @@ -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 +} diff --git a/devcloud/mcenter/apps/namespace/impl/namespace_test.go b/devcloud/mcenter/apps/namespace/impl/namespace_test.go new file mode 100644 index 0000000..f5f9c4f --- /dev/null +++ b/devcloud/mcenter/apps/namespace/impl/namespace_test.go @@ -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) +} diff --git a/devcloud/mcenter/apps/namespace/interface.go b/devcloud/mcenter/apps/namespace/interface.go new file mode 100644 index 0000000..e1eecdb --- /dev/null +++ b/devcloud/mcenter/apps/namespace/interface.go @@ -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 +} diff --git a/devcloud/mcenter/apps/namespace/model.go b/devcloud/mcenter/apps/namespace/model.go new file mode 100644 index 0000000..607c13f --- /dev/null +++ b/devcloud/mcenter/apps/namespace/model.go @@ -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) +} diff --git a/devcloud/mcenter/apps/policy/impl/impl.go b/devcloud/mcenter/apps/policy/impl/impl.go new file mode 100644 index 0000000..6247aca --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/impl.go @@ -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 +} diff --git a/devcloud/mcenter/apps/policy/impl/impl_test.go b/devcloud/mcenter/apps/policy/impl/impl_test.go new file mode 100644 index 0000000..35bee8c --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/impl_test.go @@ -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() +} diff --git a/devcloud/mcenter/apps/policy/impl/permission.go b/devcloud/mcenter/apps/policy/impl/permission.go new file mode 100644 index 0000000..0f20c96 --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/permission.go @@ -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 +} diff --git a/devcloud/mcenter/apps/policy/impl/permission_test.go b/devcloud/mcenter/apps/policy/impl/permission_test.go new file mode 100644 index 0000000..a01b4dd --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/permission_test.go @@ -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) +} diff --git a/devcloud/mcenter/apps/policy/impl/policy.go b/devcloud/mcenter/apps/policy/impl/policy.go new file mode 100644 index 0000000..bbc68ef --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/policy.go @@ -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 +} diff --git a/devcloud/mcenter/apps/policy/impl/policy_test.go b/devcloud/mcenter/apps/policy/impl/policy_test.go new file mode 100644 index 0000000..1a95f0a --- /dev/null +++ b/devcloud/mcenter/apps/policy/impl/policy_test.go @@ -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) +} diff --git a/devcloud/mcenter/apps/policy/interface.go b/devcloud/mcenter/apps/policy/interface.go new file mode 100644 index 0000000..4a2f5f9 --- /dev/null +++ b/devcloud/mcenter/apps/policy/interface.go @@ -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 +} diff --git a/devcloud/mcenter/apps/policy/model.go b/devcloud/mcenter/apps/policy/model.go new file mode 100644 index 0000000..1deb0dc --- /dev/null +++ b/devcloud/mcenter/apps/policy/model.go @@ -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 +} diff --git a/devcloud/mcenter/apps/registry.go b/devcloud/mcenter/apps/registry.go index cf9cd5d..3a55215 100644 --- a/devcloud/mcenter/apps/registry.go +++ b/devcloud/mcenter/apps/registry.go @@ -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" diff --git a/devcloud/mcenter/apps/role/design.drawio b/devcloud/mcenter/apps/role/design.drawio index 2d7cb36..740f903 100644 --- a/devcloud/mcenter/apps/role/design.drawio +++ b/devcloud/mcenter/apps/role/design.drawio @@ -1,18 +1,42 @@ - + - + - + + + + + + + - + + + + + + + + + + + + + + + + + + +