mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 20:26:13 +01:00
Move user related funcs from common/dao
This commit moves more user related funcs, such as ChangePassword, Login, ChangeUserProfile from common/dao to rely on /pkg/user and pkg/oidc. It also removes the code for resetting user's password as it's disabled. Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
parent
3322716bc6
commit
6d0e391740
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
libOrm "github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
@ -154,45 +153,6 @@ func clearAll() {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginByUserName(t *testing.T) {
|
||||
loginUser, err := LoginByDb(models.AuthModel{
|
||||
Principal: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in LoginByDb: %v", err)
|
||||
}
|
||||
if loginUser == nil {
|
||||
t.Errorf("No found for user logined by username and password: %s, %s", username, password)
|
||||
}
|
||||
|
||||
if loginUser.Username != username {
|
||||
t.Errorf("User's username does not match after login, expected: %s, actual: %s", username, loginUser.Username)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginByEmail(t *testing.T) {
|
||||
|
||||
userQuery := models.User{
|
||||
Email: "tester01@vmware.com",
|
||||
Password: "Abc12345",
|
||||
}
|
||||
|
||||
loginUser, err := LoginByDb(models.AuthModel{
|
||||
Principal: userQuery.Email,
|
||||
Password: userQuery.Password,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in LoginByDb: %v", err)
|
||||
}
|
||||
if loginUser == nil {
|
||||
t.Errorf("No found for user logined by email and password : %v", userQuery)
|
||||
}
|
||||
if loginUser.Username != username {
|
||||
t.Errorf("User's username does not match after login, expected: %s, actual: %s", username, loginUser.Username)
|
||||
}
|
||||
}
|
||||
|
||||
var currentUser *models.User
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
@ -217,60 +177,6 @@ func TestGetUser(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestResetUserPassword(t *testing.T) {
|
||||
uuid := utils.GenerateRandomString()
|
||||
|
||||
err := UpdateUserResetUUID(models.User{ResetUUID: uuid, Email: currentUser.Email})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in UpdateUserResetUuid: %v", err)
|
||||
}
|
||||
|
||||
err = ResetUserPassword(
|
||||
models.User{
|
||||
UserID: currentUser.UserID,
|
||||
PasswordVersion: utils.SHA256,
|
||||
ResetUUID: uuid,
|
||||
Salt: currentUser.Salt}, "HarborTester12345")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ResetUserPassword: %v", err)
|
||||
}
|
||||
|
||||
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "HarborTester12345"})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in LoginByDb: %v", err)
|
||||
}
|
||||
|
||||
if loginedUser.Username != username {
|
||||
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", username, loginedUser.Username)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeUserPassword(t *testing.T) {
|
||||
user := models.User{UserID: currentUser.UserID}
|
||||
query, err := GetUser(user)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when get user salt")
|
||||
}
|
||||
currentUser.Salt = query.Salt
|
||||
err = ChangeUserPassword(
|
||||
models.User{
|
||||
UserID: currentUser.UserID,
|
||||
Password: "NewHarborTester12345",
|
||||
PasswordVersion: utils.SHA256,
|
||||
Salt: currentUser.Salt})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ChangeUserPassword: %v", err)
|
||||
}
|
||||
|
||||
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NewHarborTester12345"})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in LoginByDb: %v", err)
|
||||
}
|
||||
|
||||
if loginedUser.Username != username {
|
||||
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", username, loginedUser.Username)
|
||||
}
|
||||
}
|
||||
func TestAddProject(t *testing.T) {
|
||||
|
||||
project := models.Project{
|
||||
@ -345,29 +251,6 @@ func TestGetProjects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeUserProfile(t *testing.T) {
|
||||
user := models.User{UserID: currentUser.UserID, Email: username + "@163.com", Realname: "test", Comment: "Unit Test"}
|
||||
err := ChangeUserProfile(user)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ChangeUserProfile: %v", err)
|
||||
}
|
||||
loginedUser, err := GetUser(models.User{UserID: currentUser.UserID})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUser: %v", err)
|
||||
}
|
||||
if loginedUser != nil {
|
||||
if loginedUser.Email != username+"@163.com" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", username+"@163.com", loginedUser.Email)
|
||||
}
|
||||
if loginedUser.Realname != "test" {
|
||||
t.Errorf("user realname does not update, expected: %s, acutal: %s", "test", loginedUser.Realname)
|
||||
}
|
||||
if loginedUser.Comment != "Unit Test" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", "Unit Test", loginedUser.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64
|
||||
|
||||
func TestGetOrmer(t *testing.T) {
|
||||
@ -430,12 +313,6 @@ func TestDeleteRepository(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSuperUser(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.True(IsSuperUser("admin"))
|
||||
assert.False(IsSuperUser("none"))
|
||||
}
|
||||
|
||||
func TestIsDupRecError(t *testing.T) {
|
||||
assert.True(t, IsDupRecErr(fmt.Errorf("pq: duplicate key value violates unique constraint \"properties_k_key\"")))
|
||||
assert.False(t, IsDupRecErr(fmt.Errorf("other error")))
|
||||
|
@ -39,44 +39,6 @@ var (
|
||||
ErrRollBackOIDCUser = errors.New("sql: transaction roll back error in oicd_user")
|
||||
)
|
||||
|
||||
// GetUserBySubIss ...
|
||||
func GetUserBySubIss(sub, issuer string) (*models.User, error) {
|
||||
var oidcUsers []models.OIDCUser
|
||||
n, err := GetOrmer().Raw(`select * from oidc_user where subiss = ? `, sub+issuer).QueryRows(&oidcUsers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
user, err := GetUser(models.User{
|
||||
UserID: oidcUsers[0].UserID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("can not get user %d", oidcUsers[0].UserID)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetOIDCUserByUserID ...
|
||||
func GetOIDCUserByUserID(userID int) (*models.OIDCUser, error) {
|
||||
var oidcUsers []models.OIDCUser
|
||||
n, err := GetOrmer().Raw(`select * from oidc_user where user_id = ? `, userID).QueryRows(&oidcUsers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &oidcUsers[0], nil
|
||||
}
|
||||
|
||||
// UpdateOIDCUser updates the OIDCUser based on the input parm, only the column "secret" and "token" can be updated
|
||||
func UpdateOIDCUser(oidcUser *models.OIDCUser) error {
|
||||
cols := []string{"secret", "token"}
|
||||
|
@ -60,11 +60,6 @@ func TestOIDCUserMetaDaoMethods(t *testing.T) {
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, "unable to onboard as empty oidc user", err.Error())
|
||||
|
||||
// test get by sub and iss
|
||||
userGetBySubIss, err := GetUserBySubIss("QWE123", "123RT1")
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "user111@email.com", userGetBySubIss.Email)
|
||||
|
||||
// test update
|
||||
meta3 := &models.OIDCUser{
|
||||
ID: ou111.ID,
|
||||
|
@ -15,13 +15,9 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
// GetUser ...
|
||||
@ -71,88 +67,6 @@ func GetUser(query models.User) (*models.User, error) {
|
||||
return &u[0], nil
|
||||
}
|
||||
|
||||
// LoginByDb is used for user to login with database auth mode.
|
||||
func LoginByDb(auth models.AuthModel) (*models.User, error) {
|
||||
var users []models.User
|
||||
o := GetOrmer()
|
||||
|
||||
n, err := o.Raw(`select * from harbor_user where (username = ? or email = ?) and deleted = false`,
|
||||
auth.Principal, auth.Principal).QueryRows(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
user := users[0]
|
||||
|
||||
if !matchPassword(&user, auth.Password) {
|
||||
return nil, nil
|
||||
}
|
||||
user.Password = "" // do not return the password
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// ChangeUserPassword ...
|
||||
func ChangeUserPassword(u models.User) error {
|
||||
u.UpdateTime = time.Now()
|
||||
u.Salt = utils.GenerateRandomString()
|
||||
u.Password = utils.Encrypt(u.Password, u.Salt, utils.SHA256)
|
||||
var err error
|
||||
if u.PasswordVersion == utils.SHA1 {
|
||||
u.PasswordVersion = utils.SHA256
|
||||
_, err = GetOrmer().Update(&u, "Password", "PasswordVersion", "Salt", "UpdateTime")
|
||||
} else {
|
||||
_, err = GetOrmer().Update(&u, "Password", "Salt", "UpdateTime")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ResetUserPassword ...
|
||||
func ResetUserPassword(u models.User, rawPassword string) error {
|
||||
var rowsAffected int64
|
||||
var err error
|
||||
u.UpdateTime = time.Now()
|
||||
u.Password = utils.Encrypt(rawPassword, u.Salt, utils.SHA256)
|
||||
u.ResetUUID = ""
|
||||
if u.PasswordVersion == utils.SHA1 {
|
||||
u.PasswordVersion = utils.SHA256
|
||||
rowsAffected, err = GetOrmer().Update(&u, "Password", "PasswordVersion", "ResetUUID", "UpdateTime")
|
||||
} else {
|
||||
rowsAffected, err = GetOrmer().Update(&u, "Password", "ResetUUID", "UpdateTime")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
return errors.New("no record be changed, reset password failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUserResetUUID ...
|
||||
func UpdateUserResetUUID(u models.User) error {
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(`update harbor_user set reset_uuid=? where email=?`, u.ResetUUID, u.Email).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
cols = []string{"Email", "Realname", "Comment"}
|
||||
}
|
||||
if _, err := o.Update(&user, cols...); err != nil {
|
||||
log.Errorf("update user failed, error: %v", err)
|
||||
return err
|
||||
}
|
||||
return 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.
|
||||
@ -185,26 +99,8 @@ func OnBoardUser(u *models.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSuperUser checks if the user is super user(conventionally id == 1) of Harbor
|
||||
func IsSuperUser(username string) bool {
|
||||
u, err := GetUser(models.User{
|
||||
Username: username,
|
||||
})
|
||||
log.Debugf("Check if user %s is super user", username)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get user from DB, username: %s, error: %v", username, err)
|
||||
return false
|
||||
}
|
||||
return u != nil && u.UserID == 1
|
||||
}
|
||||
|
||||
// CleanUser - Clean this user information from DB
|
||||
func CleanUser(id int64) error {
|
||||
_, err := GetOrmer().QueryTable(&models.User{}).Filter("UserID", id).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// MatchPassword returns true is password matched
|
||||
func matchPassword(u *models.User, password string) bool {
|
||||
return utils.Encrypt(password, u.Salt, u.PasswordVersion) == u.Password
|
||||
}
|
||||
|
@ -15,7 +15,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
)
|
||||
|
||||
// UserTable is the name of table in DB that holds the user object
|
||||
@ -50,3 +53,20 @@ type User struct {
|
||||
func (u *User) TableName() string {
|
||||
return UserTable
|
||||
}
|
||||
|
||||
// FilterByUsernameOrEmail generates the query setter to match username or email column to the same value
|
||||
func (u *User) FilterByUsernameOrEmail(ctx context.Context, qs orm.QuerySeter, key string, value interface{}) orm.QuerySeter {
|
||||
usernameOrEmail, ok := value.(string)
|
||||
if !ok {
|
||||
return qs
|
||||
}
|
||||
subCond := orm.NewCondition()
|
||||
subCond = subCond.Or("Username", usernameOrEmail).Or("Email", usernameOrEmail)
|
||||
|
||||
conds := qs.GetCond()
|
||||
if conds == nil {
|
||||
conds = orm.NewCondition()
|
||||
}
|
||||
qs = qs.SetCond(conds.AndCond(subCond))
|
||||
return qs
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ import (
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
pkguser "github.com/goharbor/harbor/src/pkg/user"
|
||||
)
|
||||
|
||||
// InitDatabaseFromEnv is used to initialize database for testing
|
||||
@ -86,9 +87,7 @@ func updateUserInitialPassword(userID int, password string) error {
|
||||
return fmt.Errorf("user id: %d does not exist", userID)
|
||||
}
|
||||
if user.Salt == "" {
|
||||
user.Salt = utils.GenerateRandomString()
|
||||
user.Password = password
|
||||
err = dao.ChangeUserPassword(*user)
|
||||
err = pkguser.Mgr.UpdatePassword(orm.Context(), userID, password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to update user encrypted password, userID: %d, err: %v", userID, err)
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type Controller interface {
|
||||
// SetSysAdmin ...
|
||||
SetSysAdmin(ctx context.Context, id int, adminFlag bool) error
|
||||
// VerifyPassword ...
|
||||
VerifyPassword(ctx context.Context, username string, password string) (bool, error)
|
||||
VerifyPassword(ctx context.Context, usernameOrEmail string, password string) (bool, error)
|
||||
// UpdatePassword ...
|
||||
UpdatePassword(ctx context.Context, id int, password string) error
|
||||
// List ...
|
||||
@ -50,11 +50,13 @@ type Controller interface {
|
||||
Get(ctx context.Context, id int, opt *Option) (*models.User, error)
|
||||
// GetByName gets the user model by username, it only supports getting the basic and does not support opt
|
||||
GetByName(ctx context.Context, username string) (*models.User, error)
|
||||
// GetBySubIss gets the user model by subject and issuer, the result will contain the basic user model and does not support opt
|
||||
GetBySubIss(ctx context.Context, sub, iss string) (*models.User, error)
|
||||
// Delete ...
|
||||
Delete(ctx context.Context, id int) error
|
||||
// UpdateProfile update the profile based on the ID and data in the model in parm, only a subset of attributes in the model
|
||||
// will be update, see the implementation of manager.
|
||||
UpdateProfile(ctx context.Context, u *models.User) error
|
||||
UpdateProfile(ctx context.Context, u *models.User, cols ...string) error
|
||||
// SetCliSecret sets the OIDC CLI secret for a user
|
||||
SetCliSecret(ctx context.Context, id int, secret string) error
|
||||
}
|
||||
@ -77,6 +79,14 @@ type controller struct {
|
||||
oidcMetaMgr oidc.MetaManager
|
||||
}
|
||||
|
||||
func (c *controller) GetBySubIss(ctx context.Context, sub, iss string) (*models.User, error) {
|
||||
oidcMeta, err := c.oidcMetaMgr.GetBySubIss(ctx, sub, iss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Get(ctx, oidcMeta.UserID, nil)
|
||||
}
|
||||
|
||||
func (c *controller) GetByName(ctx context.Context, username string) (*models.User, error) {
|
||||
return c.mgr.GetByName(ctx, username)
|
||||
}
|
||||
@ -89,8 +99,8 @@ func (c *controller) Create(ctx context.Context, u *models.User) (int, error) {
|
||||
return c.mgr.Create(ctx, u)
|
||||
}
|
||||
|
||||
func (c *controller) UpdateProfile(ctx context.Context, u *models.User) error {
|
||||
return c.mgr.UpdateProfile(ctx, u)
|
||||
func (c *controller) UpdateProfile(ctx context.Context, u *models.User, cols ...string) error {
|
||||
return c.mgr.UpdateProfile(ctx, u, cols...)
|
||||
}
|
||||
|
||||
func (c *controller) Get(ctx context.Context, id int, opt *Option) (*models.User, error) {
|
||||
@ -132,8 +142,12 @@ func (c *controller) UpdatePassword(ctx context.Context, id int, password string
|
||||
return c.mgr.UpdatePassword(ctx, id, password)
|
||||
}
|
||||
|
||||
func (c *controller) VerifyPassword(ctx context.Context, username, password string) (bool, error) {
|
||||
return c.mgr.VerifyLocalPassword(ctx, username, password)
|
||||
func (c *controller) VerifyPassword(ctx context.Context, usernameOrEmail, password string) (bool, error) {
|
||||
rec, err := c.mgr.MatchLocalPassword(ctx, usernameOrEmail, password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return rec != nil, nil
|
||||
}
|
||||
|
||||
func (c *controller) SetSysAdmin(ctx context.Context, id int, adminFlag bool) error {
|
||||
|
@ -21,9 +21,10 @@ import (
|
||||
|
||||
o "github.com/astaxie/beego/orm"
|
||||
"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/quota"
|
||||
"github.com/goharbor/harbor/src/controller/user"
|
||||
"github.com/goharbor/harbor/src/core/auth"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
@ -49,13 +50,14 @@ func (ia *InternalAPI) Prepare() {
|
||||
|
||||
// RenameAdmin we don't provide flexibility in this API, as this is a workaround.
|
||||
func (ia *InternalAPI) RenameAdmin() {
|
||||
if !dao.IsSuperUser(ia.SecurityCtx.GetUsername()) {
|
||||
ctx := ia.Ctx.Request.Context()
|
||||
if !auth.IsSuperUser(ctx, ia.SecurityCtx.GetUsername()) {
|
||||
log.Errorf("User %s is not super user, not allow to rename admin.", ia.SecurityCtx.GetUsername())
|
||||
ia.SendForbiddenError(errors.New(ia.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
newName := common.NewHarborAdminName
|
||||
if err := dao.ChangeUserProfile(models.User{
|
||||
if err := user.Ctl.UpdateProfile(ctx, &models.User{
|
||||
UserID: 1,
|
||||
Username: newName,
|
||||
}, "username"); err != nil {
|
||||
|
@ -15,19 +15,19 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
libErrors "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/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
)
|
||||
|
||||
// 1.5 seconds
|
||||
@ -137,12 +137,12 @@ func Register(name string, h AuthenticateHelper) {
|
||||
|
||||
// Login authenticates user credentials based on setting.
|
||||
func Login(m models.AuthModel) (*models.User, error) {
|
||||
|
||||
authMode, err := config.AuthMode(orm.Context())
|
||||
ctx := orm.Context()
|
||||
authMode, err := config.AuthMode(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authMode == "" || dao.IsSuperUser(m.Principal) {
|
||||
if authMode == "" || IsSuperUser(ctx, m.Principal) {
|
||||
authMode = common.DBAuth
|
||||
}
|
||||
log.Debug("Current AUTH_MODE is ", authMode)
|
||||
@ -257,3 +257,13 @@ func PostAuthenticate(u *models.User) error {
|
||||
}
|
||||
return helper.PostAuthenticate(u)
|
||||
}
|
||||
|
||||
// IsSuperUser checks if the user is super user(conventionally id == 1) of Harbor
|
||||
func IsSuperUser(ctx context.Context, username string) bool {
|
||||
u, err := user.Mgr.GetByName(ctx, username)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get user from DB, username: %s, error: %v", username, err)
|
||||
return false
|
||||
}
|
||||
return u.UserID == 1
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
"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/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
)
|
||||
|
||||
// Auth implements Authenticator interface to authenticate user against DB.
|
||||
@ -28,7 +30,7 @@ type Auth struct {
|
||||
|
||||
// Authenticate calls dao to authenticate user.
|
||||
func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
u, err := dao.LoginByDb(m)
|
||||
u, err := user.Mgr.MatchLocalPassword(orm.Context(), m.Principal, m.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ func (l *Auth) PostAuthenticate(u *models.User) error {
|
||||
if !Re.MatchString(u.Email) {
|
||||
log.Debugf("Not a valid email address: %v, skip to sync", u.Email)
|
||||
} else {
|
||||
if err = dao.ChangeUserProfile(*u, "Email"); err != nil {
|
||||
if err = user.Mgr.UpdateProfile(ctx, u, "Email"); err != nil {
|
||||
u.Email = dbUser.Email
|
||||
log.Errorf("failed to sync user email: %v", err)
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ package uaa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -27,7 +25,10 @@ import (
|
||||
"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/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
userpkg "github.com/goharbor/harbor/src/pkg/user"
|
||||
)
|
||||
|
||||
// Auth is the implementation of AuthenticateHelper to access uaa for authentication.
|
||||
@ -95,10 +96,9 @@ func (u *Auth) PostAuthenticate(user *models.User) error {
|
||||
user.UserID = dbUser.UserID
|
||||
user.SysAdminFlag = dbUser.SysAdminFlag
|
||||
fillEmailRealName(user)
|
||||
if err2 := dao.ChangeUserProfile(*user, "Email", "Realname"); err2 != nil {
|
||||
if err2 := userpkg.Mgr.UpdateProfile(orm.Context(), user, "Email", "Realname"); err2 != nil {
|
||||
log.Warningf("Failed to update user profile, user: %s, error: %v", user.Username, err2)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -15,25 +15,18 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
o "github.com/astaxie/beego/orm"
|
||||
"github.com/beego/i18n"
|
||||
"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/utils"
|
||||
email_util "github.com/goharbor/harbor/src/common/utils/email"
|
||||
"github.com/goharbor/harbor/src/controller/user"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/core/auth"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
@ -41,7 +34,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
)
|
||||
|
||||
// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ...
|
||||
@ -68,18 +60,18 @@ func redirectForOIDC(ctx context.Context, username string) bool {
|
||||
if lib.GetAuthMode(ctx) != common.OIDCAuth {
|
||||
return false
|
||||
}
|
||||
u, err := dao.GetUser(models.User{Username: username})
|
||||
u, err := user.Ctl.GetByName(ctx, username)
|
||||
if err != nil {
|
||||
log.Warningf("Failed to get user by name: %s, error: %v", username, err)
|
||||
}
|
||||
if u == nil {
|
||||
return true
|
||||
}
|
||||
ou, err := dao.GetOIDCUserByUserID(u.UserID)
|
||||
us, err := user.Ctl.Get(ctx, u.UserID, &user.Option{WithOIDCInfo: true})
|
||||
if err != nil {
|
||||
log.Warningf("Failed to get OIDC user info for user, id: %d, error: %v", u.UserID, err)
|
||||
}
|
||||
if ou != nil {
|
||||
if us != nil && us.OIDCUserMeta != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -149,7 +141,7 @@ func (cc *CommonController) UserExists() {
|
||||
query = q.New(q.KeyWords{"Email": value})
|
||||
}
|
||||
|
||||
n, err := user.Mgr.Count(ctx, query)
|
||||
n, err := user.Ctl.Count(ctx, query)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in UserExists: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
@ -158,145 +150,6 @@ func (cc *CommonController) UserExists() {
|
||||
cc.ServeJSON()
|
||||
}
|
||||
|
||||
// SendResetEmail verifies the Email address and contact SMTP server to send reset password Email.
|
||||
func (cc *CommonController) SendResetEmail() {
|
||||
|
||||
email := cc.GetString("email")
|
||||
|
||||
valid, err := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, email)
|
||||
if err != nil {
|
||||
log.Errorf("failed to match regexp: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
|
||||
if !valid {
|
||||
cc.CustomAbort(http.StatusBadRequest, "invalid email")
|
||||
}
|
||||
|
||||
queryUser := models.User{Email: email}
|
||||
u, err := dao.GetUser(queryUser)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetUser: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if u == nil {
|
||||
log.Debugf("email %s not found", email)
|
||||
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
|
||||
}
|
||||
|
||||
if !isUserResetable(u) {
|
||||
log.Errorf("Resetting password for user with ID: %d is not allowed", u.UserID)
|
||||
cc.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||
}
|
||||
|
||||
uuid := utils.GenerateRandomString()
|
||||
user := models.User{ResetUUID: uuid, Email: email}
|
||||
if err = dao.UpdateUserResetUUID(user); err != nil {
|
||||
log.Errorf("failed to update user reset UUID: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
messageTemplate, err := template.ParseFiles("views/reset-password-mail.tpl")
|
||||
if err != nil {
|
||||
log.Errorf("Parse email template file failed: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
message := new(bytes.Buffer)
|
||||
|
||||
harborURL, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get domain name: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
err = messageTemplate.Execute(message, messageDetail{
|
||||
Hint: cc.Tr("reset_email_hint"),
|
||||
URL: harborURL,
|
||||
UUID: uuid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Message template error: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||
}
|
||||
|
||||
settings, err := config.Email(orm.Context())
|
||||
if err != nil {
|
||||
log.Errorf("failed to get email configurations: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(settings.Host, strconv.Itoa(settings.Port))
|
||||
err = email_util.Send(addr,
|
||||
settings.Identity,
|
||||
settings.Username,
|
||||
settings.Password,
|
||||
60, settings.SSL,
|
||||
settings.Insecure,
|
||||
settings.From,
|
||||
[]string{u.Email},
|
||||
"Reset Harbor user password",
|
||||
message.String())
|
||||
if err != nil {
|
||||
log.Errorf("Send email failed: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
|
||||
}
|
||||
}
|
||||
|
||||
// ResetPassword handles request from the reset page and reset password
|
||||
func (cc *CommonController) ResetPassword() {
|
||||
|
||||
resetUUID := cc.GetString("reset_uuid")
|
||||
if resetUUID == "" {
|
||||
cc.CustomAbort(http.StatusBadRequest, "Reset uuid is blank.")
|
||||
}
|
||||
|
||||
queryUser := models.User{ResetUUID: resetUUID}
|
||||
user, err := dao.GetUser(queryUser)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetUser: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if user == nil {
|
||||
log.Error("User does not exist")
|
||||
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
|
||||
}
|
||||
|
||||
if !isUserResetable(user) {
|
||||
log.Errorf("Resetting password for user with ID: %d is not allowed", user.UserID)
|
||||
cc.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
|
||||
}
|
||||
|
||||
rawPassword := cc.GetString("password")
|
||||
|
||||
if rawPassword != "" {
|
||||
err = dao.ResetUserPassword(*user, rawPassword)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in ResetUserPassword: %v", err)
|
||||
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
} else {
|
||||
cc.CustomAbort(http.StatusBadRequest, "password_is_required")
|
||||
}
|
||||
}
|
||||
|
||||
func isUserResetable(u *models.User) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
mode, err := config.AuthMode(orm.Context())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get the auth mode, error: %v", err)
|
||||
return false
|
||||
}
|
||||
if mode == common.DBAuth {
|
||||
return true
|
||||
}
|
||||
return u.UserID == 1
|
||||
}
|
||||
|
||||
func init() {
|
||||
// conf/app.conf -> os.Getenv("config_path")
|
||||
configPath := os.Getenv("CONFIG_PATH")
|
||||
|
@ -15,10 +15,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/core/middlewares"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@ -27,9 +23,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/core/middlewares"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
utilstest "github.com/goharbor/harbor/src/common/utils/test"
|
||||
_ "github.com/goharbor/harbor/src/pkg/config/db"
|
||||
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
||||
@ -58,35 +58,6 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserResettable
|
||||
func TestUserResettable(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
DBAuthConfig := map[string]interface{}{
|
||||
common.AUTHMode: common.DBAuth,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
|
||||
LDAPAuthConfig := map[string]interface{}{
|
||||
common.AUTHMode: common.LDAPAuth,
|
||||
common.TokenExpiration: 30,
|
||||
}
|
||||
config.InitWithSettings(LDAPAuthConfig)
|
||||
u1 := &models.User{
|
||||
UserID: 3,
|
||||
Username: "daniel",
|
||||
Email: "daniel@test.com",
|
||||
}
|
||||
u2 := &models.User{
|
||||
UserID: 1,
|
||||
Username: "jack",
|
||||
Email: "jack@test.com",
|
||||
}
|
||||
assert.False(isUserResetable(u1))
|
||||
assert.True(isUserResetable(u2))
|
||||
config.InitWithSettings(DBAuthConfig)
|
||||
assert.True(isUserResetable(u1))
|
||||
}
|
||||
|
||||
func TestRedirectForOIDC(t *testing.T) {
|
||||
ctx := lib.WithAuthMode(orm.Context(), common.DBAuth)
|
||||
assert.False(t, redirectForOIDC(ctx, "nonexist"))
|
||||
|
@ -17,8 +17,6 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -26,9 +24,12 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/controller/user"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"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/oidc"
|
||||
)
|
||||
|
||||
@ -110,31 +111,23 @@ func (oc *OIDCController) Callback() {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
u, err := dao.GetUserBySubIss(info.Subject, info.Issuer)
|
||||
if err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
tokenBytes, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
oc.SetSession(tokenKey, tokenBytes)
|
||||
|
||||
u, err := user.Ctl.GetBySubIss(ctx, info.Subject, info.Issuer)
|
||||
if errors.IsNotFoundErr(err) { // User is not onboarded, kickoff the onboard flow
|
||||
// Recover the username from d.Username by default
|
||||
username := info.Username
|
||||
// Fix blanks in username
|
||||
username = strings.Replace(username, " ", "_", -1)
|
||||
oidcSettings, err := config.OIDCSetting(ctx)
|
||||
if err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
// Recover the username from d.Username by default
|
||||
username := info.Username
|
||||
|
||||
// Fix blanks in username
|
||||
username = strings.Replace(username, " ", "_", -1)
|
||||
|
||||
// If automatic onboard is enabled, skip the onboard page
|
||||
if oidcSettings.AutoOnboard {
|
||||
log.Debug("Doing automatic onboarding\n")
|
||||
@ -143,27 +136,31 @@ func (oc *OIDCController) Callback() {
|
||||
oidcSettings.UserClaim))
|
||||
return
|
||||
}
|
||||
user, onboarded := userOnboard(oc, info, username, tokenBytes)
|
||||
userRec, onboarded := userOnboard(oc, info, username, tokenBytes)
|
||||
if onboarded == false {
|
||||
log.Error("User not onboarded\n")
|
||||
return
|
||||
}
|
||||
log.Debug("User automatically onboarded\n")
|
||||
u = user
|
||||
u = userRec
|
||||
} else {
|
||||
oc.SetSession(userInfoKey, string(ouDataStr))
|
||||
oc.Controller.Redirect(fmt.Sprintf("/oidc-onboard?username=%s", username), http.StatusFound)
|
||||
// Once redirected, no further actions are done
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
oidc.InjectGroupsToUser(info, u)
|
||||
oidcUser, err := dao.GetOIDCUserByUserID(u.UserID)
|
||||
um, err := user.Ctl.Get(ctx, u.UserID, &user.Option{WithOIDCInfo: true})
|
||||
if err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
_, t, err := secretAndToken(tokenBytes)
|
||||
oidcUser := um.OIDCUserMeta
|
||||
oidcUser.Token = t
|
||||
if err := dao.UpdateOIDCUser(oidcUser); err != nil {
|
||||
oc.SendInternalServerError(err)
|
||||
@ -171,7 +168,6 @@ func (oc *OIDCController) Callback() {
|
||||
}
|
||||
oc.PopulateUserSession(*u)
|
||||
oc.Controller.Redirect("/", http.StatusFound)
|
||||
|
||||
}
|
||||
|
||||
func userOnboard(oc *OIDCController, info *oidc.UserInfo, username string, tokenBytes []byte) (*models.User, bool) {
|
||||
|
@ -32,10 +32,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
common_http "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
_ "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"
|
||||
@ -64,7 +64,7 @@ const (
|
||||
adminUserID = 1
|
||||
)
|
||||
|
||||
func updateInitPassword(userID int, password string) error {
|
||||
func updateInitPassword(ctx context.Context, userID int, password string) error {
|
||||
queryUser := models.User{UserID: userID}
|
||||
user, err := dao.GetUser(queryUser)
|
||||
if err != nil {
|
||||
@ -74,11 +74,7 @@ func updateInitPassword(userID int, password string) error {
|
||||
return fmt.Errorf("user id: %d does not exist", userID)
|
||||
}
|
||||
if user.Salt == "" {
|
||||
salt := utils.GenerateRandomString()
|
||||
|
||||
user.Salt = salt
|
||||
user.Password = password
|
||||
err = dao.ChangeUserPassword(*user)
|
||||
err = ctluser.Ctl.UpdatePassword(ctx, userID, password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to update user encrypted password, userID: %d, err: %v", userID, err)
|
||||
}
|
||||
@ -190,7 +186,8 @@ func main() {
|
||||
if err = migration.Migrate(database); err != nil {
|
||||
log.Fatalf("failed to migrate: %v", err)
|
||||
}
|
||||
if err := config.Load(orm.Context()); err != nil {
|
||||
ctx := orm.Context()
|
||||
if err := config.Load(ctx); err != nil {
|
||||
log.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
@ -198,7 +195,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get admin's initial password: %v", err)
|
||||
}
|
||||
if err := updateInitPassword(adminUserID, password); err != nil {
|
||||
if err := updateInitPassword(ctx, adminUserID, password); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,8 @@ type MetaManager interface {
|
||||
Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error)
|
||||
// GetByUserID gets the oidc meta record by user's ID
|
||||
GetByUserID(ctx context.Context, uid int) (*models.OIDCUser, error)
|
||||
// GetBySubIss gets the oidc meta record by the subject and issuer
|
||||
GetBySubIss(ctx context.Context, sub, iss string) (*models.OIDCUser, error)
|
||||
// SetCliSecretByUserID updates the cli secret of a user based on the user ID
|
||||
SetCliSecretByUserID(ctx context.Context, uid int, secret string) error
|
||||
}
|
||||
@ -39,6 +41,21 @@ type metaManager struct {
|
||||
dao dao.MetaDAO
|
||||
}
|
||||
|
||||
func (m *metaManager) GetBySubIss(ctx context.Context, sub, iss string) (*models.OIDCUser, error) {
|
||||
logger := log.GetLogger(ctx)
|
||||
l, err := m.dao.List(ctx, q.New(q.KeyWords{"subiss": sub + iss}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(l) == 0 {
|
||||
return nil, errors.NotFoundError(nil).WithMessage("oidc info for user with issuer %s, subject %s not found", iss, sub)
|
||||
}
|
||||
if len(l) > 1 {
|
||||
logger.Warningf("Multiple oidc info records found for issuer %s, subject %s", iss, sub)
|
||||
}
|
||||
return l[0], nil
|
||||
}
|
||||
|
||||
func (m *metaManager) Create(ctx context.Context, oidcUser *models.OIDCUser) (int, error) {
|
||||
return m.dao.Create(ctx, oidcUser)
|
||||
}
|
||||
|
@ -74,17 +74,43 @@ func (suite *DaoTestSuite) TestCount() {
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestList() {
|
||||
ctx := orm.Context()
|
||||
{
|
||||
users, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"user_id": 1}))
|
||||
users, err := suite.dao.List(ctx, q.New(q.KeyWords{"user_id": 1}))
|
||||
suite.Nil(err)
|
||||
suite.Len(users, 1)
|
||||
}
|
||||
|
||||
{
|
||||
users, err := suite.dao.List(orm.Context(), q.New(q.KeyWords{"username": "admin"}))
|
||||
users, err := suite.dao.List(ctx, q.New(q.KeyWords{"username": "admin"}))
|
||||
suite.Nil(err)
|
||||
suite.Len(users, 1)
|
||||
}
|
||||
id, err := suite.dao.Create(ctx, &models.User{
|
||||
Username: "list_test",
|
||||
Realname: "list test",
|
||||
Email: "list_test@test.com",
|
||||
Password: "somepassword",
|
||||
PasswordVersion: "sha256",
|
||||
})
|
||||
suite.appendClearSQL(id)
|
||||
suite.Nil(err)
|
||||
{
|
||||
users, err := suite.dao.List(ctx, q.New(q.KeyWords{"username_or_email": "list_test"}))
|
||||
suite.Nil(err)
|
||||
suite.Len(users, 1)
|
||||
}
|
||||
{
|
||||
users, err := suite.dao.List(ctx, q.New(q.KeyWords{"username_or_email": "list_test@test.com"}))
|
||||
suite.Nil(err)
|
||||
suite.Len(users, 1)
|
||||
}
|
||||
{
|
||||
users, err := suite.dao.List(ctx, q.New(q.KeyWords{"username_or_email": "noremail_norusername"}))
|
||||
suite.Nil(err)
|
||||
suite.Len(users, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (suite *DaoTestSuite) TestCreate() {
|
||||
|
@ -35,7 +35,7 @@ var (
|
||||
type Manager interface {
|
||||
// Get get user by user id
|
||||
Get(ctx context.Context, id int) (*models.User, error)
|
||||
// GetByName get user by username
|
||||
// GetByName get user by username, it will return an error if the user does not exist
|
||||
GetByName(ctx context.Context, username string) (*models.User, error)
|
||||
// List users according to the query
|
||||
List(ctx context.Context, query *q.Query) (models.Users, error)
|
||||
@ -48,11 +48,12 @@ type Manager interface {
|
||||
// SetSysAdminFlag sets the system admin flag of the user in local DB
|
||||
SetSysAdminFlag(ctx context.Context, id int, admin bool) error
|
||||
// UpdateProfile updates the user's profile
|
||||
UpdateProfile(ctx context.Context, user *models.User) error
|
||||
UpdateProfile(ctx context.Context, user *models.User, col ...string) error
|
||||
// UpdatePassword updates user's password
|
||||
UpdatePassword(ctx context.Context, id int, newPassword string) error
|
||||
// VerifyLocalPassword verifies the password against the record in DB based on the input
|
||||
VerifyLocalPassword(ctx context.Context, username, password string) (bool, error)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// New returns a default implementation of Manager
|
||||
@ -75,20 +76,29 @@ func (m *manager) Delete(ctx context.Context, id int) error {
|
||||
return m.dao.Update(ctx, u, "username", "email", "deleted")
|
||||
}
|
||||
|
||||
func (m *manager) VerifyLocalPassword(ctx context.Context, username, password string) (bool, error) {
|
||||
u, err := m.GetByName(ctx, username)
|
||||
func (m *manager) MatchLocalPassword(ctx context.Context, usernameOrEmail, password string) (*models.User, error) {
|
||||
l, err := m.dao.List(ctx, q.New(q.KeyWords{"username_or_email": usernameOrEmail}))
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
return utils.Encrypt(password, u.Salt, u.PasswordVersion) == u.Password, nil
|
||||
for _, entry := range l {
|
||||
if utils.Encrypt(password, entry.Salt, entry.PasswordVersion) == entry.Password {
|
||||
entry.Password = ""
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return m.dao.Count(ctx, query)
|
||||
}
|
||||
|
||||
func (m *manager) UpdateProfile(ctx context.Context, user *models.User) error {
|
||||
return m.dao.Update(ctx, user, "email", "realname", "comment")
|
||||
func (m *manager) UpdateProfile(ctx context.Context, user *models.User, cols ...string) error {
|
||||
if cols == nil || len(cols) == 0 {
|
||||
cols = []string{"Email", "Realname", "Comment"}
|
||||
}
|
||||
return m.dao.Update(ctx, user, cols...)
|
||||
}
|
||||
|
||||
func (m *manager) UpdatePassword(ctx context.Context, id int, newPassword string) error {
|
||||
@ -126,7 +136,7 @@ func (m *manager) Get(ctx context.Context, id int) (*models.User, error) {
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// Get get user by username
|
||||
// GetByName get user by username
|
||||
func (m *manager) GetByName(ctx context.Context, username string) (*models.User, error) {
|
||||
users, err := m.dao.List(ctx, q.New(q.KeyWords{"username": username}))
|
||||
if err != nil {
|
||||
|
@ -15,15 +15,15 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/common/security/local"
|
||||
"github.com/goharbor/harbor/src/controller/user"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/oidc"
|
||||
)
|
||||
@ -48,15 +48,11 @@ func (i *idToken) Generate(req *http.Request) security.Context {
|
||||
log.Warningf("failed to verify token: %v", err)
|
||||
return nil
|
||||
}
|
||||
u, err := dao.GetUserBySubIss(claims.Subject, claims.Issuer)
|
||||
u, err := user.Ctl.GetBySubIss(ctx, claims.Subject, claims.Issuer)
|
||||
if err != nil {
|
||||
log.Warningf("failed to get user based on token claims: %v", err)
|
||||
return nil
|
||||
}
|
||||
if u == nil {
|
||||
log.Warning("user matches token's claims is not onboarded.")
|
||||
return nil
|
||||
}
|
||||
setting, err := config.OIDCSetting(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get OIDC settings: %v", err)
|
||||
|
@ -120,6 +120,29 @@ func (_m *Controller) GetByName(ctx context.Context, username string) (*models.U
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBySubIss provides a mock function with given fields: ctx, sub, iss
|
||||
func (_m *Controller) GetBySubIss(ctx context.Context, sub string, iss string) (*models.User, error) {
|
||||
ret := _m.Called(ctx, sub, iss)
|
||||
|
||||
var r0 *models.User
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *models.User); ok {
|
||||
r0 = rf(ctx, sub, iss)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.User)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, sub, iss)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query
|
||||
func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.User, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
@ -185,13 +208,20 @@ func (_m *Controller) UpdatePassword(ctx context.Context, id int, password strin
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateProfile provides a mock function with given fields: ctx, u
|
||||
func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User) error {
|
||||
ret := _m.Called(ctx, u)
|
||||
// UpdateProfile provides a mock function with given fields: ctx, u, cols
|
||||
func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User, cols ...string) error {
|
||||
_va := make([]interface{}, len(cols))
|
||||
for _i := range cols {
|
||||
_va[_i] = cols[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, u)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.User) error); ok {
|
||||
r0 = rf(ctx, u)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok {
|
||||
r0 = rf(ctx, u, cols...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@ -199,20 +229,20 @@ func (_m *Controller) UpdateProfile(ctx context.Context, u *models.User) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// VerifyPassword provides a mock function with given fields: ctx, username, password
|
||||
func (_m *Controller) VerifyPassword(ctx context.Context, username string, password string) (bool, error) {
|
||||
ret := _m.Called(ctx, username, password)
|
||||
// VerifyPassword provides a mock function with given fields: ctx, usernameOrEmail, password
|
||||
func (_m *Controller) VerifyPassword(ctx context.Context, usernameOrEmail string, password string) (bool, error) {
|
||||
ret := _m.Called(ctx, usernameOrEmail, password)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok {
|
||||
r0 = rf(ctx, username, password)
|
||||
r0 = rf(ctx, usernameOrEmail, password)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, username, password)
|
||||
r1 = rf(ctx, usernameOrEmail, password)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
@ -143,6 +143,29 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) (usermodels.Users,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MatchLocalPassword provides a mock function with given fields: ctx, username, password
|
||||
func (_m *Manager) MatchLocalPassword(ctx context.Context, username string, password string) (*models.User, error) {
|
||||
ret := _m.Called(ctx, username, password)
|
||||
|
||||
var r0 *models.User
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *models.User); ok {
|
||||
r0 = rf(ctx, username, password)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.User)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, username, password)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -171,37 +194,23 @@ func (_m *Manager) UpdatePassword(ctx context.Context, id int, newPassword strin
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateProfile provides a mock function with given fields: ctx, _a1
|
||||
func (_m *Manager) UpdateProfile(ctx context.Context, _a1 *models.User) error {
|
||||
ret := _m.Called(ctx, _a1)
|
||||
// UpdateProfile provides a mock function with given fields: ctx, _a1, col
|
||||
func (_m *Manager) UpdateProfile(ctx context.Context, _a1 *models.User, col ...string) error {
|
||||
_va := make([]interface{}, len(col))
|
||||
for _i := range col {
|
||||
_va[_i] = col[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, _a1)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.User) error); ok {
|
||||
r0 = rf(ctx, _a1)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.User, ...string) error); ok {
|
||||
r0 = rf(ctx, _a1, col...)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// VerifyLocalPassword provides a mock function with given fields: ctx, username, password
|
||||
func (_m *Manager) VerifyLocalPassword(ctx context.Context, username string, password string) (bool, error) {
|
||||
ret := _m.Called(ctx, username, password)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok {
|
||||
r0 = rf(ctx, username, password)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, username, password)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user