From eb9a155efcafc6ee2e76a09a00f53e23d1c20c95 Mon Sep 17 00:00:00 2001 From: nannan00 <17491932+nannan00@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:18:10 +0800 Subject: [PATCH] feat(web api): query subject group details (#303) * feat(web api): query subject group details --- pkg/abac/pap/group.go | 68 +++++++++++++++++++++++ pkg/abac/pap/group_test.go | 89 ++++++++++++++++++++++++++++++ pkg/abac/pap/mock/group.go | 15 +++++ pkg/api/model/handler/action.go | 2 - pkg/api/model/handler/query.go | 2 - pkg/api/web/handler/group.go | 32 +++++++++++ pkg/api/web/handler/subject_slz.go | 6 ++ pkg/api/web/router.go | 3 + pkg/service/group.go | 32 +++++++++++ pkg/service/mock/group.go | 15 +++++ 10 files changed, 260 insertions(+), 4 deletions(-) diff --git a/pkg/abac/pap/group.go b/pkg/abac/pap/group.go index 9ce9d523..f39689b1 100644 --- a/pkg/abac/pap/group.go +++ b/pkg/abac/pap/group.go @@ -45,6 +45,7 @@ type GroupController interface { _type, id, systemID string, beforeExpiredAt, limit, offset int64, ) ([]SubjectGroup, error) ListGroupSubjectBeforeExpiredAtBySubjects(subjects []Subject, expiredAt int64) ([]GroupSubject, error) + ListSubjectGroupDetails(_type, id string, groupIDs []string) ([]SubjectGroup, error) CheckSubjectEffectGroups(_type, id string, groupIDs []string) (map[string]map[string]interface{}, error) GetGroupMemberCount(_type, id string) (int64, error) @@ -169,6 +170,73 @@ func (c *groupController) ListGroupSubjectBeforeExpiredAtBySubjects( return relations, nil } +func (c *groupController) ListSubjectGroupDetails(_type, id string, groupIDs []string) ([]SubjectGroup, error) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf(GroupCTL, "ListSubjectGroupDetails") + + // subject Type+ID to PK + subjectPK, err := cacheimpls.GetLocalSubjectPK(_type, id) + if err != nil { + return nil, errorWrapf(err, "cacheimpls.GetLocalSubjectPK _type=`%s`, id=`%s` fail", _type, id) + } + + groupPKToID := make(map[int64]string, len(groupIDs)) + groupPKs := make([]int64, 0, len(groupIDs)) + for _, groupID := range groupIDs { + // if groupID is empty, skip + if groupID == "" { + continue + } + + // get the groupPK via groupID + groupPK, err := cacheimpls.GetLocalSubjectPK(types.GroupType, groupID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + log.WithError(err).Debugf("cacheimpls.GetSubjectPK type=`group`, id=`%s` fail", groupID) + continue + } + + return nil, errorWrapf( + err, + "cacheimpls.GetSubjectPK _type=`%s`, id=`%s` fail", + types.GroupType, + groupID, + ) + } + + groupPKs = append(groupPKs, groupPK) + groupPKToID[groupPK] = groupID + } + + // NOTE: if the performance is a problem, change this to a local cache, key: subjectPK, value int64Set + svcSubjectGroups, err := c.service.ListSubjectGroupsBySubjectPKGroupPKs(subjectPK, groupPKs) + if err != nil { + return nil, errorWrapf( + err, + "service.ListSubjectGroupsBySubjectPKGroupPKs subjectPKs=`%d`, groupPKs=`%+v` fail", + subjectPK, + groupPKs, + ) + } + + groups := make([]SubjectGroup, 0, len(svcSubjectGroups)) + for _, m := range svcSubjectGroups { + groupID, ok := groupPKToID[m.GroupPK] + if !ok { + continue + } + + groups = append(groups, SubjectGroup{ + PK: m.PK, + Type: types.GroupType, + ID: groupID, + ExpiredAt: m.ExpiredAt, + CreatedAt: m.CreatedAt, + }) + } + + return groups, nil +} + func (c *groupController) CheckSubjectEffectGroups( _type, id string, groupIDs []string, diff --git a/pkg/abac/pap/group_test.go b/pkg/abac/pap/group_test.go index 100298db..5fd9b9d8 100644 --- a/pkg/abac/pap/group_test.go +++ b/pkg/abac/pap/group_test.go @@ -354,6 +354,95 @@ var _ = Describe("GroupController", func() { }) }) + Describe("ListSubjectGroupDetails", func() { + var ctl *gomock.Controller + var patches *gomonkey.Patches + BeforeEach(func() { + ctl = gomock.NewController(GinkgoT()) + + patches = gomonkey.ApplyFunc(cacheimpls.GetLocalSubjectPK, func(_type, id string) (pk int64, err error) { + if _type == "user" && id == "1" { + return int64(1), nil + } + if _type == "user" && id == "2" { + return int64(2), nil + } + if _type == "group" && id == "10" { + return int64(10), nil + } + + if _type == "group" && id == "20" { + return int64(20), nil + } + + return 0, sql.ErrNoRows + }) + + patches.ApplyFunc(cacheimpls.GetSubjectDepartmentPKs, func(subjectPK int64) ([]int64, error) { + return []int64{10, 20, 30}, nil + }) + }) + AfterEach(func() { + ctl.Finish() + patches.Reset() + }) + + It("get user subject PK fail", func() { + c := &groupController{ + service: mock.NewMockGroupService(ctl), + } + + _, err := c.ListSubjectGroupDetails("user", "notexist", []string{"10", "20"}) + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "cacheimpls.GetLocalSubjectPK") + }) + It("get subject all group pks fail", func() { + mockGroupService := mock.NewMockGroupService(ctl) + mockGroupService.EXPECT().ListSubjectGroupsBySubjectPKGroupPKs(gomock.Any(), gomock.Any()).Return( + nil, errors.New("error"), + ).AnyTimes() + + c := &groupController{ + service: mockGroupService, + } + + _, err := c.ListSubjectGroupDetails("user", "1", []string{"10", "20"}) + + assert.Error(GinkgoT(), err) + assert.Contains(GinkgoT(), err.Error(), "ListSubjectGroupsBySubjectPKGroupPKs") + }) + + It("ok, all groupID valid", func() { + mockGroupService := mock.NewMockGroupService(ctl) + mockGroupService.EXPECT().ListSubjectGroupsBySubjectPKGroupPKs(gomock.Any(), gomock.Any()).Return( + []types.SubjectGroup{{ + PK: 1, + GroupPK: 10, + ExpiredAt: 1, + CreatedAt: time.Time{}, + }, { + PK: 2, + GroupPK: 20, + ExpiredAt: 1, + CreatedAt: time.Time{}, + }}, nil, + ).AnyTimes() + + c := &groupController{ + service: mockGroupService, + } + + groups, err := c.ListSubjectGroupDetails("user", "1", []string{"10", "20"}) + assert.NoError(GinkgoT(), err) + assert.Len(GinkgoT(), groups, 2) + assert.Equal(GinkgoT(), groups[0].PK, int64(1)) + assert.Equal(GinkgoT(), groups[0].ID, "10") + assert.Equal(GinkgoT(), groups[1].PK, int64(2)) + assert.Equal(GinkgoT(), groups[1].ID, "20") + + }) + }) + Describe("CheckSubjectExistGroups", func() { var ctl *gomock.Controller var patches *gomonkey.Patches diff --git a/pkg/abac/pap/mock/group.go b/pkg/abac/pap/mock/group.go index 1286628d..f8152461 100644 --- a/pkg/abac/pap/mock/group.go +++ b/pkg/abac/pap/mock/group.go @@ -333,6 +333,21 @@ func (mr *MockGroupControllerMockRecorder) ListRbacGroupByResource(systemID, res return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRbacGroupByResource", reflect.TypeOf((*MockGroupController)(nil).ListRbacGroupByResource), systemID, resource) } +// ListSubjectGroupDetails mocks base method. +func (m *MockGroupController) ListSubjectGroupDetails(_type, id string, groupIDs []string) ([]pap.SubjectGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSubjectGroupDetails", _type, id, groupIDs) + ret0, _ := ret[0].([]pap.SubjectGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListSubjectGroupDetails indicates an expected call of ListSubjectGroupDetails. +func (mr *MockGroupControllerMockRecorder) ListSubjectGroupDetails(_type, id, groupIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSubjectGroupDetails", reflect.TypeOf((*MockGroupController)(nil).ListSubjectGroupDetails), _type, id, groupIDs) +} + // UpdateGroupMembersExpiredAt mocks base method. func (m *MockGroupController) UpdateGroupMembersExpiredAt(_type, id string, members []pap.GroupMember) error { m.ctrl.T.Helper() diff --git a/pkg/api/model/handler/action.go b/pkg/api/model/handler/action.go index a2a3ff85..7f27548f 100644 --- a/pkg/api/model/handler/action.go +++ b/pkg/api/model/handler/action.go @@ -123,7 +123,6 @@ func BatchCreateActions(c *gin.Context) { // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/systems/{system_id}/actions/{action_id} [put] func UpdateAction(c *gin.Context) { systemID := c.Param("system_id") @@ -256,7 +255,6 @@ func UpdateAction(c *gin.Context) { // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/systems/{system_id}/actions/{action_id} [delete] func DeleteAction(c *gin.Context) { systemID := c.Param("system_id") actionID := c.Param("action_id") diff --git a/pkg/api/model/handler/query.go b/pkg/api/model/handler/query.go index 3216be33..d6be5fd2 100644 --- a/pkg/api/model/handler/query.go +++ b/pkg/api/model/handler/query.go @@ -45,8 +45,6 @@ const ( // @Header 200 {string} X-Request-Id "the request id" // @Security AppCode // @Security AppSecret -// @Router /api/v1/systems/{system_id}/query [get] -// //nolint:gocognit func SystemInfoQuery(c *gin.Context) { var query querySerializer diff --git a/pkg/api/web/handler/group.go b/pkg/api/web/handler/group.go index 7284cdfe..69c0466d 100644 --- a/pkg/api/web/handler/group.go +++ b/pkg/api/web/handler/group.go @@ -111,6 +111,38 @@ func ListSubjectGroups(c *gin.Context) { }) } +// QuerySubjectGroupsDetail ... +func QuerySubjectGroupsDetail(c *gin.Context) { + var query querySubjectGroupsDetailSerializer + if err := c.ShouldBindQuery(&query); err != nil { + util.BadRequestErrorJSONResponse(c, util.ValidationErrorMessage(err)) + return + } + // input: subject.type= & subject.id= & group_ids=1,2,3,4 + // output: 个人或部门 直接加入的每个用户组里的详情(包括过期时间、创建时间) + groupIDs := strings.Split(query.GroupIDs, ",") + if len(groupIDs) > 100 { + util.BadRequestErrorJSONResponse(c, "group_ids should be less than 100") + return + } + + ctl := pap.NewGroupController() + subjectGroups, err := ctl.ListSubjectGroupDetails(query.Type, query.ID, groupIDs) + if err != nil { + err = errorx.Wrapf( + err, + "Handler", + "ctl.ListSubjectGroupDetails type=`%s`, id=`%s` fail", + query.Type, + query.ID, + ) + util.SystemErrorJSONResponse(c, err) + return + } + + util.SuccessJSONResponse(c, "ok", subjectGroups) +} + // CheckSubjectGroupsBelong ... func CheckSubjectGroupsBelong(c *gin.Context) { var query checkSubjectGroupsBelongSerializer diff --git a/pkg/api/web/handler/subject_slz.go b/pkg/api/web/handler/subject_slz.go index bf8e3d1c..67a29a24 100644 --- a/pkg/api/web/handler/subject_slz.go +++ b/pkg/api/web/handler/subject_slz.go @@ -51,6 +51,12 @@ type listGroupMemberSerializer struct { pageSerializer } +type querySubjectGroupsDetailSerializer struct { + Type string `form:"type" binding:"required,oneof=user department"` + ID string `form:"id" binding:"required"` + GroupIDs string `form:"group_ids" binding:"required"` +} + type checkSubjectGroupsBelongSerializer struct { Type string `form:"type" binding:"required,oneof=user department"` ID string `form:"id" binding:"required"` diff --git a/pkg/api/web/router.go b/pkg/api/web/router.go index 13bcc49f..e306d07f 100644 --- a/pkg/api/web/router.go +++ b/pkg/api/web/router.go @@ -134,6 +134,9 @@ func Register(r *gin.RouterGroup) { // 带分页 https://github.com/TencentBlueKing/bk-iam-saas/issues/1155 r.GET("/subject-groups", handler.ListSubjectGroups) + // subject-groups/detail?type=user&id=tome&groups=1,2,3,4,5 + r.GET("/subjects-groups/detail", handler.QuerySubjectGroupsDetail) + // add subject-groups?type=user&id=tome&groups=1,2,3,4,5 r.GET("/subjects-groups/belong", handler.CheckSubjectGroupsBelong) diff --git a/pkg/service/group.go b/pkg/service/group.go index 6225736e..d24e36ef 100644 --- a/pkg/service/group.go +++ b/pkg/service/group.go @@ -47,6 +47,10 @@ type GroupService interface { ListPagingSubjectSystemGroups( subjectPK int64, systemID string, beforeExpiredAt, limit, offset int64, ) ([]types.SubjectGroup, error) + ListSubjectGroupsBySubjectPKGroupPKs( + subjectPK int64, + groupPKs []int64, + ) ([]types.SubjectGroup, error) ListEffectSubjectGroupsBySubjectPKGroupPKs( subjectPK int64, groupPKs []int64, @@ -289,6 +293,34 @@ func (l *groupService) ListGroupSubjectBeforeExpiredAtByGroupPKs( return convertToGroupSubjects(daoRelations), nil } +func (l *groupService) ListSubjectGroupsBySubjectPKGroupPKs( + subjectPK int64, + groupPKs []int64, +) (subjectGroups []types.SubjectGroup, err error) { + errorWrapf := errorx.NewLayerFunctionErrorWrapf(GroupSVC, "ListSubjectGroupsBySubjectPKGroupPKs") + + relations, err := l.manager.ListRelationBySubjectPKGroupPKs(subjectPK, groupPKs) + if err != nil { + return nil, errorWrapf( + err, + "manager.ListRelationBySubjectPKGroupPKs subjectPK=`%d`, parenPKs=`%+v` fail", + subjectPK, groupPKs, + ) + } + + subjectGroups = make([]types.SubjectGroup, 0, len(relations)) + for _, r := range relations { + subjectGroups = append(subjectGroups, types.SubjectGroup{ + PK: r.PK, + GroupPK: r.GroupPK, + ExpiredAt: r.ExpiredAt, + CreatedAt: r.CreatedAt, + }) + } + + return subjectGroups, nil +} + func (l *groupService) ListEffectSubjectGroupsBySubjectPKGroupPKs( subjectPK int64, groupPKs []int64, diff --git a/pkg/service/mock/group.go b/pkg/service/mock/group.go index edbccbbf..7a9f757d 100644 --- a/pkg/service/mock/group.go +++ b/pkg/service/mock/group.go @@ -464,6 +464,21 @@ func (mr *MockGroupServiceMockRecorder) ListPagingTemplateGroupMember(groupPK, t return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPagingTemplateGroupMember", reflect.TypeOf((*MockGroupService)(nil).ListPagingTemplateGroupMember), groupPK, templateID, limit, offset) } +// ListSubjectGroupsBySubjectPKGroupPKs mocks base method. +func (m *MockGroupService) ListSubjectGroupsBySubjectPKGroupPKs(subjectPK int64, groupPKs []int64) ([]types.SubjectGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSubjectGroupsBySubjectPKGroupPKs", subjectPK, groupPKs) + ret0, _ := ret[0].([]types.SubjectGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListSubjectGroupsBySubjectPKGroupPKs indicates an expected call of ListSubjectGroupsBySubjectPKGroupPKs. +func (mr *MockGroupServiceMockRecorder) ListSubjectGroupsBySubjectPKGroupPKs(subjectPK, groupPKs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSubjectGroupsBySubjectPKGroupPKs", reflect.TypeOf((*MockGroupService)(nil).ListSubjectGroupsBySubjectPKGroupPKs), subjectPK, groupPKs) +} + // UpdateGroupMembersExpiredAtWithTx mocks base method. func (m *MockGroupService) UpdateGroupMembersExpiredAtWithTx(tx *sqlx.Tx, groupPK int64, members []types.SubjectTemplateGroup) error { m.ctrl.T.Helper()