Merge pull request #5200 from stonezdj/add_group_search_api

Add REST API to search LDAP by Group DN
This commit is contained in:
Daniel Jiang 2018-07-06 15:10:31 +08:00 committed by GitHub
commit ad7a85da51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 28 deletions

View File

@ -2325,14 +2325,18 @@ paths:
summary: Search available ldap groups.
description: >
This endpoint searches the available ldap groups based on related
configuration parameters. Support searched by input ladp configuration,
load configuration from the system and specific filter.
configuration parameters. support to search by groupname or groupdn.
parameters:
- name: groupname
in: query
type: string
required: false
description: Ldap group name
- name: groupdn
in: query
type: string
required: false
description: The LDAP group DN
tags:
- Products
responses:
@ -2342,6 +2346,10 @@ paths:
type: array
items:
$ref: '#/definitions/UserGroup'
"400":
description: The Ldap group DN is invalid.
'404':
description: No ldap group found.
'500':
description: Unexpected internal errors.
/ldap/users/search:
@ -2372,6 +2380,7 @@ paths:
description: Only admin has this authority.
'500':
description: Unexpected internal errors.
/ldap/users/import:
post:
summary: Import selected available ldap users.
@ -3653,6 +3662,7 @@ definitions:
ldap_group_dn:
type: string
description: The DN of the LDAP group if group type is 1 (LDAP group).
Resource:
type: object
properties:

View File

@ -30,6 +30,7 @@ import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"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/filter"
"github.com/vmware/harbor/tests/apitests/apilib"
@ -77,6 +78,25 @@ type usrInfo struct {
}
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 {
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/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
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/reset", &ConfigAPI{}, "post:Reset")
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")

View File

@ -21,11 +21,15 @@ import (
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
"github.com/vmware/harbor/src/common/utils/log"
"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
type LdapAPI struct {
BaseController
ldapConfig *ldapUtils.Session
useTestConfig bool // Only used for unit test
}
const (
@ -47,6 +51,14 @@ func (l *LdapAPI) Prepare() {
l.HandleForbidden(l.SecurityCtx.GetUsername())
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 ...
@ -55,16 +67,11 @@ func (l *LdapAPI) Ping() {
LdapConnectionTimeout: 5,
}
var err error
var ldapSession *ldapUtils.Session
l.Ctx.Input.CopyBody(1 << 32)
if string(l.Ctx.Input.RequestBody) == "" {
ldapSession, err = ldapUtils.LoadSystemLdapConfig()
if err != nil {
l.HandleInternalServerError(fmt.Sprintf("Can't load system configuration, error: %v", err))
return
}
ldapSession := *l.ldapConfig
err = ldapSession.ConnectionTest()
} else {
l.DecodeJSONReqAndValidate(&ldapConfs)
@ -81,7 +88,7 @@ func (l *LdapAPI) Ping() {
func (l *LdapAPI) Search() {
var err error
var ldapUsers []models.LdapUser
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
ldapSession := *l.ldapConfig
if err = ldapSession.Open(); err != nil {
l.HandleInternalServerError(fmt.Sprintf("Can't Open LDAP session, error: %v", err))
return
@ -106,11 +113,10 @@ func (l *LdapAPI) Search() {
func (l *LdapAPI) ImportUser() {
var ldapImportUsers models.LdapImportUser
var ldapFailedImportUsers []models.LdapFailedImportUser
var ldapConfs models.LdapConf
l.DecodeJSONReqAndValidate(&ldapImportUsers)
ldapFailedImportUsers, err := importUsers(ldapConfs, ldapImportUsers.LdapUIDList)
ldapFailedImportUsers, err := importUsers(ldapImportUsers.LdapUIDList, l.ldapConfig)
if err != nil {
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 u models.LdapFailedImportUser
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
if err != nil {
log.Errorf("Can't load system configuration, error: %v", err)
return nil, err
}
if err = ldapSession.Open(); err != nil {
ldapSession := *ldapConfig
if err := ldapSession.Open(); err != nil {
log.Errorf("Can't connect to LDAP, error: %v", err)
}
defer ldapSession.Close()
@ -194,19 +195,37 @@ func importUsers(ldapConfs models.LdapConf, ldapImportUsers []string) ([]models.
// SearchGroup ... Search LDAP by groupname
func (l *LdapAPI) SearchGroup() {
var ldapGroups []models.LdapGroup
var err error
searchName := l.GetString("groupname")
ldapSession, err := ldapUtils.LoadSystemLdapConfig()
if err != nil {
l.HandleInternalServerError(fmt.Sprintf("Can't get LDAP system config, error: %v", err))
return
}
groupDN := l.GetString("groupdn")
ldapSession := *l.ldapConfig
ldapSession.Open()
defer ldapSession.Close()
ldapGroups, err := ldapSession.SearchGroupByName(searchName)
//Search LDAP group by groupName or group DN
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
}
l.Data["json"] = ldapGroups
l.ServeJSON()
}

136
src/ui/api/ldap_test.go Normal file
View 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...)
}