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. 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:

View File

@ -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")

View File

@ -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,19 +195,37 @@ 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)
//Search LDAP group by groupName or group DN
if len(searchName) > 0 {
ldapGroups, err = ldapSession.SearchGroupByName(searchName)
if err != nil { if err != nil {
l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err)) l.HandleInternalServerError(fmt.Sprintf("Can't search LDAP group by name, error: %v", err))
return 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.Data["json"] = ldapGroups
l.ServeJSON() 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...)
}