Skip to content

Commit

Permalink
feat(web api): query subject group details (#303)
Browse files Browse the repository at this point in the history
* feat(web api): query subject group details
  • Loading branch information
nannan00 authored Aug 13, 2024
1 parent aecac6c commit eb9a155
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 4 deletions.
68 changes: 68 additions & 0 deletions pkg/abac/pap/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
89 changes: 89 additions & 0 deletions pkg/abac/pap/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions pkg/abac/pap/mock/group.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions pkg/api/model/handler/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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")
Expand Down
2 changes: 0 additions & 2 deletions pkg/api/model/handler/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions pkg/api/web/handler/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/web/handler/subject_slz.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/web/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
32 changes: 32 additions & 0 deletions pkg/service/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions pkg/service/mock/group.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit eb9a155

Please sign in to comment.