Remove dependencies from pkg/oidc to common/dao (#14739)

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2021-04-26 10:56:49 +08:00 committed by GitHub
parent a344b1e17c
commit 5b526b8dc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 76 additions and 67 deletions

View File

@ -39,21 +39,6 @@ var (
ErrRollBackOIDCUser = errors.New("sql: transaction roll back error in oicd_user") ErrRollBackOIDCUser = errors.New("sql: transaction roll back error in oicd_user")
) )
// GetOIDCUserByID ...
func GetOIDCUserByID(id int64) (*models.OIDCUser, error) {
oidcUser := &models.OIDCUser{
ID: id,
}
if err := GetOrmer().Read(oidcUser); err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
return oidcUser, nil
}
// GetUserBySubIss ... // GetUserBySubIss ...
func GetUserBySubIss(sub, issuer string) (*models.User, error) { func GetUserBySubIss(sub, issuer string) (*models.User, error) {
var oidcUsers []models.OIDCUser var oidcUsers []models.OIDCUser
@ -99,19 +84,6 @@ func UpdateOIDCUser(oidcUser *models.OIDCUser) error {
return err return err
} }
// UpdateOIDCUserSecret updates the secret of the OIDC User. The secret in the input parm should be encrypted before
// calling this func
func UpdateOIDCUserSecret(oidcUser *models.OIDCUser) error {
_, err := GetOrmer().Update(oidcUser, "secret")
return err
}
// DeleteOIDCUser ...
func DeleteOIDCUser(id int64) error {
_, err := GetOrmer().QueryTable(&models.OIDCUser{}).Filter("ID", id).Delete()
return err
}
// OnBoardOIDCUser onboard OIDC user // OnBoardOIDCUser onboard OIDC user
// For the api caller, should only care about the ErrDupUser. It could lead to http.StatusConflict. // For the api caller, should only care about the ErrDupUser. It could lead to http.StatusConflict.
func OnBoardOIDCUser(u *models.User) error { func OnBoardOIDCUser(u *models.User) error {

View File

@ -60,11 +60,6 @@ func TestOIDCUserMetaDaoMethods(t *testing.T) {
require.NotNil(t, err) require.NotNil(t, err)
assert.Equal(t, "unable to onboard as empty oidc user", err.Error()) assert.Equal(t, "unable to onboard as empty oidc user", err.Error())
// test get by ID
oidcUser1, err := GetOIDCUserByID(ou111.ID)
require.Nil(t, err)
assert.Equal(t, ou111.UserID, oidcUser1.UserID)
// test get by userID // test get by userID
oidcUser2, err := GetOIDCUserByUserID(user111.UserID) oidcUser2, err := GetOIDCUserByUserID(user111.UserID)
require.Nil(t, err) require.Nil(t, err)
@ -83,11 +78,6 @@ func TestOIDCUserMetaDaoMethods(t *testing.T) {
Secret: "newSecret", Secret: "newSecret",
} }
require.Nil(t, UpdateOIDCUser(meta3)) require.Nil(t, UpdateOIDCUser(meta3))
oidcUser1Update, err := GetOIDCUserByID(ou111.ID)
require.Nil(t, err)
assert.Equal(t, "QWE123123RT1", oidcUser1Update.SubIss)
assert.Equal(t, "newSecret", oidcUser1Update.Secret)
// clear data // clear data
defer func() { defer func() {
_, err := GetOrmer().Raw(`delete from oidc_user`).Exec() _, err := GetOrmer().Raw(`delete from oidc_user`).Exec()

View File

@ -48,6 +48,8 @@ type Controller interface {
Count(ctx context.Context, query *q.Query) (int64, error) Count(ctx context.Context, query *q.Query) (int64, error)
// Get ... // Get ...
Get(ctx context.Context, id int, opt *Option) (*models.User, error) 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)
// Delete ... // Delete ...
Delete(ctx context.Context, id int) error 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 // UpdateProfile update the profile based on the ID and data in the model in parm, only a subset of attributes in the model
@ -75,6 +77,10 @@ type controller struct {
oidcMetaMgr oidc.MetaManager oidcMetaMgr oidc.MetaManager
} }
func (c *controller) GetByName(ctx context.Context, username string) (*models.User, error) {
return c.mgr.GetByName(ctx, username)
}
func (c *controller) SetCliSecret(ctx context.Context, id int, secret string) error { func (c *controller) SetCliSecret(ctx context.Context, id int, secret string) error {
return c.oidcMetaMgr.SetCliSecretByUserID(ctx, id, secret) return c.oidcMetaMgr.SetCliSecretByUserID(ctx, id, secret)
} }

View File

@ -4,11 +4,12 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/pkg/oidc/dao"
"sync" "sync"
"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/common/utils"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
) )
@ -30,8 +31,8 @@ func verifyError(err error) error {
// SecretManager is the interface for store and verify the secret // SecretManager is the interface for store and verify the secret
type SecretManager interface { type SecretManager interface {
// VerifySecret verifies the secret and the token associated with it, it refreshes the token in the DB if it's // VerifySecret verifies the secret and the token associated with it, it refreshes the token in the DB if it's
// refreshed during the verification. It returns a populated user model based on the ID token associated with the secret. // refreshed during the verification.
VerifySecret(ctx context.Context, username string, secret string) (*models.User, error) VerifySecret(ctx context.Context, username string, secret string) (*UserInfo, error)
} }
type keyGetter struct { type keyGetter struct {
@ -61,22 +62,18 @@ func (kg *keyGetter) encryptKey() (string, error) {
var keyLoader = &keyGetter{} var keyLoader = &keyGetter{}
type defaultManager struct { type defaultManager struct {
metaDao dao.MetaDAO
} }
var m SecretManager = &defaultManager{} var m SecretManager = &defaultManager{
metaDao: dao.NewMetaDao(),
}
// VerifySecret verifies the secret and the token associated with it, it refreshes the token in the DB if it's // VerifySecret verifies the secret and the token associated with it, it refreshes the token in the DB if it's
// refreshed during the verification. It returns a populated user model based on the ID token associated with the secret. // refreshed during the verification. It returns a populated user model based on the ID token associated with the secret.
func (dm *defaultManager) VerifySecret(ctx context.Context, username string, secret string) (*models.User, error) { func (dm *defaultManager) VerifySecret(ctx context.Context, username string, secret string) (*UserInfo, error) {
log.Debugf("Verifying the secret for user: %s", username) log.Debugf("Verifying the secret for user: %s", username)
user, err := dao.GetUser(models.User{Username: username}) oidcUser, err := dm.metaDao.GetByUsername(ctx, username)
if err != nil {
return nil, err
}
if user == nil {
return nil, verifyError(fmt.Errorf("user does not exist, name: %s", username))
}
oidcUser, err := dao.GetOIDCUserByUserID(user.UserID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get oidc user info, error: %v", err) return nil, fmt.Errorf("failed to get oidc user info, error: %v", err)
} }
@ -115,7 +112,7 @@ func (dm *defaultManager) VerifySecret(ctx context.Context, username string, sec
} }
encToken, _ := utils.ReversibleEncrypt(string(tb), key) encToken, _ := utils.ReversibleEncrypt(string(tb), key)
oidcUser.Token = encToken oidcUser.Token = encToken
err = dao.UpdateOIDCUser(oidcUser) err = dm.metaDao.Update(ctx, oidcUser)
if err != nil { if err != nil {
log.Errorf("Failed to persist token, user id: %d, error: %v", oidcUser.UserID, err) log.Errorf("Failed to persist token, user id: %d, error: %v", oidcUser.UserID, err)
} }
@ -125,12 +122,10 @@ func (dm *defaultManager) VerifySecret(ctx context.Context, username string, sec
if err != nil { if err != nil {
return nil, verifyError(err) return nil, verifyError(err)
} }
InjectGroupsToUser(info, user) return info, nil
log.Debugf("Secret verification succeed, username: %s", username)
return user, nil
} }
// VerifySecret calls the manager to verify the secret. // VerifySecret calls the manager to verify the secret.
func VerifySecret(ctx context.Context, name string, secret string) (*models.User, error) { func VerifySecret(ctx context.Context, name string, secret string) (*UserInfo, error) {
return m.VerifySecret(ctx, name, secret) return m.VerifySecret(ctx, name, secret)
} }

View File

@ -4,8 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"strconv" "strconv"
"github.com/goharbor/harbor/src/common/models"
) )
import "errors" import "errors"
@ -14,11 +12,17 @@ type fakeVerifier struct {
secret string secret string
} }
func (fv *fakeVerifier) VerifySecret(ctx context.Context, name string, secret string) (*models.User, error) { func (fv *fakeVerifier) VerifySecret(ctx context.Context, name string, secret string) (*UserInfo, error) {
if secret != fv.secret { if secret != fv.secret {
return nil, verifyError(errors.New("mismatch")) return nil, verifyError(errors.New("mismatch"))
} }
return &models.User{UserID: 1, Username: name, Email: fmt.Sprintf("%s@test.local", name)}, nil return &UserInfo{
Username: name,
Email: fmt.Sprintf("%s@test.local", name),
Subject: "subject",
Issuer: "issuer",
}, nil
} }
// SetHardcodeVerifierForTest overwrite the default secret manager for testing. // SetHardcodeVerifierForTest overwrite the default secret manager for testing.

View File

@ -35,7 +35,7 @@ var (
type Manager interface { type Manager interface {
// Get get user by user id // Get get user by user id
Get(ctx context.Context, id int) (*models.User, error) Get(ctx context.Context, id int) (*models.User, error)
// Get get user by username // GetByName get user by username
GetByName(ctx context.Context, username string) (*models.User, error) GetByName(ctx context.Context, username string) (*models.User, error)
// List users according to the query // List users according to the query
List(ctx context.Context, query *q.Query) (models.Users, error) List(ctx context.Context, query *q.Query) (models.Users, error)

View File

@ -24,6 +24,7 @@ import (
"github.com/goharbor/harbor/src/common/api" "github.com/goharbor/harbor/src/common/api"
"github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/local" "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"
"github.com/goharbor/harbor/src/lib/log" "github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/oidc"
@ -38,15 +39,17 @@ var (
reposAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories$`, regexp.QuoteMeta(base))) reposAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories$`, regexp.QuoteMeta(base)))
artifactsAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories/.*/artifacts$`, regexp.QuoteMeta(base))) artifactsAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories/.*/artifacts$`, regexp.QuoteMeta(base)))
tagsAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories/.*/artifacts/.*/tags/.*$`, regexp.QuoteMeta(base))) tagsAPIRe = regexp.MustCompile(fmt.Sprintf(`^%s/projects/.*/repositories/.*/artifacts/.*/tags/.*$`, regexp.QuoteMeta(base)))
uctl = user.Ctl
) )
type oidcCli struct{} type oidcCli struct{}
func (o *oidcCli) Generate(req *http.Request) security.Context { func (o *oidcCli) Generate(req *http.Request) security.Context {
if lib.GetAuthMode(req.Context()) != common.OIDCAuth { ctx := req.Context()
if lib.GetAuthMode(ctx) != common.OIDCAuth {
return nil return nil
} }
logger := log.G(req.Context()) logger := log.G(ctx)
username, secret, ok := req.BasicAuth() username, secret, ok := req.BasicAuth()
if !ok { if !ok {
return nil return nil
@ -54,13 +57,19 @@ func (o *oidcCli) Generate(req *http.Request) security.Context {
if !o.valid(req) { if !o.valid(req) {
return nil return nil
} }
user, err := oidc.VerifySecret(req.Context(), username, secret) info, err := oidc.VerifySecret(ctx, username, secret)
if err != nil { if err != nil {
logger.Errorf("failed to verify secret: %v", err) logger.Errorf("failed to verify secret, username: %s, error: %v", username, err)
return nil return nil
} }
u, err := uctl.GetByName(ctx, username)
if err != nil {
logger.Errorf("failed to get user model, username: %s, error: %v", username, err)
return nil
}
oidc.InjectGroupsToUser(info, u)
logger.Debugf("an OIDC CLI security context generated for request %s %s", req.Method, req.URL.Path) logger.Debugf("an OIDC CLI security context generated for request %s %s", req.Method, req.URL.Path)
return local.NewSecurityContext(user) return local.NewSecurityContext(u)
} }
func (o *oidcCli) valid(req *http.Request) bool { func (o *oidcCli) valid(req *http.Request) bool {

View File

@ -20,9 +20,12 @@ import (
"testing" "testing"
"github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib" "github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/pkg/oidc" "github.com/goharbor/harbor/src/pkg/oidc"
testingUser "github.com/goharbor/harbor/src/testing/controller/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -43,6 +46,13 @@ func TestOIDCCli(t *testing.T) {
// pass // pass
username := "oidcModiferTester" username := "oidcModiferTester"
password := "oidcSecret" password := "oidcSecret"
testCtl := &testingUser.Controller{}
testCtl.On("GetByName", mock.Anything, username).Return(
&models.User{
Username: username,
Email: fmt.Sprintf("%s@test.domain", username),
}, nil)
uctl = testCtl
oidc.SetHardcodeVerifierForTest(password) oidc.SetHardcodeVerifierForTest(password)
req = req.WithContext(lib.WithAuthMode(req.Context(), common.OIDCAuth)) req = req.WithContext(lib.WithAuthMode(req.Context(), common.OIDCAuth))
req.SetBasicAuth(username, password) req.SetBasicAuth(username, password)

View File

@ -97,6 +97,29 @@ func (_m *Controller) Get(ctx context.Context, id int, opt *user.Option) (*model
return r0, r1 return r0, r1
} }
// GetByName provides a mock function with given fields: ctx, username
func (_m *Controller) GetByName(ctx context.Context, username string) (*models.User, error) {
ret := _m.Called(ctx, username)
var r0 *models.User
if rf, ok := ret.Get(0).(func(context.Context, string) *models.User); ok {
r0 = rf(ctx, username)
} 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) error); ok {
r1 = rf(ctx, username)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, query // List provides a mock function with given fields: ctx, query
func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.User, error) { func (_m *Controller) List(ctx context.Context, query *q.Query) ([]*models.User, error) {
ret := _m.Called(ctx, query) ret := _m.Called(ctx, query)