mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-25 10:07:43 +01:00
Merge pull request #7244 from wy65701436/oidc_user
add the dao funcs for OIDC onboard user
This commit is contained in:
commit
0c171c642b
@ -13,6 +13,25 @@ CREATE TABLE robot (
|
|||||||
|
|
||||||
CREATE TRIGGER robot_update_time_at_modtime BEFORE UPDATE ON robot FOR EACH ROW EXECUTE PROCEDURE update_update_time_at_column();
|
CREATE TRIGGER robot_update_time_at_modtime BEFORE UPDATE ON robot FOR EACH ROW EXECUTE PROCEDURE update_update_time_at_column();
|
||||||
|
|
||||||
|
CREATE TABLE oidc_user (
|
||||||
|
id SERIAL NOT NULL,
|
||||||
|
user_id int NOT NULL,
|
||||||
|
secret varchar(255) NOT NULL,
|
||||||
|
/*
|
||||||
|
Subject and Issuer
|
||||||
|
Subject: Subject Identifier.
|
||||||
|
Issuer: Issuer Identifier for the Issuer of the response.
|
||||||
|
The sub (subject) and iss (issuer) Claims, used together, are the only Claims that an RP can rely upon as a stable identifier for the End-User
|
||||||
|
*/
|
||||||
|
subiss varchar(255) NOT NULL,
|
||||||
|
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
update_time timestamp default CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE (subiss)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER odic_user_update_time_at_modtime BEFORE UPDATE ON oidc_user FOR EACH ROW EXECUTE PROCEDURE update_update_time_at_column();
|
||||||
|
|
||||||
/*add master role*/
|
/*add master role*/
|
||||||
INSERT INTO role (role_code, name) VALUES ('DRWS', 'master');
|
INSERT INTO role (role_code, name) VALUES ('DRWS', 'master');
|
||||||
|
|
||||||
|
168
src/common/dao/oidc_user.go
Normal file
168
src/common/dao/oidc_user.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrDupUser ...
|
||||||
|
ErrDupUser = errors.New("sql: duplicate user in harbor_user")
|
||||||
|
|
||||||
|
// ErrRollBackUser ...
|
||||||
|
ErrRollBackUser = errors.New("sql: transaction roll back error in harbor_user")
|
||||||
|
|
||||||
|
// ErrDupOIDCUser ...
|
||||||
|
ErrDupOIDCUser = errors.New("sql: duplicate user in oicd_user")
|
||||||
|
|
||||||
|
// ErrRollBackOIDCUser ...
|
||||||
|
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 ...
|
||||||
|
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 ...
|
||||||
|
func UpdateOIDCUser(oidcUser *models.OIDCUser) error {
|
||||||
|
oidcUser.UpdateTime = time.Now()
|
||||||
|
_, err := GetOrmer().Update(oidcUser)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteOIDCUser ...
|
||||||
|
func DeleteOIDCUser(id int64) error {
|
||||||
|
_, err := GetOrmer().QueryTable(&models.OIDCUser{}).Filter("ID", id).Delete()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBoardOIDCUser onboard OIDC user
|
||||||
|
// For the api caller, should only care about the ErrDupUser. It could lead to http.StatusConflict.
|
||||||
|
func OnBoardOIDCUser(u *models.User) error {
|
||||||
|
if u.OIDCUserMeta == nil {
|
||||||
|
return errors.New("unable to onboard as empty oidc user")
|
||||||
|
}
|
||||||
|
|
||||||
|
o := orm.NewOrm()
|
||||||
|
err := o.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var errInsert error
|
||||||
|
|
||||||
|
// insert user
|
||||||
|
now := time.Now()
|
||||||
|
u.CreationTime = now
|
||||||
|
userID, err := o.Insert(u)
|
||||||
|
if err != nil {
|
||||||
|
errInsert = err
|
||||||
|
log.Errorf("fail to insert user, %v", err)
|
||||||
|
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||||
|
errInsert = errors.Wrap(errInsert, ErrDupUser.Error())
|
||||||
|
}
|
||||||
|
err := o.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("fail to rollback when to onboard oidc user, %v", err)
|
||||||
|
errInsert = errors.Wrap(errInsert, err.Error())
|
||||||
|
return errors.Wrap(errInsert, ErrRollBackUser.Error())
|
||||||
|
}
|
||||||
|
return errInsert
|
||||||
|
|
||||||
|
}
|
||||||
|
u.UserID = int(userID)
|
||||||
|
u.OIDCUserMeta.UserID = int(userID)
|
||||||
|
|
||||||
|
// insert oidc user
|
||||||
|
now = time.Now()
|
||||||
|
u.OIDCUserMeta.CreationTime = now
|
||||||
|
_, err = o.Insert(u.OIDCUserMeta)
|
||||||
|
if err != nil {
|
||||||
|
errInsert = err
|
||||||
|
log.Errorf("fail to insert oidc user, %v", err)
|
||||||
|
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||||
|
errInsert = errors.Wrap(errInsert, ErrDupOIDCUser.Error())
|
||||||
|
}
|
||||||
|
err := o.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
errInsert = errors.Wrap(errInsert, err.Error())
|
||||||
|
return errors.Wrap(errInsert, ErrRollBackOIDCUser.Error())
|
||||||
|
}
|
||||||
|
return errInsert
|
||||||
|
}
|
||||||
|
err = o.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("fail to commit when to onboard oidc user, %v", err)
|
||||||
|
return fmt.Errorf("fail to commit when to onboard oidc user, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
186
src/common/dao/oidc_user_test.go
Normal file
186
src/common/dao/oidc_user_test.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// 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"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOIDCUserMetaDaoMethods(t *testing.T) {
|
||||||
|
|
||||||
|
user111 := models.User{
|
||||||
|
Username: "user111",
|
||||||
|
Email: "user111@email.com",
|
||||||
|
}
|
||||||
|
user222 := models.User{
|
||||||
|
Username: "user222",
|
||||||
|
Email: "user222@email.com",
|
||||||
|
}
|
||||||
|
userEmptyOuMeta := models.User{
|
||||||
|
Username: "userEmptyOuMeta",
|
||||||
|
Email: "userEmptyOuMeta@email.com",
|
||||||
|
}
|
||||||
|
ou111 := models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT1",
|
||||||
|
Secret: "QWEQWE1",
|
||||||
|
}
|
||||||
|
ou222 := models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT2",
|
||||||
|
Secret: "QWEQWE2",
|
||||||
|
}
|
||||||
|
|
||||||
|
// onboard OIDC ...
|
||||||
|
user111.OIDCUserMeta = &ou111
|
||||||
|
err := OnBoardOIDCUser(&user111)
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer CleanUser(int64(user111.UserID))
|
||||||
|
user222.OIDCUserMeta = &ou222
|
||||||
|
err = OnBoardOIDCUser(&user222)
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer CleanUser(int64(user222.UserID))
|
||||||
|
|
||||||
|
// empty OIDC user meta ...
|
||||||
|
err = OnBoardOIDCUser(&userEmptyOuMeta)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
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
|
||||||
|
oidcUser2, err := GetOIDCUserByUserID(user111.UserID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, "QWE123123RT1", oidcUser2.SubIss)
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
UserID: ou111.UserID,
|
||||||
|
SubIss: "newSub",
|
||||||
|
}
|
||||||
|
require.Nil(t, UpdateOIDCUser(meta3))
|
||||||
|
oidcUser1Update, err := GetOIDCUserByID(ou111.ID)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, "newSub", oidcUser1Update.SubIss)
|
||||||
|
|
||||||
|
user, err := GetUserBySubIss("new", "Sub")
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, "user111", user.Username)
|
||||||
|
|
||||||
|
// clear data
|
||||||
|
defer func() {
|
||||||
|
_, err := GetOrmer().Raw(`delete from oidc_user`).Exec()
|
||||||
|
require.Nil(t, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCOnboard(t *testing.T) {
|
||||||
|
user333 := models.User{
|
||||||
|
Username: "user333",
|
||||||
|
Email: "user333@email.com",
|
||||||
|
}
|
||||||
|
user555 := models.User{
|
||||||
|
Username: "user555",
|
||||||
|
Email: "user555@email.com",
|
||||||
|
}
|
||||||
|
user666 := models.User{
|
||||||
|
Username: "user666",
|
||||||
|
Email: "user666@email.com",
|
||||||
|
}
|
||||||
|
userDup := models.User{
|
||||||
|
Username: "user333",
|
||||||
|
Email: "userDup@email.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
ou333 := &models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT3",
|
||||||
|
Secret: "QWEQWE333",
|
||||||
|
}
|
||||||
|
ou555 := &models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT5",
|
||||||
|
Secret: "QWEQWE555",
|
||||||
|
}
|
||||||
|
ouDup := &models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT3",
|
||||||
|
Secret: "QWEQWE333",
|
||||||
|
}
|
||||||
|
ouDupSub := &models.OIDCUser{
|
||||||
|
SubIss: "QWE123123RT3",
|
||||||
|
Secret: "ouDupSub",
|
||||||
|
}
|
||||||
|
|
||||||
|
// data prepare ...
|
||||||
|
user333.OIDCUserMeta = ou333
|
||||||
|
err := OnBoardOIDCUser(&user333)
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer CleanUser(int64(user333.UserID))
|
||||||
|
|
||||||
|
// duplicate user -- ErrDupRows
|
||||||
|
// userDup is duplicate with user333
|
||||||
|
userDup.OIDCUserMeta = ou555
|
||||||
|
err = OnBoardOIDCUser(&userDup)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Contains(t, err.Error(), ErrDupUser.Error())
|
||||||
|
exist, err := UserExists(userDup, "email")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.False(t, exist)
|
||||||
|
|
||||||
|
// duplicate OIDC user -- ErrDupRows
|
||||||
|
// ouDup is duplicate with ou333
|
||||||
|
user555.OIDCUserMeta = ouDup
|
||||||
|
err = OnBoardOIDCUser(&user555)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Contains(t, err.Error(), ErrDupOIDCUser.Error())
|
||||||
|
exist, err = UserExists(user555, "username")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.False(t, exist)
|
||||||
|
|
||||||
|
// success
|
||||||
|
user555.OIDCUserMeta = ou555
|
||||||
|
err = OnBoardOIDCUser(&user555)
|
||||||
|
require.Nil(t, err)
|
||||||
|
exist, err = UserExists(user555, "username")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.True(t, exist)
|
||||||
|
defer CleanUser(int64(user555.UserID))
|
||||||
|
|
||||||
|
// duplicate OIDC user's sub -- ErrDupRows
|
||||||
|
// ouDup is duplicate with ou333
|
||||||
|
user666.OIDCUserMeta = ouDupSub
|
||||||
|
err = OnBoardOIDCUser(&user666)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Contains(t, err.Error(), ErrDupOIDCUser.Error())
|
||||||
|
exist, err = UserExists(user666, "username")
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.False(t, exist)
|
||||||
|
|
||||||
|
// clear data
|
||||||
|
defer func() {
|
||||||
|
_, err := GetOrmer().Raw(`delete from oidc_user`).Exec()
|
||||||
|
require.Nil(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
@ -38,5 +38,6 @@ func init() {
|
|||||||
new(UserGroup),
|
new(UserGroup),
|
||||||
new(AdminJob),
|
new(AdminJob),
|
||||||
new(JobLog),
|
new(JobLog),
|
||||||
new(Robot))
|
new(Robot),
|
||||||
|
new(OIDCUser))
|
||||||
}
|
}
|
||||||
|
20
src/common/models/oidc_user.go
Normal file
20
src/common/models/oidc_user.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OIDCUser ...
|
||||||
|
type OIDCUser struct {
|
||||||
|
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||||
|
UserID int `orm:"column(user_id)" json:"user_id"`
|
||||||
|
Secret string `orm:"column(secret)" json:"secret"`
|
||||||
|
SubIss string `orm:"column(subiss)" json:"subiss"`
|
||||||
|
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||||
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName ...
|
||||||
|
func (o *OIDCUser) TableName() string {
|
||||||
|
return "oidc_user"
|
||||||
|
}
|
@ -41,6 +41,7 @@ type User struct {
|
|||||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
GroupList []*UserGroup `orm:"-" json:"-"`
|
GroupList []*UserGroup `orm:"-" json:"-"`
|
||||||
|
OIDCUserMeta *OIDCUser `orm:"-" json:"oidc_user_meta,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserQuery ...
|
// UserQuery ...
|
||||||
|
@ -138,4 +138,5 @@ func TestAll(t *testing.T) {
|
|||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
||||||
assert.Equal(int(404), w.Code, "GET v2/noproject/manifests/1.0 should get a 404 response")
|
assert.Equal(int(404), w.Code, "GET v2/noproject/manifests/1.0 should get a 404 response")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user