Remove GetUser and Onboard from common/dao

Replaced by funcs in src/pkg/user and src/controller/user

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2021-05-14 21:02:05 +08:00
parent 0a8ff4c1f9
commit 952644e23f
20 changed files with 241 additions and 379 deletions

View File

@ -155,30 +155,13 @@ func clearAll() {
var currentUser *models.User
func TestGetUser(t *testing.T) {
queryUser := models.User{
Username: username,
Email: "tester01@vmware.com",
}
var err error
currentUser, err = GetUser(queryUser)
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
if currentUser == nil {
t.Errorf("No user found queried by user query: %+v", queryUser)
}
if currentUser.Email != "tester01@vmware.com" {
t.Errorf("the user's email does not match, expected: tester01@vmware.com, actual: %s", currentUser.Email)
}
queryUser = models.User{}
_, err = GetUser(queryUser)
assert.NotNil(t, err)
}
func TestAddProject(t *testing.T) {
ctx := libOrm.Context()
var err error
currentUser, err = user.Mgr.GetByName(ctx, username)
if err != nil {
t.Errorf("Failed to get user by username: %s, error: %v", username, err)
}
project := models.Project{
OwnerID: currentUser.UserID,
Name: projectName,
@ -186,7 +169,7 @@ func TestAddProject(t *testing.T) {
OwnerName: currentUser.Username,
}
_, err := AddProject(project)
_, err = AddProject(project)
if err != nil {
t.Errorf("Error occurred in AddProject: %v", err)
}

View File

@ -135,6 +135,12 @@ func ExecuteBatchSQL(sqls []string) {
}
}
// CleanUser - Clean this user information from DB, this is a shortcut for UT.
func CleanUser(id int64) error {
_, err := GetOrmer().QueryTable(&models.User{}).Filter("UserID", id).Delete()
return err
}
// ArrayEqual ...
func ArrayEqual(arrayA, arrayB []int) bool {
if len(arrayA) != len(arrayB) {

View File

@ -1,106 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dao
import (
"fmt"
"github.com/goharbor/harbor/src/common/models"
)
// GetUser ...
func GetUser(query models.User) (*models.User, error) {
o := GetOrmer()
sql := `select user_id, username, password, password_version, email, realname, comment, reset_uuid, salt,
sysadmin_flag, creation_time, update_time
from harbor_user u
where deleted = false `
queryParam := make([]interface{}, 1)
if query.UserID != 0 {
sql += ` and user_id = ? `
queryParam = append(queryParam, query.UserID)
}
if query.Username != "" {
sql += ` and username = ? `
queryParam = append(queryParam, query.Username)
}
if query.ResetUUID != "" {
sql += ` and reset_uuid = ? `
queryParam = append(queryParam, query.ResetUUID)
}
if query.Email != "" {
sql += ` and email = ? `
queryParam = append(queryParam, query.Email)
}
var u []models.User
n, err := o.Raw(sql, queryParam).QueryRows(&u)
if err != nil {
return nil, err
}
if n == 0 {
return nil, nil
}
if n > 1 {
return nil, fmt.Errorf("got more than one user when executing: %s param: %v", sql, queryParam)
}
return &u[0], nil
}
// 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.
// This is used for ldap and uaa authentication, such the user can have an ID in Harbor.
func OnBoardUser(u *models.User) error {
o := GetOrmer()
created, id, err := o.ReadOrCreate(u, "Username")
if err != nil {
return err
}
if created {
u.UserID = int(id)
// current orm framework doesn't support to fetch a pointer or sql.NullString with QueryRow
// https://github.com/astaxie/beego/issues/3767
if len(u.Email) == 0 {
_, err = o.Raw("update harbor_user set email = null where user_id = ? ", id).Exec()
if err != nil {
return err
}
}
} else {
existing, err := GetUser(*u)
if err != nil {
return err
}
u.Email = existing.Email
u.SysAdminFlag = existing.SysAdminFlag
u.Realname = existing.Realname
u.UserID = existing.UserID
}
return nil
}
// CleanUser - Clean this user information from DB
func CleanUser(id int64) error {
_, err := GetOrmer().QueryTable(&models.User{}).Filter("UserID", id).Delete()
return err
}

View File

@ -1,60 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dao
import (
"testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/stretchr/testify/assert"
)
func TestOnBoardUser(t *testing.T) {
assert := assert.New(t)
u := &models.User{
Username: "user1",
Password: "password1",
Email: "dummy@placehodler.com",
Realname: "daniel",
}
err := OnBoardUser(u)
assert.Nil(err)
id := u.UserID
assert.True(id > 0)
err = OnBoardUser(u)
assert.Nil(err)
assert.True(u.UserID == id)
CleanUser(int64(id))
}
func TestOnBoardUser_EmptyEmail(t *testing.T) {
assert := assert.New(t)
u := &models.User{
Username: "empty_email",
Password: "password1",
Realname: "empty_email",
}
err := OnBoardUser(u)
assert.Nil(err)
id := u.UserID
assert.True(id > 0)
err = OnBoardUser(u)
assert.Nil(err)
assert.True(u.UserID == id)
assert.Equal("", u.Email)
user, err := GetUser(models.User{Username: "empty_email"})
assert.Equal("", user.Email)
CleanUser(int64(id))
}

View File

@ -78,16 +78,13 @@ func InitDatabaseFromEnv() {
}
func updateUserInitialPassword(userID int, password string) error {
queryUser := models.User{UserID: userID}
user, err := dao.GetUser(queryUser)
ctx := orm.Context()
user, err := pkguser.Mgr.Get(ctx, userID)
if err != nil {
return fmt.Errorf("failed to get user, userID: %d %v", userID, err)
}
if user == nil {
return fmt.Errorf("user id: %d does not exist", userID)
}
if user.Salt == "" {
err = pkguser.Mgr.UpdatePassword(orm.Context(), userID, password)
err = pkguser.Mgr.UpdatePassword(ctx, userID, password)
if err != nil {
return fmt.Errorf("failed to update user encrypted password, userID: %d, err: %v", userID, err)
}

View File

@ -67,7 +67,6 @@ func NewErrAuth(msg string) ErrAuth {
// AuthenticateHelper provides interface for user management in different auth modes.
type AuthenticateHelper interface {
// Authenticate authenticate the user based on data in m. Only when the error returned is an instance
// of ErrAuth, it will be considered a bad credentials, other errors will be treated as server side error.
Authenticate(m models.AuthModel) (*models.User, error)

View File

@ -29,16 +29,17 @@ import (
"github.com/goharbor/harbor/src/jobservice/logger"
"github.com/goharbor/harbor/src/lib/config"
cfgModels "github.com/goharbor/harbor/src/lib/config/models"
harborErrors "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/controller/usergroup"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/authproxy"
"github.com/goharbor/harbor/src/pkg/user"
)
const refreshDuration = 2 * time.Second
@ -58,6 +59,7 @@ type Auth struct {
SkipSearch bool
settingTimeStamp time.Time
client *http.Client
userMgr user.Manager
}
type session struct {
@ -136,18 +138,22 @@ func (a *Auth) VerifyToken(token string) (*models.User, error) {
// OnBoardUser delegates to dao pkg to insert/update data in DB.
func (a *Auth) OnBoardUser(u *models.User) error {
return dao.OnBoardUser(u)
return a.userMgr.Onboard(orm.Context(), u)
}
// PostAuthenticate generates the user model and on board the user.
func (a *Auth) PostAuthenticate(u *models.User) error {
if res, _ := dao.GetUser(*u); res != nil {
return nil
}
if err := a.fillInModel(u); err != nil {
_, err := a.userMgr.GetByName(orm.Context(), u.Username)
if harborErrors.IsNotFoundErr(err) {
if err2 := a.fillInModel(u); err2 != nil {
return err2
}
return a.OnBoardUser(u)
} else if err != nil {
return err
}
return a.OnBoardUser(u)
// do nothing if user exists in DB
return nil
}
// SearchUser returns nil as authproxy does not have such capability.
@ -250,5 +256,7 @@ func getTLSConfig(setting *cfgModels.HTTPAuthProxy) (*tls.Config, error) {
}
func init() {
auth.Register(common.HTTPAuth, &Auth{})
auth.Register(common.HTTPAuth, &Auth{
userMgr: user.New(),
})
}

View File

@ -15,6 +15,10 @@
package authproxy
import (
"net/http/httptest"
"os"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
@ -25,12 +29,10 @@ import (
cfgModels "github.com/goharbor/harbor/src/lib/config/models"
"github.com/goharbor/harbor/src/lib/orm"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
"github.com/goharbor/harbor/src/pkg/user"
"github.com/goharbor/harbor/src/pkg/usergroup"
"github.com/goharbor/harbor/src/pkg/usergroup/model"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"os"
"testing"
)
var mockSvr *httptest.Server
@ -48,6 +50,7 @@ func TestMain(m *testing.M) {
a = &Auth{
Endpoint: mockSvr.URL + "/test/login",
TokenReviewEndpoint: mockSvr.URL + "/test/tokenreview",
userMgr: user.New(),
}
cfgMap := cut.GetUnitTestConfig()
conf := map[string]interface{}{

View File

@ -16,9 +16,9 @@ package db
import (
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/user"
)
@ -26,11 +26,12 @@ import (
// Auth implements Authenticator interface to authenticate user against DB.
type Auth struct {
auth.DefaultAuthenticateHelper
userMgr user.Manager
}
// Authenticate calls dao to authenticate user.
func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
u, err := user.Mgr.MatchLocalPassword(orm.Context(), m.Principal, m.Password)
u, err := d.userMgr.MatchLocalPassword(orm.Context(), m.Principal, m.Password)
if err != nil {
return nil, err
}
@ -42,11 +43,13 @@ func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
// SearchUser - Check if user exist in local db
func (d *Auth) SearchUser(username string) (*models.User, error) {
var queryCondition = models.User{
Username: username,
u, err := d.userMgr.GetByName(orm.Context(), username)
if errors.IsNotFoundErr(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return dao.GetUser(queryCondition)
return u, err
}
// OnBoardUser -
@ -55,5 +58,7 @@ func (d *Auth) OnBoardUser(u *models.User) error {
}
func init() {
auth.Register(common.DBAuth, &Auth{})
auth.Register(common.DBAuth, &Auth{
userMgr: user.New(),
})
}

View File

@ -14,76 +14,41 @@
package db
import (
"github.com/goharbor/harbor/src/lib/config"
_ "github.com/goharbor/harbor/src/pkg/config/db"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
"log"
"os"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/auth"
)
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/testing/mock"
testinguserpkg "github.com/goharbor/harbor/src/testing/pkg/user"
var testConfig = map[string]interface{}{
common.ExtEndpoint: "host01.com",
common.AUTHMode: "db_auth",
common.DatabaseType: "postgresql",
common.PostGreSQLHOST: "127.0.0.1",
common.PostGreSQLPort: 5432,
common.PostGreSQLUsername: "postgres",
common.PostGreSQLPassword: "root123",
common.PostGreSQLDatabase: "registry",
common.LDAPURL: "ldap://127.0.0.1",
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
common.LDAPSearchPwd: "admin",
common.LDAPBaseDN: "dc=example,dc=com",
common.LDAPUID: "uid",
common.LDAPFilter: "",
common.LDAPScope: 3,
common.LDAPTimeout: 30,
common.AdminInitialPassword: "password",
}
testifymock "github.com/stretchr/testify/mock"
)
func TestMain(m *testing.M) {
test.InitDatabaseFromEnv()
secretKeyPath := "/tmp/secretkey"
_, err := test.GenerateKey(secretKeyPath)
if err != nil {
log.Fatalf("failed to generate secret key: %v", err)
return
}
defer os.Remove(secretKeyPath)
if err := os.Setenv("KEY_PATH", secretKeyPath); err != nil {
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
}
config.Init()
config.Upload(testConfig)
retCode := m.Run()
os.Exit(retCode)
}
func TestSearchUser(t *testing.T) {
// insert user first
user := &models.User{
UserID: 123,
Username: "existuser",
Email: "existuser@placeholder.com",
Realname: "Existing user",
}
err := dao.OnBoardUser(user)
if err != nil {
t.Fatalf("Failed to OnBoardUser %v", user)
mockUserMgr := &testinguserpkg.Manager{}
auth := &Auth{
userMgr: mockUserMgr,
}
var auth *Auth
mockUserMgr.On("GetByName", mock.Anything, testifymock.MatchedBy(
func(name string) bool {
return name == "existuser"
})).Return(user, nil)
newUser, err := auth.SearchUser("existuser")
if err != nil {
t.Fatalf("Failed to search user, error %v", err)
@ -91,31 +56,4 @@ func TestSearchUser(t *testing.T) {
if newUser == nil {
t.Fatalf("Failed to search user %v", newUser)
}
}
func TestAuthenticateHelperOnBoardUser(t *testing.T) {
user := models.User{
Username: "test01",
Realname: "test01",
Email: "test01@example.com",
}
err := auth.OnBoardUser(&user)
if err != nil {
t.Errorf("Failed to onboard user error: %v", err)
}
}
func TestAuthenticateHelperSearchUser(t *testing.T) {
user, err := auth.SearchUser("admin")
if err != nil {
t.Error("Failed to search user, admin")
}
if user == nil {
t.Error("Failed to search user admin")
}
}

View File

@ -21,6 +21,7 @@ import (
"strings"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/ldap/model"
@ -29,7 +30,6 @@ import (
goldap "github.com/go-ldap/ldap/v3"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/models"
@ -44,6 +44,7 @@ import (
// Auth implements AuthenticateHelper interface to authenticate against LDAP
type Auth struct {
auth.DefaultAuthenticateHelper
userMgr user.Manager
}
// Authenticate checks user's credential against LDAP based on basedn template and LDAP URL,
@ -92,7 +93,7 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
u.Realname = ldapUsers[0].Realname
u.Email = strings.TrimSpace(ldapUsers[0].Email)
l.syncUserInfoFromDB(&u)
l.syncUserInfoFromDB(ctx, &u)
l.attachLDAPGroup(ctx, ldapUsers, &u, ldapSession)
return &u, nil
@ -140,14 +141,13 @@ func (l *Auth) attachLDAPGroup(ctx context.Context, ldapUsers []model.User, u *m
}
}
func (l *Auth) syncUserInfoFromDB(u *models.User) {
func (l *Auth) syncUserInfoFromDB(ctx context.Context, u *models.User) {
// Retrieve SysAdminFlag from DB so that it transfer to session
dbUser, err := dao.GetUser(models.User{Username: u.Username})
if err != nil {
log.Errorf("failed to sync user info from DB error %v", err)
dbUser, err := l.userMgr.GetByName(ctx, u.Username)
if errors.IsNotFoundErr(err) {
return
}
if dbUser == nil {
} else if err != nil {
log.Errorf("failed to sync user info from DB error %v", err)
return
}
u.SysAdminFlag = dbUser.SysAdminFlag
@ -164,7 +164,7 @@ func (l *Auth) OnBoardUser(u *models.User) error {
u.Password = "12345678AbC" // Password is not kept in local db
u.Comment = "from LDAP." // Source is from LDAP
return dao.OnBoardUser(u)
return l.userMgr.Onboard(orm.Context(), u)
}
// SearchUser -- Search user in ldap
@ -259,31 +259,26 @@ func (l *Auth) PostAuthenticate(u *models.User) error {
ctx := orm.Context()
query := q.New(q.KeyWords{"Username": u.Username})
n, err := user.Mgr.Count(ctx, query)
n, err := l.userMgr.Count(ctx, query)
if err != nil {
return err
}
if n > 0 {
queryCondition := models.User{
Username: u.Username,
}
dbUser, err := dao.GetUser(queryCondition)
if err != nil {
return err
}
if dbUser == nil {
dbUser, err := l.userMgr.GetByName(ctx, u.Username)
if errors.IsNotFoundErr(err) {
fmt.Printf("User not found in DB %+v", u)
return nil
} else if err != nil {
return err
}
u.UserID = dbUser.UserID
if dbUser.Email != u.Email {
Re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
if !Re.MatchString(u.Email) {
log.Debugf("Not a valid email address: %v, skip to sync", u.Email)
} else {
if err = user.Mgr.UpdateProfile(ctx, u, "Email"); err != nil {
if err = l.userMgr.UpdateProfile(ctx, u, "Email"); err != nil {
u.Email = dbUser.Email
log.Errorf("failed to sync user email: %v", err)
}
@ -304,5 +299,7 @@ func (l *Auth) PostAuthenticate(u *models.User) error {
}
func init() {
auth.Register(common.LDAPAuth, &Auth{})
auth.Register(common.LDAPAuth, &Auth{
userMgr: user.New(),
})
}

View File

@ -21,6 +21,7 @@ import (
"github.com/goharbor/harbor/src/lib/orm"
_ "github.com/goharbor/harbor/src/pkg/config/db"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
userpkg "github.com/goharbor/harbor/src/pkg/user"
"github.com/goharbor/harbor/src/pkg/usergroup"
ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model"
"github.com/stretchr/testify/assert"
@ -62,6 +63,8 @@ var ldapTestConfig = map[string]interface{}{
common.LDAPGroupAdminDn: "cn=harbor_users,ou=groups,dc=example,dc=com",
}
var authHelper *Auth
func TestMain(m *testing.M) {
test.InitDatabaseFromEnv()
config.InitWithSettings(ldapTestConfig)
@ -78,6 +81,10 @@ func TestMain(m *testing.M) {
log.Fatalf("failed to set env %s: %v", "KEY_PATH", err)
}
authHelper = &Auth{
userMgr: userpkg.New(),
}
// Extract to test utils
initSqls := []string{
"insert into harbor_user (username, email, password, realname) values ('member_test_01', 'member_test_01@example.com', '123456', 'member_test_01')",
@ -104,7 +111,6 @@ func TestMain(m *testing.M) {
func TestAuthenticate(t *testing.T) {
var person models.AuthModel
var authHelper *Auth
person.Principal = "test"
person.Password = "123456"
user, err := authHelper.Authenticate(person)
@ -145,8 +151,7 @@ func TestAuthenticate(t *testing.T) {
func TestSearchUser(t *testing.T) {
var username = "test"
var auth *Auth
user, err := auth.SearchUser(username)
user, err := authHelper.SearchUser(username)
if err != nil {
t.Errorf("Search user failed %v", err)
}
@ -156,7 +161,6 @@ func TestSearchUser(t *testing.T) {
}
func TestAuthenticateWithAdmin(t *testing.T) {
var person models.AuthModel
var authHelper *Auth
person.Principal = "mike"
person.Password = "zhu88jie"
user, err := authHelper.Authenticate(person)
@ -172,7 +176,6 @@ func TestAuthenticateWithAdmin(t *testing.T) {
}
func TestAuthenticateWithoutAdmin(t *testing.T) {
var person models.AuthModel
var authHelper *Auth
person.Principal = "user001"
person.Password = "Test1@34"
user, err := authHelper.Authenticate(person)
@ -188,8 +191,7 @@ func TestAuthenticateWithoutAdmin(t *testing.T) {
}
func TestSearchUser_02(t *testing.T) {
var username = "nonexist"
var auth *Auth
user, _ := auth.SearchUser(username)
user, _ := authHelper.SearchUser(username)
if user != nil {
t.Errorf("Should failed to search nonexist user")
}
@ -202,9 +204,7 @@ func TestOnBoardUser(t *testing.T) {
Email: "sample@example.com",
Realname: "Sample",
}
var auth *Auth
err := auth.OnBoardUser(user)
err := authHelper.OnBoardUser(user)
if err != nil {
t.Errorf("Failed to onboard user")
}
@ -219,8 +219,7 @@ func TestOnBoardUser_02(t *testing.T) {
Username: "sample02",
Realname: "Sample02",
}
var auth *Auth
err := auth.OnBoardUser(user)
err := authHelper.OnBoardUser(user)
if err != nil {
t.Errorf("Failed to onboard user")
}
@ -237,8 +236,7 @@ func TestOnBoardUser_03(t *testing.T) {
Username: "sample03@example.com",
Realname: "Sample03",
}
var auth *Auth
err := auth.OnBoardUser(user)
err := authHelper.OnBoardUser(user)
if err != nil {
t.Errorf("Failed to onboard user")
}
@ -304,10 +302,7 @@ func TestPostAuthentication(t *testing.T) {
Realname: "test003",
}
queryCondition := models.User{
Username: "test003",
Realname: "test003",
}
queryUsername := "test003"
err := auth.OnBoardUser(user1)
assert.Nil(err)
@ -318,8 +313,8 @@ func TestPostAuthentication(t *testing.T) {
}
auth.PostAuthenticate(user2)
dbUser, err := dao.GetUser(queryCondition)
ctx := orm.Context()
dbUser, err := userpkg.Mgr.GetByName(ctx, queryUsername)
if err != nil {
t.Fatalf("Failed to get user, error %v", err)
}
@ -330,7 +325,7 @@ func TestPostAuthentication(t *testing.T) {
}
auth.PostAuthenticate(user3)
dbUser, err = dao.GetUser(queryCondition)
dbUser, err = userpkg.Mgr.GetByName(ctx, queryUsername)
if err != nil {
t.Fatalf("Failed to get user, error %v", err)
}
@ -342,8 +337,7 @@ func TestPostAuthentication(t *testing.T) {
}
auth.PostAuthenticate(user4)
dbUser, err = dao.GetUser(queryCondition)
dbUser, err = userpkg.Mgr.GetByName(ctx, queryUsername)
if err != nil {
t.Fatalf("Failed to get user, error %v", err)
}

View File

@ -21,11 +21,11 @@ import (
"sync"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/uaa"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/orm"
userpkg "github.com/goharbor/harbor/src/pkg/user"
@ -36,6 +36,7 @@ type Auth struct {
sync.Mutex
client uaa.Client
auth.DefaultAuthenticateHelper
userMgr userpkg.Manager
}
// Authenticate ...
@ -72,7 +73,7 @@ func (u *Auth) OnBoardUser(user *models.User) error {
}
fillEmailRealName(user)
user.Comment = "From UAA"
return dao.OnBoardUser(user)
return u.userMgr.Onboard(orm.Context(), user)
}
func fillEmailRealName(user *models.User) {
@ -86,17 +87,17 @@ func fillEmailRealName(user *models.User) {
// PostAuthenticate will check if user exists in DB, if not on Board user, if he does, update the profile.
func (u *Auth) PostAuthenticate(user *models.User) error {
dbUser, err := dao.GetUser(models.User{Username: user.Username})
if err != nil {
return err
}
if dbUser == nil {
ctx := orm.Context()
dbUser, err := u.userMgr.GetByName(ctx, user.Username)
if errors.IsNotFoundErr(err) {
return u.OnBoardUser(user)
} else if err != nil {
return err
}
user.UserID = dbUser.UserID
user.SysAdminFlag = dbUser.SysAdminFlag
fillEmailRealName(user)
if err2 := userpkg.Mgr.UpdateProfile(orm.Context(), user, "Email", "Realname"); err2 != nil {
if err2 := u.userMgr.UpdateProfile(ctx, user, "Email", "Realname"); err2 != nil {
log.Warningf("Failed to update user profile, user: %s, error: %v", user.Username, err2)
}
return nil
@ -158,5 +159,7 @@ func (u *Auth) ensureClient() error {
return nil
}
func init() {
auth.Register(common.UAAAuth, &Auth{})
auth.Register(common.UAAAuth, &Auth{
userMgr: userpkg.New(),
})
}

View File

@ -15,10 +15,10 @@
package uaa
import (
"github.com/goharbor/harbor/src/lib/config"
"os"
"testing"
"github.com/goharbor/harbor/src/lib/orm"
_ "github.com/goharbor/harbor/src/pkg/config/db"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
@ -26,6 +26,8 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils/test"
"github.com/goharbor/harbor/src/common/utils/uaa"
"github.com/goharbor/harbor/src/lib/config"
userpkg "github.com/goharbor/harbor/src/pkg/user"
"github.com/stretchr/testify/assert"
)
@ -68,7 +70,10 @@ func TestAuthenticate(t *testing.T) {
Username: "user1",
Password: "password1",
}
auth := Auth{client: client}
auth := Auth{
client: client,
userMgr: userpkg.New(),
}
m1 := models.AuthModel{
Principal: "user1",
Password: "password1",
@ -90,7 +95,10 @@ func TestAuthenticate(t *testing.T) {
func TestOnBoardUser(t *testing.T) {
assert := assert.New(t)
auth := Auth{}
ctx := orm.Context()
auth := Auth{
userMgr: userpkg.New(),
}
um1 := &models.User{
Username: " ",
}
@ -99,11 +107,11 @@ func TestOnBoardUser(t *testing.T) {
um2 := &models.User{
Username: "test ",
}
user2, _ := dao.GetUser(models.User{Username: "test"})
user2, _ := auth.userMgr.GetByName(ctx, "test")
assert.Nil(user2)
err2 := auth.OnBoardUser(um2)
assert.Nil(err2)
user, _ := dao.GetUser(models.User{Username: "test"})
user, _ := auth.userMgr.GetByName(ctx, "test")
assert.Equal("test", user.Realname)
assert.Equal("test", user.Username)
assert.Equal("", user.Email)
@ -113,7 +121,9 @@ func TestOnBoardUser(t *testing.T) {
func TestPostAuthenticate(t *testing.T) {
assert := assert.New(t)
auth := Auth{}
auth := Auth{
userMgr: userpkg.New(),
}
um := &models.User{
Username: "test",
}
@ -122,15 +132,16 @@ func TestPostAuthenticate(t *testing.T) {
um2 := &models.User{
Username: "test",
}
ctx := orm.Context()
assert.Nil(err)
user, _ := dao.GetUser(models.User{Username: "test"})
user, _ := auth.userMgr.GetByName(ctx, "test")
assert.Equal("", user.Email)
um2.Email = "newEmail@new.com"
um2.Realname = "newName"
err2 := auth.PostAuthenticate(um2)
assert.Equal(user.UserID, um2.UserID)
assert.Nil(err2)
user2, _ := dao.GetUser(models.User{Username: "test"})
user2, _ := auth.userMgr.GetByName(ctx, "test")
assert.Equal("newEmail@new.com", user2.Email)
assert.Equal("newName", user2.Realname)
// need a new user model to simulate a login case...
@ -139,7 +150,7 @@ func TestPostAuthenticate(t *testing.T) {
}
err3 := auth.PostAuthenticate(um3)
assert.Nil(err3)
user3, _ := dao.GetUser(models.User{Username: "test"})
user3, _ := auth.userMgr.GetByName(ctx, "test")
assert.Equal(user3.UserID, um3.UserID)
assert.Equal("", user3.Email)
assert.Equal("test", user3.Realname)

View File

@ -37,7 +37,6 @@ import (
_ "github.com/goharbor/harbor/src/controller/event/handler"
"github.com/goharbor/harbor/src/controller/health"
"github.com/goharbor/harbor/src/controller/registry"
ctluser "github.com/goharbor/harbor/src/controller/user"
"github.com/goharbor/harbor/src/core/api"
_ "github.com/goharbor/harbor/src/core/auth/authproxy"
_ "github.com/goharbor/harbor/src/core/auth/db"
@ -58,6 +57,7 @@ import (
_ "github.com/goharbor/harbor/src/pkg/notifier/topic"
"github.com/goharbor/harbor/src/pkg/scan"
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
pkguser "github.com/goharbor/harbor/src/pkg/user"
"github.com/goharbor/harbor/src/pkg/version"
"github.com/goharbor/harbor/src/server"
)
@ -67,16 +67,13 @@ const (
)
func updateInitPassword(ctx context.Context, userID int, password string) error {
queryUser := models.User{UserID: userID}
user, err := dao.GetUser(queryUser)
userMgr := pkguser.Mgr
user, err := userMgr.Get(ctx, userID)
if err != nil {
return fmt.Errorf("failed to get user, userID: %d %v", userID, err)
}
if user == nil {
return fmt.Errorf("user id: %d does not exist", userID)
}
if user.Salt == "" {
err = ctluser.Ctl.UpdatePassword(ctx, userID, password)
err = userMgr.UpdatePassword(ctx, userID, password)
if err != nil {
return fmt.Errorf("failed to update user encrypted password, userID: %d, err: %v", userID, err)
}

View File

@ -54,6 +54,10 @@ type Manager interface {
// MatchLocalPassword tries to match the record in DB based on the input, the first return value is
// the user model corresponding to the entry in DB
MatchLocalPassword(ctx context.Context, username, password string) (*models.User, error)
// Onboard 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.
// This is used for ldap and uaa authentication, such the user can have an ID in Harbor.
Onboard(ctx context.Context, user *models.User) error
}
// New returns a default implementation of Manager
@ -65,6 +69,27 @@ type manager struct {
dao dao.DAO
}
func (m *manager) Onboard(ctx context.Context, user *models.User) error {
u, err := m.GetByName(ctx, user.Username)
if err == nil {
user.Email = u.Email
user.SysAdminFlag = u.SysAdminFlag
user.Realname = u.Realname
user.UserID = u.UserID
return nil
} else if !errors.IsNotFoundErr(err) {
return err
}
// User does not exists, insert the user record.
// Given this func is ALWAYS called in a tx, the conflict error can rollback the tx to ensure the consistency
id, err2 := m.Create(ctx, user)
if err2 != nil {
return err2
}
user.UserID = id
return nil
}
func (m *manager) Delete(ctx context.Context, id int) error {
u, err := m.Get(ctx, id)
if err != nil {

View File

@ -6,6 +6,8 @@ import (
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/testing/mock"
"github.com/goharbor/harbor/src/testing/pkg/user/dao"
"github.com/stretchr/testify/assert"
@ -45,6 +47,56 @@ func (m *mgrTestSuite) TestSetAdminFlag() {
m.dao.AssertExpectations(m.T())
}
func (m *mgrTestSuite) TestOnboard() {
existingUser := &models.User{
UserID: 123,
Username: "existing",
Email: "existing@mytest.com",
Realname: "existing",
}
newID := 124
m.dao.On("Create", mock.Anything, testifymock.MatchedBy(
func(u *models.User) bool {
return u.Username == "existing"
})).Return(0, errors.ConflictError(nil).WithMessage("username exists"))
m.dao.On("Create", mock.Anything, testifymock.MatchedBy(
func(u *models.User) bool {
return u.Username != "existing" && u.Username != "dup-but-not-existing"
})).Return(newID, nil)
m.dao.On("List", mock.Anything, testifymock.MatchedBy(
func(query *q.Query) bool {
return query.Keywords["username"] == "existing"
})).Return([]*models.User{existingUser}, nil)
m.dao.On("List", mock.Anything, testifymock.MatchedBy(
func(query *q.Query) bool {
return query.Keywords["username"] != "existing"
})).Return([]*models.User{}, nil)
{
newUser := &models.User{
Username: "newUser",
Email: "newUser@mytest.com",
Realname: "newUser",
}
err := m.mgr.Onboard(context.Background(), newUser)
m.Nil(err)
m.Equal(newID, newUser.UserID)
m.Equal(newUser.Username, newUser.Username)
}
{
newUser := &models.User{
Username: "existing",
Email: "existing@mytest.com",
Realname: "existing",
}
err := m.mgr.Onboard(context.Background(), newUser)
m.Nil(err)
m.Equal(existingUser.Username, newUser.Username)
m.Equal(existingUser.Email, newUser.Email)
m.Equal(existingUser.UserID, newUser.UserID)
}
}
func TestManager(t *testing.T) {
suite.Run(t, &mgrTestSuite{})
}

View File

@ -15,20 +15,20 @@
package security
import (
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/orm"
"net/http"
"strings"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/config"
"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/pkg/authproxy"
pkguser "github.com/goharbor/harbor/src/pkg/user"
)
type authProxy struct{}
@ -65,27 +65,22 @@ func (a *authProxy) Generate(req *http.Request) security.Context {
log.Errorf("user name doesn't match with token: %s", rawUserName)
return nil
}
user, err := dao.GetUser(models.User{
Username: rawUserName,
})
if err != nil {
log.Errorf("failed to get user %s: %v", rawUserName, err)
return nil
}
if user == nil {
user, err := pkguser.Mgr.GetByName(req.Context(), rawUserName)
if errors.IsNotFoundErr(err) {
// onboard user if it's not yet onboarded.
uid, err := auth.SearchAndOnBoardUser(rawUserName)
if err != nil {
uid, err2 := auth.SearchAndOnBoardUser(rawUserName)
if err2 != nil {
log.Errorf("failed to search and onboard user %s: %v", rawUserName, err)
return nil
}
user, err = dao.GetUser(models.User{
UserID: uid,
})
if err != nil {
user, err2 = pkguser.Mgr.Get(req.Context(), uid)
if err2 != nil {
log.Errorf("failed to get user, name: %s, ID: %d: %v", rawUserName, uid, err)
return nil
}
} else if err != nil {
log.Errorf("failed to get user %s: %v", rawUserName, err)
return nil
}
u2, err := authproxy.UserFromReviewStatus(tokenReviewStatus, httpAuthProxyConf.AdminGroups, httpAuthProxyConf.AdminUsernames)
if err != nil {

View File

@ -67,7 +67,8 @@ func TestAuthProxy(t *testing.T) {
// No onboard
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/v2", nil)
require.Nil(t, err)
req = req.WithContext(lib.WithAuthMode(req.Context(), common.HTTPAuth))
ormCtx := orm.Context()
req = req.WithContext(lib.WithAuthMode(ormCtx, common.HTTPAuth))
req.SetBasicAuth("tokenreview$administrator@vsphere.local", "reviEwt0k3n")
ctx := authProxy.Generate(req)
assert.NotNil(t, ctx)

View File

@ -166,6 +166,20 @@ func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, pass
return r0, r1
}
// Onboard provides a mock function with given fields: ctx, _a1
func (_m *Manager) Onboard(ctx context.Context, _a1 *models.User) error {
ret := _m.Called(ctx, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.User) error); ok {
r0 = rf(ctx, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetSysAdminFlag provides a mock function with given fields: ctx, id, admin
func (_m *Manager) SetSysAdminFlag(ctx context.Context, id int, admin bool) error {
ret := _m.Called(ctx, id, admin)