Add usergroup search API (#15483)

Fixes #15450
  Add paging function to usergroup list/search API
  Fix some 500 error when adding LDAP user/group to project member

Signed-off-by: stonezdj <stonezdj@gmail.com>
This commit is contained in:
stonezdj(Daojun Zhang) 2021-09-02 09:04:33 +08:00 committed by GitHub
parent ff617950b7
commit 6b8c5c9edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 58 deletions

View File

@ -2697,6 +2697,8 @@ paths:
- usergroup
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize'
- name: ldap_group_dn
in: query
type: string
@ -2709,6 +2711,13 @@ paths:
type: array
items:
$ref: '#/definitions/UserGroup'
headers:
X-Total-Count:
description: The total count of available items
type: integer
Link:
description: Link to previous page and next page
type: string
'401':
$ref: '#/responses/401'
'403':
@ -2744,6 +2753,41 @@ paths:
$ref: '#/responses/409'
'500':
$ref: '#/responses/500'
/usergroups/search:
get:
summary: Search groups by groupname
description: |
This endpoint is to search groups by group name. It's open for all authenticated requests.
tags:
- usergroup
operationId: searchUserGroups
parameters:
- $ref: '#/parameters/requestId'
- $ref: '#/parameters/page'
- $ref: '#/parameters/pageSize'
- name: groupname
in: query
type: string
required: true
description: Group name for filtering results.
responses:
'200':
description: Search groups successfully.
schema:
type: array
items:
$ref: '#/definitions/UserGroupSearchItem'
headers:
X-Total-Count:
description: The total count of available items
type: integer
Link:
description: Link to previous page and next page
type: string
'401':
$ref: '#/responses/401'
'500':
$ref: '#/responses/500'
'/usergroups/{group_id}':
get:
summary: Get user group information
@ -7689,6 +7733,18 @@ definitions:
ldap_group_dn:
type: string
description: The DN of the LDAP group if group type is 1 (LDAP group).
UserGroupSearchItem:
type: object
properties:
id:
type: integer
description: The ID of the user group
group_name:
type: string
description: The name of the user group
group_type:
type: integer
description: 'The group type, 1 for LDAP group, 2 for HTTP group.'
SupportedWebhookEventTypes:
type: object
description: Supportted webhook event types and notify types.

View File

@ -26,7 +26,6 @@ import (
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/user"
"github.com/goharbor/harbor/src/pkg/usergroup"
ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model"
)
// Controller defines the operation related to project member
@ -151,14 +150,26 @@ func (c *controller) Create(ctx context.Context, projectNameOrID interface{}, re
member.EntityID = userID
} else if len(req.MemberGroup.LdapGroupDN) > 0 {
req.MemberGroup.GroupType = common.LDAPGroupType
// If groupname provided, use the provided groupname to name this group
groupID, err := auth.SearchAndOnBoardGroup(req.MemberGroup.LdapGroupDN, req.MemberGroup.GroupName)
// if the ldap group dn already exist
ugs, err := usergroup.Mgr.List(ctx, q.New(q.KeyWords{"LdapGroupDN": req.MemberGroup.LdapGroupDN, "GroupType": req.MemberGroup.GroupType}))
if err != nil {
return 0, err
}
member.EntityID = groupID
} else if len(req.MemberGroup.GroupName) > 0 && req.MemberGroup.GroupType == common.HTTPGroupType || req.MemberGroup.GroupType == common.OIDCGroupType {
ugs, err := usergroup.Mgr.List(ctx, ugModel.UserGroup{GroupName: req.MemberGroup.GroupName, GroupType: req.MemberGroup.GroupType})
if len(ugs) > 0 {
member.EntityID = ugs[0].ID
member.EntityType = common.GroupMember
} else {
// If groupname provided, use the provided groupname to name this group
groupID, err := auth.SearchAndOnBoardGroup(req.MemberGroup.LdapGroupDN, req.MemberGroup.GroupName)
if err != nil {
return 0, err
}
member.EntityID = groupID
}
} else if len(req.MemberGroup.GroupName) > 0 {
// all group type can be added to project member by name
ugs, err := usergroup.Mgr.List(ctx, q.New(q.KeyWords{"GroupName": req.MemberGroup.GroupName, "GroupType": req.MemberGroup.GroupType}))
if err != nil {
return 0, err
}

View File

@ -19,6 +19,7 @@ import (
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/ldap"
"github.com/goharbor/harbor/src/pkg/usergroup"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
@ -44,7 +45,9 @@ type Controller interface {
// Populate populate user group and get the user group's id
Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error)
// List list user groups
List(ctx context.Context, userGroup model.UserGroup) ([]*model.UserGroup, error)
List(ctx context.Context, q *q.Query) ([]*model.UserGroup, error)
// Count user group count
Count(ctx context.Context, q *q.Query) (int64, error)
}
type controller struct {
@ -55,8 +58,8 @@ func newController() Controller {
return &controller{mgr: usergroup.Mgr}
}
func (c *controller) List(ctx context.Context, userGroup model.UserGroup) ([]*model.UserGroup, error) {
return c.mgr.List(ctx, userGroup)
func (c *controller) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) {
return c.mgr.List(ctx, query)
}
func (c *controller) Populate(ctx context.Context, userGroups []model.UserGroup) ([]int, error) {
@ -72,7 +75,7 @@ func (c *controller) Delete(ctx context.Context, id int) error {
}
func (c *controller) Update(ctx context.Context, id int, groupName string) error {
ug, err := c.mgr.List(ctx, model.UserGroup{ID: id})
ug, err := c.mgr.List(ctx, q.New(q.KeyWords{"ID": id}))
if err != nil {
return err
}
@ -109,3 +112,7 @@ func (c *controller) Create(ctx context.Context, group model.UserGroup) (int, er
func (c *controller) Get(ctx context.Context, id int) (*model.UserGroup, error) {
return c.mgr.Get(ctx, id)
}
func (c *controller) Count(ctx context.Context, query *q.Query) (int64, error) {
return c.mgr.Count(ctx, query)
}

View File

@ -224,7 +224,7 @@ func SearchAndOnBoardUser(username string) (int, error) {
return 0, err
}
if user == nil {
return 0, ErrorUserNotExist
return 0, libErrors.NotFoundError(nil).WithMessage(fmt.Sprintf("user %s is not found", username))
}
err = OnBoardUser(user)
if err != nil {

View File

@ -194,7 +194,7 @@ func (l *Auth) SearchUser(username string) (*models.User, error) {
log.Debugf("Found ldap user %v", user)
} else {
return nil, fmt.Errorf("no user found, %v", username)
return nil, errors.NotFoundError(nil).WithMessage("no user found: %v", username)
}
return &user, nil
@ -224,7 +224,7 @@ func (l *Auth) SearchGroup(groupKey string) (*ugModel.UserGroup, error) {
}
if len(userGroupList) == 0 {
return nil, fmt.Errorf("failed to searh ldap group with groupDN:%v", groupKey)
return nil, errors.NotFoundError(nil).WithMessage("failed to searh ldap group with groupDN:%v", groupKey)
}
userGroup := ugModel.UserGroup{
GroupName: userGroupList[0].Name,
@ -244,7 +244,7 @@ func (l *Auth) OnBoardGroup(u *ugModel.UserGroup, altGroupName string) error {
}
u.GroupType = common.LDAPGroupType
// Check duplicate LDAP DN in usergroup, if usergroup exist, return error
userGroupList, err := ugCtl.Ctl.List(ctx, ugModel.UserGroup{LdapGroupDN: u.LdapGroupDN})
userGroupList, err := ugCtl.Ctl.List(ctx, q.New(q.KeyWords{"LdapGroupDN": u.LdapGroupDN}))
if err != nil {
return err
}

View File

@ -21,6 +21,7 @@ import (
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
"time"
)
@ -35,8 +36,10 @@ func init() {
type DAO interface {
// Add add user group
Add(ctx context.Context, userGroup model.UserGroup) (int, error)
// Count query user group count
Count(ctx context.Context, query *q.Query) (int64, error)
// Query query user group
Query(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error)
Query(ctx context.Context, query *q.Query) ([]*model.UserGroup, error)
// Get get user group by id
Get(ctx context.Context, id int) (*model.UserGroup, error)
// Delete delete user group by id
@ -60,7 +63,8 @@ var ErrGroupNameDup = errors.ConflictError(nil).WithMessage("duplicated user gro
// Add - Add User Group
func (d *dao) Add(ctx context.Context, userGroup model.UserGroup) (int, error) {
userGroupList, err := d.Query(ctx, model.UserGroup{GroupName: userGroup.GroupName, GroupType: common.HTTPGroupType})
query := q.New(q.KeyWords{"GroupName": userGroup.GroupName, "GroupType": common.HTTPGroupType})
userGroupList, err := d.Query(ctx, query)
if err != nil {
return 0, ErrGroupNameDup
}
@ -84,43 +88,22 @@ func (d *dao) Add(ctx context.Context, userGroup model.UserGroup) (int, error) {
}
// Query - Query User Group
func (d *dao) Query(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) {
o, err := orm.FromContext(ctx)
func (d *dao) Query(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) {
query = q.MustClone(query)
qs, err := orm.QuerySetter(ctx, &model.UserGroup{}, query)
if err != nil {
return nil, err
}
sql := `select id, group_name, group_type, ldap_group_dn from user_group where 1=1 `
sqlParam := make([]interface{}, 1)
var groups []*model.UserGroup
if len(query.GroupName) != 0 {
sql += ` and group_name = ? `
sqlParam = append(sqlParam, query.GroupName)
}
if query.GroupType != 0 {
sql += ` and group_type = ? `
sqlParam = append(sqlParam, query.GroupType)
}
if len(query.LdapGroupDN) != 0 {
sql += ` and ldap_group_dn = ? `
sqlParam = append(sqlParam, utils.TrimLower(query.LdapGroupDN))
}
if query.ID != 0 {
sql += ` and id = ? `
sqlParam = append(sqlParam, query.ID)
}
_, err = o.Raw(sql, sqlParam).QueryRows(&groups)
if err != nil {
var usergroups []*model.UserGroup
if _, err := qs.All(&usergroups); err != nil {
return nil, err
}
return groups, nil
return usergroups, nil
}
// Get ...
func (d *dao) Get(ctx context.Context, id int) (*model.UserGroup, error) {
userGroup := model.UserGroup{ID: id}
userGroupList, err := d.Query(ctx, userGroup)
userGroupList, err := d.Query(ctx, q.New(q.KeyWords{"ID": id}))
if err != nil {
return nil, err
}
@ -192,3 +175,12 @@ func (d *dao) onBoardCommonUserGroup(ctx context.Context, g *model.UserGroup, ke
return nil
}
func (d *dao) Count(ctx context.Context, query *q.Query) (int64, error) {
query = q.MustClone(query)
qs, err := orm.QuerySetterForCount(ctx, &model.UserGroup{}, query)
if err != nil {
return 0, err
}
return qs.Count()
}

View File

@ -19,6 +19,7 @@ import (
"errors"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/usergroup/dao"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
)
@ -35,7 +36,9 @@ type Manager interface {
// Create create user group
Create(ctx context.Context, userGroup model.UserGroup) (int, error)
// List list user group
List(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error)
List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error)
// Count get user group count
Count(ctx context.Context, query *q.Query) (int64, error)
// Get get user group by id
Get(ctx context.Context, id int) (*model.UserGroup, error)
// Populate populate user group from external auth server to Harbor and return the group id
@ -57,11 +60,7 @@ func newManager() Manager {
}
func (m *manager) Create(ctx context.Context, userGroup model.UserGroup) (int, error) {
query := model.UserGroup{
GroupName: userGroup.GroupName,
GroupType: userGroup.GroupType,
}
ug, err := m.dao.Query(ctx, query)
ug, err := m.dao.Query(ctx, q.New(q.KeyWords{"GroupName": userGroup.GroupName, "GroupType": userGroup.GroupType}))
if err != nil {
return 0, err
}
@ -71,7 +70,7 @@ func (m *manager) Create(ctx context.Context, userGroup model.UserGroup) (int, e
return m.dao.Add(ctx, userGroup)
}
func (m *manager) List(ctx context.Context, query model.UserGroup) ([]*model.UserGroup, error) {
func (m *manager) List(ctx context.Context, query *q.Query) ([]*model.UserGroup, error) {
return m.dao.Query(ctx, query)
}
@ -127,3 +126,7 @@ func (m *manager) onBoardCommonUserGroup(ctx context.Context, g *model.UserGroup
}
return nil
}
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
return m.dao.Count(ctx, query)
}

View File

@ -15,6 +15,7 @@
package usergroup
import (
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
htesting "github.com/goharbor/harbor/src/testing"
"github.com/stretchr/testify/suite"
@ -41,8 +42,7 @@ func (s *ManagerTestSuite) TestOnboardGroup() {
}
err := s.mgr.Onboard(ctx, ug)
s.Nil(err)
qm := model.UserGroup{GroupType: 1, LdapGroupDN: "cn=harbor_dev,ou=groups,dc=example,dc=com"}
ugs, err := s.mgr.List(ctx, qm)
ugs, err := s.mgr.List(ctx, q.New(q.KeyWords{"GroupType": 1, "LdapGroupDN": "cn=harbor_dev,ou=groups,dc=example,dc=com"}))
s.Nil(err)
s.True(len(ugs) > 0)
}

View File

@ -23,6 +23,7 @@ import (
ugCtl "github.com/goharbor/harbor/src/controller/usergroup"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
"github.com/goharbor/harbor/src/server/v2.0/models"
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/usergroup"
@ -105,22 +106,35 @@ func (u *userGroupAPI) ListUserGroups(ctx context.Context, params operation.List
if err != nil {
return u.SendError(ctx, err)
}
query := model.UserGroup{}
query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize)
if err != nil {
return u.SendError(ctx, err)
}
switch authMode {
case common.LDAPAuth:
query.GroupType = common.LDAPGroupType
query.Keywords["GroupType"] = common.LDAPGroupType
if params.LdapGroupDn != nil && len(*params.LdapGroupDn) > 0 {
query.LdapGroupDN = *params.LdapGroupDn
query.Keywords["LdapGroupDN"] = *params.LdapGroupDn
}
case common.HTTPAuth:
query.GroupType = common.HTTPGroupType
query.Keywords["GroupType"] = common.HTTPGroupType
}
total, err := u.ctl.Count(ctx, query)
if err != nil {
return u.SendError(ctx, err)
}
if total == 0 {
return operation.NewListUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroup{})
}
ug, err := u.ctl.List(ctx, query)
if err != nil {
return u.SendError(ctx, err)
}
return operation.NewListUserGroupsOK().WithPayload(getUserGroupResp(ug))
return operation.NewListUserGroupsOK().
WithXTotalCount(total).
WithPayload(getUserGroupResp(ug)).
WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
}
func getUserGroupResp(ug []*model.UserGroup) []*models.UserGroup {
result := make([]*models.UserGroup, 0)
@ -135,6 +149,18 @@ func getUserGroupResp(ug []*model.UserGroup) []*models.UserGroup {
}
return result
}
func getUserGroupSearchItem(ug []*model.UserGroup) []*models.UserGroupSearchItem {
result := make([]*models.UserGroupSearchItem, 0)
for _, u := range ug {
ug := &models.UserGroupSearchItem{
GroupName: u.GroupName,
GroupType: int64(u.GroupType),
ID: int64(u.ID),
}
result = append(result, ug)
}
return result
}
func (u *userGroupAPI) UpdateUserGroup(ctx context.Context, params operation.UpdateUserGroupParams) middleware.Responder {
if err := u.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceUserGroup); err != nil {
return u.SendError(ctx, err)
@ -151,3 +177,31 @@ func (u *userGroupAPI) UpdateUserGroup(ctx context.Context, params operation.Upd
}
return operation.NewUpdateUserGroupOK()
}
func (u *userGroupAPI) SearchUserGroups(ctx context.Context, params operation.SearchUserGroupsParams) middleware.Responder {
if err := u.RequireAuthenticated(ctx); err != nil {
return u.SendError(ctx, err)
}
query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize)
if err != nil {
return u.SendError(ctx, err)
}
if len(params.Groupname) == 0 {
return u.SendError(ctx, errors.BadRequestError(nil).WithMessage("need to provide groupname to search user group"))
}
query.Keywords["GroupName"] = &q.FuzzyMatchValue{Value: params.Groupname}
total, err := u.ctl.Count(ctx, query)
if err != nil {
return u.SendError(ctx, err)
}
if total == 0 {
return operation.NewSearchUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroupSearchItem{})
}
ug, err := u.ctl.List(ctx, query)
if err != nil {
return u.SendError(ctx, err)
}
return operation.NewSearchUserGroupsOK().WithXTotalCount(total).
WithPayload(getUserGroupSearchItem(ug)).
WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
}