diff --git a/src/common/dao/user.go b/src/common/dao/user.go index 2ce881ad5..57d8ab693 100644 --- a/src/common/dao/user.go +++ b/src/common/dao/user.go @@ -257,7 +257,9 @@ func DeleteUser(userID int) error { return err } -// ChangeUserProfile ... +// ChangeUserProfile - Update user in local db, +// cols to specify the columns need to update, +// Email, and RealName, Comment are updated by default. func ChangeUserProfile(user models.User, cols ...string) error { o := GetOrmer() if len(cols) == 0 { diff --git a/src/common/models/config.go b/src/common/models/config.go index 42caf3be8..a4a8b8edd 100644 --- a/src/common/models/config.go +++ b/src/common/models/config.go @@ -23,19 +23,6 @@ type Authentication struct { } */ -// LDAP ... -type LDAP struct { - URL string `json:"url"` - SearchDN string `json:"search_dn"` - SearchPassword string `json:"search_password"` - BaseDN string `json:"base_dn"` - Filter string `json:"filter"` - UID string `json:"uid"` - Scope int `json:"scope"` - Timeout int `json:"timeout"` // in second - VerifyCert bool `json:"verify_cert"` -} - // Database ... type Database struct { Type string `json:"type"` @@ -59,8 +46,8 @@ type SQLite struct { // PostGreSQL ... type PostGreSQL struct { - Host string `json:"host"` - Port int `json:"port"` + Host string `json:"host"` + Port int `json:"port"` Username string `json:"username"` Password string `json:"password,omitempty"` Database string `json:"database"` @@ -110,11 +97,12 @@ type SystemCfg struct { // ConfigEntry ... type ConfigEntry struct { - ID int64 `orm:"pk;auto;column(id)" json:"-"` - Key string `orm:"column(k)" json:"k"` + ID int64 `orm:"pk;auto;column(id)" json:"-"` + Key string `orm:"column(k)" json:"k"` Value string `orm:"column(v)" json:"v"` } + // TableName ... -func (ce *ConfigEntry)TableName() string { +func (ce *ConfigEntry) TableName() string { return "properties" -} \ No newline at end of file +} diff --git a/src/common/utils/ldap/ldap.go b/src/common/utils/ldap/ldap.go index 85d75592f..77cf1b462 100644 --- a/src/common/utils/ldap/ldap.go +++ b/src/common/utils/ldap/ldap.go @@ -37,7 +37,6 @@ type Session struct { //LoadSystemLdapConfig - load LDAP configure from adminserver func LoadSystemLdapConfig() (*Session, error) { - var session Session authMode, err := config.AuthMode() if err != nil { @@ -49,87 +48,30 @@ func LoadSystemLdapConfig() (*Session, error) { return nil, fmt.Errorf("system auth_mode isn't ldap_auth, please check configuration") } - ldap, err := config.LDAP() + ldapConf, err := config.LDAPConf() if err != nil { return nil, err } - if ldap.URL == "" { - return nil, fmt.Errorf("can not get any available LDAP_URL") - } - - ldapURL, err := formatURL(ldap.URL) - if err != nil { - return nil, err - } - - session.ldapConfig.LdapURL = ldapURL - session.ldapConfig.LdapSearchDn = ldap.SearchDN - session.ldapConfig.LdapSearchPassword = ldap.SearchPassword - session.ldapConfig.LdapBaseDn = ldap.BaseDN - session.ldapConfig.LdapFilter = ldap.Filter - session.ldapConfig.LdapUID = ldap.UID - session.ldapConfig.LdapConnectionTimeout = ldap.Timeout - session.ldapConfig.LdapVerifyCert = ldap.VerifyCert - log.Debugf("Load system configuration: %v", ldap) - - switch ldap.Scope { - case 1: - session.ldapConfig.LdapScope = goldap.ScopeBaseObject - case 2: - session.ldapConfig.LdapScope = goldap.ScopeSingleLevel - case 3: - session.ldapConfig.LdapScope = goldap.ScopeWholeSubtree - default: - log.Errorf("invalid ldap search scope %v", ldap.Scope) - return nil, fmt.Errorf("invalid ldap search scope") - } - - return &session, nil + return CreateWithConfig(*ldapConf) } -// CreateWithUIConfig - create a Session with config from UI -func CreateWithUIConfig(ldapConfs models.LdapConf) (*Session, error) { - - switch ldapConfs.LdapScope { - case 1: - ldapConfs.LdapScope = goldap.ScopeBaseObject - case 2: - ldapConfs.LdapScope = goldap.ScopeSingleLevel - case 3: - ldapConfs.LdapScope = goldap.ScopeWholeSubtree - default: - return nil, fmt.Errorf("invalid ldap search scope") - } - - return createWithInternalConfig(ldapConfs) -} - -// createWithInternalConfig - create a Session with internal config -func createWithInternalConfig(ldapConfs models.LdapConf) (*Session, error) { - +// CreateWithConfig - create a Session with internal config +func CreateWithConfig(ldapConf models.LdapConf) (*Session, error) { var session Session - if ldapConfs.LdapURL == "" { + if ldapConf.LdapURL == "" { return nil, fmt.Errorf("can not get any available LDAP_URL") } - ldapURL, err := formatURL(ldapConfs.LdapURL) + ldapURL, err := formatURL(ldapConf.LdapURL) if err != nil { return nil, err } - session.ldapConfig.LdapURL = ldapURL - session.ldapConfig.LdapSearchDn = ldapConfs.LdapSearchDn - session.ldapConfig.LdapSearchPassword = ldapConfs.LdapSearchPassword - session.ldapConfig.LdapBaseDn = ldapConfs.LdapBaseDn - session.ldapConfig.LdapFilter = ldapConfs.LdapFilter - session.ldapConfig.LdapUID = ldapConfs.LdapUID - session.ldapConfig.LdapConnectionTimeout = ldapConfs.LdapConnectionTimeout - session.ldapConfig.LdapVerifyCert = ldapConfs.LdapVerifyCert - session.ldapConfig.LdapScope = ldapConfs.LdapScope + ldapConf.LdapURL = ldapURL + session.ldapConfig = ldapConf return &session, nil - } func formatURL(ldapURL string) (string, error) { @@ -208,7 +150,7 @@ func ConnectionTestWithConfig(ldapConfig models.LdapConf) error { ldapConfig.LdapSearchPassword = session.ldapConfig.LdapSearchPassword } - testSession, err := createWithInternalConfig(ldapConfig) + testSession, err := CreateWithConfig(ldapConfig) if err != nil { return err diff --git a/src/common/utils/ldap/ldap_test.go b/src/common/utils/ldap/ldap_test.go index f1320b4d2..7f242d548 100644 --- a/src/common/utils/ldap/ldap_test.go +++ b/src/common/utils/ldap/ldap_test.go @@ -138,10 +138,6 @@ func TestLoadSystemLdapConfig(t *testing.T) { t.Errorf("unexpected LdapURL: %s != %s", session.ldapConfig.LdapURL, "ldap://127.0.0.1:389") } - if session.ldapConfig.LdapScope != 2 { - t.Errorf("unexpected LdapScope: %d != %d", session.ldapConfig.LdapScope, 2) - } - } func TestConnectTest(t *testing.T) { @@ -156,7 +152,7 @@ func TestConnectTest(t *testing.T) { } -func TestCreateUIConfig(t *testing.T) { +func TestCreateWithConfig(t *testing.T) { var testConfigs = []struct { config models.LdapConf internalValue int @@ -184,7 +180,7 @@ func TestCreateUIConfig(t *testing.T) { } for _, val := range testConfigs { - session, err := CreateWithUIConfig(val.config) + _, err := CreateWithConfig(val.config) if val.internalValue < 0 { if err == nil { t.Fatalf("Should have error with url :%v", val.config) @@ -194,9 +190,6 @@ func TestCreateUIConfig(t *testing.T) { if err != nil { t.Fatalf("Can not create with ui config, err:%v", err) } - if session.ldapConfig.LdapScope != val.internalValue { - t.Fatalf("Test failed expected %v, actual %v", val.internalValue, session.ldapConfig.LdapScope) - } } } diff --git a/src/ui/api/config.go b/src/ui/api/config.go index d2e19bd08..8a6781fc5 100644 --- a/src/ui/api/config.go +++ b/src/ui/api/config.go @@ -245,28 +245,28 @@ func validateCfg(c map[string]interface{}) (bool, error) { } if mode == common.LDAPAuth { - ldap, err := config.LDAP() + ldapConf, err := config.LDAPConf() if err != nil { return true, err } - if len(ldap.URL) == 0 { + if len(ldapConf.LdapURL) == 0 { if _, ok := strMap[common.LDAPURL]; !ok { return false, fmt.Errorf("%s is missing", common.LDAPURL) } } - if len(ldap.BaseDN) == 0 { + if len(ldapConf.LdapBaseDn) == 0 { if _, ok := strMap[common.LDAPBaseDN]; !ok { return false, fmt.Errorf("%s is missing", common.LDAPBaseDN) } } - if len(ldap.UID) == 0 { + if len(ldapConf.LdapUID) == 0 { if _, ok := strMap[common.LDAPUID]; !ok { return false, fmt.Errorf("%s is missing", common.LDAPUID) } } - if ldap.Scope == 0 { + if ldapConf.LdapScope == 0 { if _, ok := numMap[common.LDAPScope]; !ok { return false, fmt.Errorf("%s is missing", common.LDAPScope) } diff --git a/src/ui/api/ldap.go b/src/ui/api/ldap.go index 42ffb058c..e39b491ed 100644 --- a/src/ui/api/ldap.go +++ b/src/ui/api/ldap.go @@ -94,7 +94,7 @@ func (l *LdapAPI) Search() { } } else { l.DecodeJSONReqAndValidate(&ldapConfs) - ldapSession, err = ldapUtils.CreateWithUIConfig(ldapConfs) + ldapSession, err = ldapUtils.CreateWithConfig(ldapConfs) } if err = ldapSession.Open(); err != nil { diff --git a/src/ui/auth/auth_test.go b/src/ui/auth/auth_test.go index c4faa0b9b..126145fcf 100644 --- a/src/ui/auth/auth_test.go +++ b/src/ui/auth/auth_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" ) var l = NewUserLock(2 * time.Second) @@ -61,3 +62,21 @@ func TestLock(t *testing.T) { t.Errorf("daniel has never been locked, he should not be locked") } } + +func TestDefaultAuthenticate(t *testing.T) { + authHelper := DefaultAuthenticateHelper{} + m := models.AuthModel{} + user, err := authHelper.Authenticate(m) + if user != nil || err != nil { + t.Fatal("Default implementation should return nil") + } +} + +func TestDefaultOnBoardUser(t *testing.T) { + user := &models.User{} + authHelper := DefaultAuthenticateHelper{} + err := authHelper.OnBoardUser(user) + if err != nil { + t.Fatal("Default implementation should return nil") + } +} diff --git a/src/ui/auth/authenticator.go b/src/ui/auth/authenticator.go index eb9da0d06..4d3474b18 100644 --- a/src/ui/auth/authenticator.go +++ b/src/ui/auth/authenticator.go @@ -128,6 +128,7 @@ func getHelper() (AuthenticateHelper, error) { // OnBoardUser will check if a user exists in user table, if not insert the user and // put the id in the pointer of user model, if it does exist, return the user's profile. func OnBoardUser(user *models.User) error { + log.Debugf("OnBoardUser, user %+v", user) helper, err := getHelper() if err != nil { return err diff --git a/src/ui/auth/ldap/ldap.go b/src/ui/auth/ldap/ldap.go index 8ca332a34..e4dd938bd 100644 --- a/src/ui/auth/ldap/ldap.go +++ b/src/ui/auth/ldap/ldap.go @@ -76,11 +76,11 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { dn := ldapUsers[0].DN - log.Debugf("username: %s, dn: %s", u.Username, dn) if err = ldapSession.Bind(dn, m.Password); err != nil { log.Warningf("Failed to bind user, username: %s, dn: %s, error: %v", u.Username, dn, err) return nil, nil } + return &u, nil } @@ -120,6 +120,7 @@ func (l *Auth) SearchUser(username string) (*models.User, error) { user.Username = strings.TrimSpace(ldapUsers[0].Username) user.Realname = strings.TrimSpace(ldapUsers[0].Realname) + user.Email = strings.TrimSpace(ldapUsers[0].Email) log.Debugf("Found ldap user %v", user) } else { diff --git a/src/ui/auth/ldap/ldap_test.go b/src/ui/auth/ldap/ldap_test.go index 909e13d2b..1e2927dfb 100644 --- a/src/ui/auth/ldap/ldap_test.go +++ b/src/ui/auth/ldap/ldap_test.go @@ -14,12 +14,12 @@ package ldap import ( + "github.com/stretchr/testify/assert" //"fmt" //"strings" "os" "testing" - "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" @@ -136,6 +136,21 @@ func TestAuthenticate(t *testing.T) { if user != nil { t.Errorf("Nil user for empty credentials") } + + //authenticate the second time + person2 := models.AuthModel{ + Principal: "test", + Password: "123456", + } + user2, err := auth.Authenticate(person2) + + if err != nil { + t.Errorf("unexpected ldap error: %v", err) + } + + if user2 == nil { + t.Errorf("Can not login user with person2 %+v", person2) + } } func TestSearchUser(t *testing.T) { @@ -174,6 +189,43 @@ func TestOnboardUser(t *testing.T) { if user.UserID <= 0 { t.Errorf("Failed to onboard user") } + assert.Equal(t, "sample@example.com", user.Email) +} + +func TestOnboardUser_02(t *testing.T) { + user := &models.User{ + Username: "sample02", + Realname: "Sample02", + } + var auth *Auth + err := auth.OnBoardUser(user) + if err != nil { + t.Errorf("Failed to onboard user") + } + if user.UserID <= 0 { + t.Errorf("Failed to onboard user") + } + + assert.Equal(t, "sample02@placeholder.com", user.Email) + dao.CleanUser(int64(user.UserID)) +} + +func TestOnboardUser_03(t *testing.T) { + user := &models.User{ + Username: "sample03@example.com", + Realname: "Sample03", + } + var auth *Auth + err := auth.OnBoardUser(user) + if err != nil { + t.Errorf("Failed to onboard user") + } + if user.UserID <= 0 { + t.Errorf("Failed to onboard user") + } + + assert.Equal(t, "sample03@example.com", user.Email) + dao.CleanUser(int64(user.UserID)) } func TestAuthenticateHelperOnboardUser(t *testing.T) { diff --git a/src/ui/config/config.go b/src/ui/config/config.go index d651aa35c..c3d36e331 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -181,28 +181,28 @@ func AuthMode() (string, error) { return cfg[common.AUTHMode].(string), nil } -// LDAP returns the setting of ldap server -func LDAP() (*models.LDAP, error) { +// LDAPConf returns the setting of ldap server +func LDAPConf() (*models.LdapConf, error) { cfg, err := mg.Get() if err != nil { return nil, err } - ldap := &models.LDAP{} - ldap.URL = cfg[common.LDAPURL].(string) - ldap.SearchDN = cfg[common.LDAPSearchDN].(string) - ldap.SearchPassword = cfg[common.LDAPSearchPwd].(string) - ldap.BaseDN = cfg[common.LDAPBaseDN].(string) - ldap.UID = cfg[common.LDAPUID].(string) - ldap.Filter = cfg[common.LDAPFilter].(string) - ldap.Scope = int(cfg[common.LDAPScope].(float64)) - ldap.Timeout = int(cfg[common.LDAPTimeout].(float64)) + ldapConf := &models.LdapConf{} + ldapConf.LdapURL = cfg[common.LDAPURL].(string) + ldapConf.LdapSearchDn = cfg[common.LDAPSearchDN].(string) + ldapConf.LdapSearchPassword = cfg[common.LDAPSearchPwd].(string) + ldapConf.LdapBaseDn = cfg[common.LDAPBaseDN].(string) + ldapConf.LdapUID = cfg[common.LDAPUID].(string) + ldapConf.LdapFilter = cfg[common.LDAPFilter].(string) + ldapConf.LdapScope = int(cfg[common.LDAPScope].(float64)) + ldapConf.LdapConnectionTimeout = int(cfg[common.LDAPTimeout].(float64)) if cfg[common.LDAPVerifyCert] != nil { - ldap.VerifyCert = cfg[common.LDAPVerifyCert].(bool) + ldapConf.LdapVerifyCert = cfg[common.LDAPVerifyCert].(bool) } else { - ldap.VerifyCert = true + ldapConf.LdapVerifyCert = true } - return ldap, nil + return ldapConf, nil } // TokenExpiration returns the token expiration time (in minute) @@ -393,7 +393,7 @@ func ClairEndpoint() string { } // ClairDB return Clair db info -func ClairDB() (*models.PostGreSQL, error){ +func ClairDB() (*models.PostGreSQL, error) { cfg, err := mg.Get() if err != nil { log.Errorf("Failed to get configuration of Clair DB, Error detail %v", err) @@ -407,6 +407,7 @@ func ClairDB() (*models.PostGreSQL, error){ clairDB.Database = cfg[common.ClairDB].(string) return clairDB, nil } + // AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string. func AdmiralEndpoint() string { cfg, err := mg.Get() diff --git a/src/ui/config/config_test.go b/src/ui/config/config_test.go index 033dbc01b..aa1508adb 100644 --- a/src/ui/config/config_test.go +++ b/src/ui/config/config_test.go @@ -18,8 +18,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/utils/test" ) // test functions under package ui/config @@ -71,7 +71,7 @@ func TestConfig(t *testing.T) { t.Errorf("unexpected mode: %s != %s", mode, "db_auth") } - if _, err := LDAP(); err != nil { + if _, err := LDAPConf(); err != nil { t.Fatalf("failed to get ldap settings: %v", err) } @@ -119,17 +119,17 @@ func TestConfig(t *testing.T) { t.Fatalf("failed to get database: %v", err) } - clairDB, err := ClairDB(); + clairDB, err := ClairDB() if err != nil { t.Fatalf("failed to get clair DB %v", err) } adminServerDefaultConfig := test.GetDefaultConfigMap() - assert.Equal(adminServerDefaultConfig[common.ClairDB],clairDB.Database) - assert.Equal(adminServerDefaultConfig[common.ClairDBUsername],clairDB.Username) - assert.Equal(adminServerDefaultConfig[common.ClairDBPassword],clairDB.Password) + assert.Equal(adminServerDefaultConfig[common.ClairDB], clairDB.Database) + assert.Equal(adminServerDefaultConfig[common.ClairDBUsername], clairDB.Username) + assert.Equal(adminServerDefaultConfig[common.ClairDBPassword], clairDB.Password) assert.Equal(adminServerDefaultConfig[common.ClairDBHost], clairDB.Host) assert.Equal(adminServerDefaultConfig[common.ClairDBPort], clairDB.Port) - + if InternalNotaryEndpoint() != "http://notary-server:4443" { t.Errorf("Unexpected notary endpoint: %s", InternalNotaryEndpoint()) } diff --git a/src/ui_ng/src/app/config/auth/config-auth.component.html b/src/ui_ng/src/app/config/auth/config-auth.component.html index a3329ef8f..8a20c9ec4 100644 --- a/src/ui_ng/src/app/config/auth/config-auth.component.html +++ b/src/ui_ng/src/app/config/auth/config-auth.component.html @@ -126,9 +126,9 @@
diff --git a/tests/resources/Harbor-Pages/Administration-Users.robot b/tests/resources/Harbor-Pages/Administration-Users.robot index c42170c0b..a2284b129 100644 --- a/tests/resources/Harbor-Pages/Administration-Users.robot +++ b/tests/resources/Harbor-Pages/Administration-Users.robot @@ -37,3 +37,9 @@ Switch to User Tag Administration Tag Should Display Page Should Contain Element xpath=${administration_tag_xpath} + +User Email Should Exist + [Arguments] ${email} + Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} + Switch to User Tag + Page Should Contain Element xpath=//clr-dg-cell[contains(., '${email}')] \ No newline at end of file diff --git a/tests/resources/Harbor-Pages/Project-Members.robot b/tests/resources/Harbor-Pages/Project-Members.robot index 419861ee8..a2671506c 100644 --- a/tests/resources/Harbor-Pages/Project-Members.robot +++ b/tests/resources/Harbor-Pages/Project-Members.robot @@ -180,3 +180,11 @@ User Should Be Admin Page Should Contain Element xpath=//clr-dg-row[contains(.,'${user}')]//clr-dg-cell[contains(.,'Admin')] Logout Harbor Push Image With Tag ${ip} ${user} ${pwd} ${project} hello-world ${ip}/${project}/hello-world:v2 + +Project Should Have Member + [Arguments] ${project} ${user} + Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD} + Go Into Project ${project} + Switch To Member + Page Should Contain Element xpath=//clr-dg-cell[contains(., '${user}')] + Logout Harbor diff --git a/tests/robot-cases/Group0-BAT/BAT.robot b/tests/robot-cases/Group0-BAT/BAT.robot index 9d51d091b..f064b3855 100644 --- a/tests/robot-cases/Group0-BAT/BAT.robot +++ b/tests/robot-cases/Group0-BAT/BAT.robot @@ -353,6 +353,8 @@ Test Case - Ldap User Create Project Create An New Project project${d} Logout Harbor Manage Project Member %{HARBOR_ADMIN} %{HARBOR_PASSWORD} project${d} mike02 Add + Project Should Have Member project${d} mike02 + User Email Should Exist mike02@example.com Close Browser Test Case - Ldap User Push An Image