mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-30 06:18:02 +02:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
55243f274c
@ -20,6 +20,12 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
# If you want run this script on Mac OS X,
|
||||
# I suggest you install gnu-sed (whth --with-default-names option).
|
||||
# $ brew install gnu-sed --with-default-names
|
||||
# Reference:
|
||||
# http://stackoverflow.com/a/27834828/3167471
|
||||
|
||||
#remove space
|
||||
echo "Remove space.."
|
||||
sed 's/ \+/ /g' -i /tmp/harbor.app.temp.js
|
||||
|
@ -46,6 +46,27 @@ type ReplicationReq struct {
|
||||
TagList []string `json:"tags"`
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (rj *ReplicationJob) Prepare() {
|
||||
rj.authenticate()
|
||||
}
|
||||
|
||||
func (rj *ReplicationJob) authenticate() {
|
||||
cookie, err := rj.Ctx.Request.Cookie(models.UISecretCookie)
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
log.Errorf("failed to get cookie %s: %v", models.UISecretCookie, err)
|
||||
rj.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
|
||||
if err == http.ErrNoCookie {
|
||||
rj.CustomAbort(http.StatusUnauthorized, "")
|
||||
}
|
||||
|
||||
if cookie.Value != config.UISecret() {
|
||||
rj.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Post creates replication jobs according to the policy.
|
||||
func (rj *ReplicationJob) Post() {
|
||||
var data ReplicationReq
|
||||
|
@ -147,7 +147,14 @@ func (ra *RepJobAPI) GetLog() {
|
||||
ra.CustomAbort(http.StatusBadRequest, "id is nil")
|
||||
}
|
||||
|
||||
resp, err := http.Get(buildJobLogURL(strconv.FormatInt(ra.jobID, 10)))
|
||||
req, err := http.NewRequest("GET", buildJobLogURL(strconv.FormatInt(ra.jobID, 10)), nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create a request: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
addAuthentication(req)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get log for job %d: %v", ra.jobID, err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
|
30
api/utils.go
30
api/utils.go
@ -115,7 +115,14 @@ func TriggerReplication(policyID int64, repository string,
|
||||
|
||||
url := buildReplicationURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -188,7 +195,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
|
||||
url := buildReplicationActionURL()
|
||||
|
||||
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addAuthentication(req)
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -207,6 +223,16 @@ func postReplicationAction(policyID int64, acton string) error {
|
||||
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
||||
}
|
||||
|
||||
func addAuthentication(req *http.Request) {
|
||||
if req != nil {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: models.UISecretCookie,
|
||||
// TODO read secret from config
|
||||
Value: os.Getenv("UI_SECRET"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildReplicationURL() string {
|
||||
url := getJobServiceURL()
|
||||
return fmt.Sprintf("%s/api/jobs/replication", url)
|
||||
|
@ -112,6 +112,7 @@ func clearUp(username string) {
|
||||
}
|
||||
|
||||
const username string = "Tester01"
|
||||
const password string = "Abc12345"
|
||||
const projectName string = "test_project"
|
||||
const repoTag string = "test1.1"
|
||||
const repoTag2 string = "test1.2"
|
||||
@ -157,7 +158,7 @@ func TestRegister(t *testing.T) {
|
||||
user := models.User{
|
||||
Username: username,
|
||||
Email: "tester01@vmware.com",
|
||||
Password: "Abc12345",
|
||||
Password: password,
|
||||
Realname: "tester01",
|
||||
Comment: "register",
|
||||
}
|
||||
@ -184,6 +185,41 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUserPassword(t *testing.T) {
|
||||
nonExistUser := models.User{
|
||||
Username: "non-exist",
|
||||
}
|
||||
correctUser := models.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
wrongPwd := models.User{
|
||||
Username: username,
|
||||
Password: "wrong",
|
||||
}
|
||||
u, err := CheckUserPassword(nonExistUser)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
t.Errorf("Expected nil for Non exist user, but actual: %+v", u)
|
||||
}
|
||||
u, err = CheckUserPassword(wrongPwd)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
t.Errorf("Expected nil for user with wrong password, but actual: %+v", u)
|
||||
}
|
||||
u, err = CheckUserPassword(correctUser)
|
||||
if err != nil {
|
||||
t.Errorf("Failed in CheckUserPassword: %v", err)
|
||||
}
|
||||
if u == nil {
|
||||
t.Errorf("User should not be nil for correct user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserExists(t *testing.T) {
|
||||
var exists bool
|
||||
var err error
|
||||
@ -672,6 +708,21 @@ func TestAddProjectMember(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateProjectMember(t *testing.T) {
|
||||
err := UpdateProjectMember(currentProject.ProjectID, 1, models.GUEST)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in UpdateProjectMember: %v", err)
|
||||
}
|
||||
roles, err := GetUserProjectRoles(1, currentProject.ProjectID)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUserProjectRoles: %v", err)
|
||||
}
|
||||
if roles[0].Name != "guest" {
|
||||
t.Errorf("The user with ID 1 is not guest role after update, the acutal role: %s", roles[0].Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteProjectMember(t *testing.T) {
|
||||
err := DeleteProjectMember(currentProject.ProjectID, 1)
|
||||
if err != nil {
|
||||
@ -688,6 +739,23 @@ func TestDeleteProjectMember(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRoleByID(t *testing.T) {
|
||||
r, err := GetRoleByID(models.PROJECTADMIN)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to call GetRoleByID: %v", err)
|
||||
}
|
||||
if r == nil || r.Name != "projectAdmin" || r.RoleCode != "MDRWS" {
|
||||
t.Errorf("Role does not match for role id: %d, actual: %+v", models.PROJECTADMIN, r)
|
||||
}
|
||||
r, err = GetRoleByID(9999)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to call GetRoleByID: %v", err)
|
||||
}
|
||||
if r != nil {
|
||||
t.Errorf("Role should nil for non-exist id 9999, actual: %+v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToggleAdminRole(t *testing.T) {
|
||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||
if err != nil {
|
||||
|
21
dao/user.go
21
dao/user.go
@ -111,7 +111,7 @@ func ListUsers(query models.User) ([]models.User, error) {
|
||||
// ToggleUserAdminRole gives a user admin role.
|
||||
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||
o := GetOrmer()
|
||||
queryParams := make([]interface{}, 1)
|
||||
queryParams := make([]interface{}, 1)
|
||||
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
||||
queryParams = append(queryParams, hasAdmin)
|
||||
queryParams = append(queryParams, userID)
|
||||
@ -185,37 +185,24 @@ func UpdateUserResetUUID(u models.User) error {
|
||||
func CheckUserPassword(query models.User) (*models.User, error) {
|
||||
|
||||
currentUser, err := GetUser(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if currentUser == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0`
|
||||
|
||||
sql := `select user_id, username, salt from user where deleted = 0 and username = ? and password = ?`
|
||||
queryParam := make([]interface{}, 1)
|
||||
|
||||
if query.UserID != 0 {
|
||||
sql += ` and password = ? and user_id = ?`
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
queryParam = append(queryParam, query.UserID)
|
||||
} else {
|
||||
sql += ` and username = ? and password = ?`
|
||||
queryParam = append(queryParam, currentUser.Username)
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
}
|
||||
queryParam = append(queryParam, currentUser.Username)
|
||||
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
|
||||
o := GetOrmer()
|
||||
var user []models.User
|
||||
|
||||
n, err := o.Raw(sql, queryParam).QueryRows(&user)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
log.Warning("User principal does not match password. Current:", currentUser)
|
||||
return nil, nil
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
@ -33,6 +34,11 @@ func Encrypt(content string, salt string) string {
|
||||
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
|
||||
}
|
||||
|
||||
const (
|
||||
// EncryptHeaderV1 ...
|
||||
EncryptHeaderV1 = "<enc-v1>"
|
||||
)
|
||||
|
||||
// ReversibleEncrypt encrypts the str with aes/base64
|
||||
func ReversibleEncrypt(str, key string) (string, error) {
|
||||
keyBytes := []byte(key)
|
||||
@ -50,12 +56,26 @@ func ReversibleEncrypt(str, key string) (string, error) {
|
||||
|
||||
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||
cfb.XORKeyStream(cipherText[aes.BlockSize:], []byte(str))
|
||||
encrypted := base64.StdEncoding.EncodeToString(cipherText)
|
||||
encrypted := EncryptHeaderV1 + base64.StdEncoding.EncodeToString(cipherText)
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// ReversibleDecrypt decrypts the str with aes/base64
|
||||
// ReversibleDecrypt decrypts the str with aes/base64 or base 64 depending on "header"
|
||||
func ReversibleDecrypt(str, key string) (string, error) {
|
||||
if strings.HasPrefix(str, EncryptHeaderV1) {
|
||||
str = str[len(EncryptHeaderV1):]
|
||||
return decryptAES(str, key)
|
||||
}
|
||||
//fallback to base64
|
||||
return decodeB64(str)
|
||||
}
|
||||
|
||||
func decodeB64(str string) (string, error) {
|
||||
cipherText, err := base64.StdEncoding.DecodeString(str)
|
||||
return string(cipherText), err
|
||||
}
|
||||
|
||||
func decryptAES(str, key string) (string, error) {
|
||||
keyBytes := []byte(key)
|
||||
var block cipher.Block
|
||||
var cipherText []byte
|
||||
|
@ -16,6 +16,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -73,6 +75,9 @@ func TestReversibleEncrypt(t *testing.T) {
|
||||
if encrypted == password {
|
||||
t.Errorf("Encrypted password is identical to the original")
|
||||
}
|
||||
if !strings.HasPrefix(encrypted, EncryptHeaderV1) {
|
||||
t.Errorf("Encrypted password does not have v1 header")
|
||||
}
|
||||
decrypted, err := ReversibleDecrypt(encrypted, key)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decrypt: %v", err)
|
||||
@ -80,4 +85,13 @@ func TestReversibleEncrypt(t *testing.T) {
|
||||
if decrypted != password {
|
||||
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
||||
}
|
||||
//Test b64 for backward compatibility
|
||||
b64password := base64.StdEncoding.EncodeToString([]byte(password))
|
||||
decrypted, err = ReversibleDecrypt(b64password, key)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decrypt: %v", err)
|
||||
}
|
||||
if decrypted != password {
|
||||
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user