mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-25 03:35:21 +01:00
Merge pull request #5200 from stonezdj/add_group_search_api
Add REST API to search LDAP by Group DN
This commit is contained in:
commit
ad7a85da51
@ -2325,14 +2325,18 @@ paths:
|
|||||||
summary: Search available ldap groups.
|
summary: Search available ldap groups.
|
||||||
description: >
|
description: >
|
||||||
This endpoint searches the available ldap groups based on related
|
This endpoint searches the available ldap groups based on related
|
||||||
configuration parameters. Support searched by input ladp configuration,
|
configuration parameters. support to search by groupname or groupdn.
|
||||||
load configuration from the system and specific filter.
|
|
||||||
parameters:
|
parameters:
|
||||||
- name: groupname
|
- name: groupname
|
||||||
in: query
|
in: query
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
description: Ldap group name
|
description: Ldap group name
|
||||||
|
- name: groupdn
|
||||||
|
in: query
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
description: The LDAP group DN
|
||||||
tags:
|
tags:
|
||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
@ -2342,6 +2346,10 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/UserGroup'
|
$ref: '#/definitions/UserGroup'
|
||||||
|
"400":
|
||||||
|
description: The Ldap group DN is invalid.
|
||||||
|
'404':
|
||||||
|
description: No ldap group found.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
/ldap/users/search:
|
/ldap/users/search:
|
||||||
@ -2372,6 +2380,7 @@ paths:
|
|||||||
description: Only admin has this authority.
|
description: Only admin has this authority.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
|
||||||
/ldap/users/import:
|
/ldap/users/import:
|
||||||
post:
|
post:
|
||||||
summary: Import selected available ldap users.
|
summary: Import selected available ldap users.
|
||||||
@ -3653,6 +3662,7 @@ definitions:
|
|||||||
ldap_group_dn:
|
ldap_group_dn:
|
||||||
type: string
|
type: string
|
||||||
description: The DN of the LDAP group if group type is 1 (LDAP group).
|
description: The DN of the LDAP group if group type is 1 (LDAP group).
|
||||||
|
|
||||||
Resource:
|
Resource:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
|
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
"github.com/vmware/harbor/src/ui/filter"
|
"github.com/vmware/harbor/src/ui/filter"
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
@ -77,6 +78,25 @@ type usrInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
ldapConfig := models.LdapConf{
|
||||||
|
LdapURL: "ldap://127.0.0.1:389",
|
||||||
|
LdapSearchDn: "cn=admin,dc=example,dc=com",
|
||||||
|
LdapSearchPassword: "admin",
|
||||||
|
LdapBaseDn: "dc=example,dc=com",
|
||||||
|
LdapUID: "cn",
|
||||||
|
LdapScope: 2,
|
||||||
|
LdapConnectionTimeout: 5,
|
||||||
|
}
|
||||||
|
ldapGroupConfig := models.LdapGroupConf{
|
||||||
|
LdapGroupBaseDN: "ou=groups,dc=example,dc=com",
|
||||||
|
LdapGroupFilter: "objectclass=groupOfNames",
|
||||||
|
LdapGroupSearchScope: 2,
|
||||||
|
LdapGroupNameAttribute: "cn",
|
||||||
|
}
|
||||||
|
ldapTestConfig, err := ldapUtils.CreateWithAllConfig(ldapConfig, ldapGroupConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
|
}
|
||||||
if err := config.Init(); err != nil {
|
if err := config.Init(); err != nil {
|
||||||
log.Fatalf("failed to initialize configurations: %v", err)
|
log.Fatalf("failed to initialize configurations: %v", err)
|
||||||
}
|
}
|
||||||
@ -134,7 +154,10 @@ func init() {
|
|||||||
beego.Router("/api/systeminfo", &SystemInfoAPI{}, "get:GetGeneralInfo")
|
beego.Router("/api/systeminfo", &SystemInfoAPI{}, "get:GetGeneralInfo")
|
||||||
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
||||||
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &LdapAPI{ldapConfig: ldapTestConfig, useTestConfig: true}, "post:Ping")
|
||||||
|
beego.Router("/api/ldap/users/search", &LdapAPI{ldapConfig: ldapTestConfig, useTestConfig: true}, "get:Search")
|
||||||
|
beego.Router("/api/ldap/groups/search", &LdapAPI{ldapConfig: ldapTestConfig, useTestConfig: true}, "get:SearchGroup")
|
||||||
|
beego.Router("/api/ldap/users/import", &LdapAPI{ldapConfig: ldapTestConfig, useTestConfig: true}, "post:ImportUser")
|
||||||
beego.Router("/api/configurations", &ConfigAPI{})
|
beego.Router("/api/configurations", &ConfigAPI{})
|
||||||
beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset")
|
beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset")
|
||||||
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
||||||
|
@ -21,11 +21,15 @@ import (
|
|||||||
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/auth"
|
"github.com/vmware/harbor/src/ui/auth"
|
||||||
|
|
||||||
|
goldap "gopkg.in/ldap.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/user/search /api/ldap/user/import
|
// LdapAPI handles requesst to /api/ldap/ping /api/ldap/user/search /api/ldap/user/import
|
||||||
type LdapAPI struct {
|
type LdapAPI struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
ldapConfig *ldapUtils.Session
|
||||||
|
useTestConfig bool // Only used for unit test
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -47,6 +51,14 @@ func (l *LdapAPI) Prepare() {
|
|||||||
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
l.HandleForbidden(l.SecurityCtx.GetUsername())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if l.useTestConfig {
|
||||||
|
ldapCfg, err := ldapUtils.LoadSystemLdapConfig()
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("Can't load system configuration, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.ldapConfig = ldapCfg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping ...
|
// Ping ...
|
||||||
@ -55,16 +67,11 @@ func (l *LdapAPI) Ping() {
|
|||||||
LdapConnectionTimeout: 5,
|
LdapConnectionTimeout: 5,
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var ldapSession *ldapUtils.Session
|
|
||||||
|
|
||||||
l.Ctx.Input.CopyBody(1 << 32)
|
l.Ctx.Input.CopyBody(1 << 32)
|
||||||
|
|
||||||
if string(l.Ctx.Input.RequestBody) == "" {
|
if string(l.Ctx.Input.RequestBody) == "" {
|
||||||
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
|
ldapSession := *l.ldapConfig
|
||||||
if err != nil {
|
|
||||||
l.HandleInternalServerError(fmt.Sprintf("Can't load system configuration, error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = ldapSession.ConnectionTest()
|
err = ldapSession.ConnectionTest()
|
||||||
} else {
|
} else {
|
||||||
l.DecodeJSONReqAndValidate(&ldapConfs)
|
l.DecodeJSONReqAndValidate(&ldapConfs)
|
||||||
@ -81,7 +88,7 @@ func (l *LdapAPI) Ping() {
|
|||||||
func (l *LdapAPI) Search() {
|
func (l *LdapAPI) Search() {
|
||||||
var err error
|
var err error
|
||||||
var ldapUsers []models.LdapUser
|
var ldapUsers []models.LdapUser
|
||||||
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
ldapSession := *l.ldapConfig
|
||||||
if err = ldapSession.Open(); err != nil {
|
if err = ldapSession.Open(); err != nil {
|
||||||
l.HandleInternalServerError(fmt.Sprintf("Can't Open LDAP session, error: %v", err))
|
l.HandleInternalServerError(fmt.Sprintf("Can't Open LDAP session, error: %v", err))
|
||||||
return
|
return
|
||||||
@ -106,11 +113,10 @@ func (l *LdapAPI) Search() {
|
|||||||
func (l *LdapAPI) ImportUser() {
|
func (l *LdapAPI) ImportUser() {
|
||||||
var ldapImportUsers models.LdapImportUser
|
var ldapImportUsers models.LdapImportUser
|
||||||
var ldapFailedImportUsers []models.LdapFailedImportUser
|
var ldapFailedImportUsers []models.LdapFailedImportUser
|
||||||
var ldapConfs models.LdapConf
|
|
||||||
|
|
||||||
l.DecodeJSONReqAndValidate(&ldapImportUsers)
|
l.DecodeJSONReqAndValidate(&ldapImportUsers)
|
||||||
|
|
||||||
ldapFailedImportUsers, err := importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
|
ldapFailedImportUsers, err := importUsers(ldapImportUsers.LdapUIDList, l.ldapConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.HandleInternalServerError(fmt.Sprintf("LDAP import user fail, error: %v", err))
|
l.HandleInternalServerError(fmt.Sprintf("LDAP import user fail, error: %v", err))
|
||||||
@ -127,17 +133,12 @@ func (l *LdapAPI) ImportUser() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.LdapFailedImportUser, error) {
|
func importUsers(ldapImportUsers []string, ldapConfig *ldapUtils.Session) ([]models.LdapFailedImportUser, error) {
|
||||||
var failedImportUser []models.LdapFailedImportUser
|
var failedImportUser []models.LdapFailedImportUser
|
||||||
var u models.LdapFailedImportUser
|
var u models.LdapFailedImportUser
|
||||||
|
|
||||||
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
ldapSession := *ldapConfig
|
||||||
if err != nil {
|
if err := ldapSession.Open(); err != nil {
|
||||||
log.Errorf("Can't load system configuration, error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ldapSession.Open(); err != nil {
|
|
||||||
log.Errorf("Can't connect to LDAP, error: %v", err)
|
log.Errorf("Can't connect to LDAP, error: %v", err)
|
||||||
}
|
}
|
||||||
defer ldapSession.Close()
|
defer ldapSession.Close()
|
||||||
@ -194,17 +195,35 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
|
|||||||
|
|
||||||
// SearchGroup ... Search LDAP by groupname
|
// SearchGroup ... Search LDAP by groupname
|
||||||
func (l *LdapAPI) SearchGroup() {
|
func (l *LdapAPI) SearchGroup() {
|
||||||
|
var ldapGroups []models.LdapGroup
|
||||||
|
var err error
|
||||||
searchName := l.GetString("groupname")
|
searchName := l.GetString("groupname")
|
||||||
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
|
groupDN := l.GetString("groupdn")
|
||||||
if err != nil {
|
ldapSession := *l.ldapConfig
|
||||||
l.HandleInternalServerError(fmt.Sprintf("Can't get LDAP system config, error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ldapSession.Open()
|
ldapSession.Open()
|
||||||
defer ldapSession.Close()
|
defer ldapSession.Close()
|
||||||
ldapGroups, err := ldapSession.SearchGroupByName(searchName)
|
|
||||||
if err != nil {
|
//Search LDAP group by groupName or group DN
|
||||||
l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err))
|
if len(searchName) > 0 {
|
||||||
|
ldapGroups, err = ldapSession.SearchGroupByName(searchName)
|
||||||
|
if err != nil {
|
||||||
|
l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if len(groupDN) > 0 {
|
||||||
|
if _, err := goldap.ParseDN(groupDN); err != nil {
|
||||||
|
l.HandleBadRequest(fmt.Sprintf("Invalid DN: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ldapGroups, err = ldapSession.SearchGroupByDN(groupDN)
|
||||||
|
if err != nil {
|
||||||
|
//OpenLDAP usually return an error if DN is not found
|
||||||
|
l.HandleNotFound(fmt.Sprintf("Search LDAP group fail, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ldapGroups) == 0 {
|
||||||
|
l.HandleNotFound("No ldap group found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Data["json"] = ldapGroups
|
l.Data["json"] = ldapGroups
|
||||||
|
136
src/ui/api/ldap_test.go
Normal file
136
src/ui/api/ldap_test.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLDAPPing(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/ldap/ping",
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/ldap/ping",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/ldap/ping",
|
||||||
|
bodyJSON: &models.LdapConf{
|
||||||
|
LdapURL: "ldap://127.0.0.1:389",
|
||||||
|
LdapSearchDn: "cn=admin,dc=example,dc=com",
|
||||||
|
LdapSearchPassword: "admin",
|
||||||
|
LdapBaseDn: "dc=example,dc=com",
|
||||||
|
LdapUID: "cn",
|
||||||
|
LdapScope: 2,
|
||||||
|
LdapConnectionTimeout: 5,
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLDAPUserSearch(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/users/search?username=mike",
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/users/search?username=mike",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLDAPGroupSearch(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/groups/search?groupname=harbor_users",
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/groups/search?groupname=harbor_users",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLDAPGroupSearchWithDN(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/groups/search?groupdn=cn=harbor_users,ou=groups,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodGet,
|
||||||
|
url: "/api/ldap/groups/search?groupname=cn=harbor_users,ou=groups,dc=example,dc=com",
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLDAPImportUser(t *testing.T) {
|
||||||
|
cases := []*codeCheckingCase{
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/ldap/users/import",
|
||||||
|
bodyJSON: &models.LdapImportUser{
|
||||||
|
LdapUIDList: []string{"mike", "mike02"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
&codeCheckingCase{
|
||||||
|
request: &testingRequest{
|
||||||
|
method: http.MethodPost,
|
||||||
|
url: "/api/ldap/users/import",
|
||||||
|
bodyJSON: &models.LdapImportUser{
|
||||||
|
LdapUIDList: []string{"mike", "mike02"},
|
||||||
|
},
|
||||||
|
credential: admin,
|
||||||
|
},
|
||||||
|
code: http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runCodeCheckingCases(t, cases...)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user