Add LDAP remote certifcate validation

push test

Add unit test for ldap verify cert

remove common.VerifyRemoteCert

Update code with PR review comments

Add change ldaps config and add UT testcase for TLS feature

add ldap verfiy cert checkbox about #3513

Draft harbor ova install guide

Search and import ldap user when add project members

Add unit test case for SearchAndImportUser

ova guide

Add ova install guide

Add ova install guide 2

Add ova install guide 3

Call ValidateLdapConf before search ldap

trim space in username

Remove leading space in openLdap username

Remove doc change in this branch

Update unit test for ldap search and import user

Add test case about ldap verify cert checkbox

Modify ldap testcase
This commit is contained in:
stonezdj 2017-11-01 18:19:58 +08:00
parent a8ba486c3a
commit 16243cfbbc
22 changed files with 242 additions and 26 deletions

View File

@ -11,6 +11,7 @@ LDAP_FILTER=$ldap_filter
LDAP_UID=$ldap_uid
LDAP_SCOPE=$ldap_scope
LDAP_TIMEOUT=$ldap_timeout
LDAP_VERIFY_CERT=true
DATABASE_TYPE=mysql
MYSQL_HOST=$db_host
MYSQL_PORT=$db_port

View File

@ -81,6 +81,10 @@ var (
env: "LDAP_TIMEOUT",
parse: parseStringToInt,
},
common.LDAPVerifyCert: &parser{
env: "LDAP_VERIFY_CERT",
parse: parseStringToBool,
},
common.EmailHost: "EMAIL_HOST",
common.EmailPort: &parser{
env: "EMAIL_PORT",

View File

@ -90,11 +90,16 @@ func TestLoadFromEnv(t *testing.T) {
t.Fatalf("failed to set env: %v", err)
}
if err := os.Setenv("LDAP_VERIFY_CERT", "false"); err != nil {
t.Fatalf("failed to set env: %v", err)
}
cfgs = map[string]interface{}{}
err = LoadFromEnv(cfgs, false)
assert.Nil(t, err)
assert.Equal(t, extEndpoint, cfgs[common.ExtEndpoint])
assert.Equal(t, ldapURL, cfgs[common.LDAPURL])
assert.Equal(t, false, cfgs[common.LDAPVerifyCert])
os.Clearenv()
if err := os.Setenv("LDAP_URL", ldapURL); err != nil {
@ -104,6 +109,10 @@ func TestLoadFromEnv(t *testing.T) {
t.Fatalf("failed to set env: %v", err)
}
if err := os.Setenv("LDAP_VERIFY_CERT", "true"); err != nil {
t.Fatalf("failed to set env: %v", err)
}
cfgs = map[string]interface{}{
common.LDAPURL: "ldap_url",
}
@ -111,4 +120,5 @@ func TestLoadFromEnv(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, extEndpoint, cfgs[common.ExtEndpoint])
assert.Equal(t, "ldap_url", cfgs[common.LDAPURL])
assert.Equal(t, true, cfgs[common.LDAPVerifyCert])
}

View File

@ -48,6 +48,7 @@ const (
LDAPFilter = "ldap_filter"
LDAPScope = "ldap_scope"
LDAPTimeout = "ldap_timeout"
LDAPVerifyCert = "ldap_verify_cert"
TokenServiceURL = "token_service_url"
RegistryURL = "registry_url"
EmailHost = "email_host"

View File

@ -33,6 +33,7 @@ type LDAP struct {
UID string `json:"uid"`
Scope int `json:"scope"`
Timeout int `json:"timeout"` // in second
VerifyCert bool `json:"verify_cert"`
}
// Database ...

View File

@ -24,6 +24,7 @@ type LdapConf struct {
LdapUID string `json:"ldap_uid"`
LdapScope int `json:"ldap_scope"`
LdapConnectionTimeout int `json:"ldap_connection_timeout"`
LdapVerifyCert bool `json:"ldap_verify_cert"`
}
// LdapUser ...

View File

@ -60,6 +60,7 @@ func GetSystemLdapConf() (models.LdapConf, error) {
ldapConfs.LdapUID = ldap.UID
ldapConfs.LdapScope = ldap.Scope
ldapConfs.LdapConnectionTimeout = ldap.Timeout
ldapConfs.LdapVerifyCert = ldap.VerifyCert
// ldapConfs = config.LDAP().URL
// ldapConfs.LdapSearchDn = config.LDAP().SearchDn
@ -205,7 +206,8 @@ func SearchUser(ldapConfs models.LdapConf) ([]models.LdapUser, error) {
for _, ldapEntry := range result.Entries {
var u models.LdapUser
for _, attr := range ldapEntry.Attributes {
val := attr.Values[0]
//OpenLDAP sometimes contains leading space in username
val := strings.TrimSpace(attr.Values[0])
log.Debugf("Current ldap entry attr name: %s\n", attr.Name)
switch strings.ToLower(attr.Name) {
case strings.ToLower(ldapConfs.LdapUID):
@ -337,6 +339,7 @@ func dialLDAP(ldapConfs models.LdapConf) (*goldap.Conn, error) {
var ldap *goldap.Conn
splitLdapURL := strings.Split(ldapConfs.LdapURL, "://")
protocol, hostport := splitLdapURL[0], splitLdapURL[1]
host := strings.Split(hostport, ":")[0]
// Sets a Dial Timeout for LDAP
connectionTimeout := ldapConfs.LdapConnectionTimeout
@ -346,7 +349,8 @@ func dialLDAP(ldapConfs models.LdapConf) (*goldap.Conn, error) {
case "ldap":
ldap, err = goldap.Dial("tcp", hostport)
case "ldaps":
ldap, err = goldap.DialTLS("tcp", hostport, &tls.Config{InsecureSkipVerify: true})
log.Debug("Start to dial ldaps")
ldap, err = goldap.DialTLS("tcp", hostport, &tls.Config{ServerName: host, InsecureSkipVerify: !ldapConfs.LdapVerifyCert})
}
return ldap, err
@ -401,3 +405,38 @@ func searchLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.SearchRes
return result, nil
}
// SearchAndImportUser - Search user in LDAP, if this user exist, import it to database
func SearchAndImportUser(username string) (int64, error) {
var err error
var userID int64
ldapConfs, err := GetSystemLdapConf()
if err != nil {
log.Errorf("Can not get ldap configuration, error %v", err)
return 0, err
}
ldapConfs, err = ValidateLdapConf(ldapConfs)
if err != nil {
log.Errorf("Invalid ldap request, error: %v", err)
return 0, err
}
ldapConfs.LdapFilter = MakeFilter(username, ldapConfs.LdapFilter, ldapConfs.LdapUID)
log.Debugf("Search with LDAP with filter %s", ldapConfs.LdapFilter)
ldapUsers, err := SearchUser(ldapConfs)
if err != nil {
log.Errorf("Can not search ldap, error %v, filter: %s", err, ldapConfs.LdapFilter)
return 0, err
}
if len(ldapUsers) > 0 {
log.Debugf("Importing user %s to local database", ldapUsers[0].Username)
if userID, err = ImportUser(ldapUsers[0]); err != nil {
log.Errorf("Can not import ldap user to local db, error %v", err)
return 0, err
}
}
return userID, err
}

View File

@ -17,6 +17,7 @@ package ldap
import (
//"fmt"
//"strings"
"os"
"testing"
@ -65,6 +66,45 @@ var adminServerLdapTestConfig = map[string]interface{}{
common.AdminInitialPassword: "password",
}
var adminServerDefaultConfigWithVerifyCert = map[string]interface{}{
common.ExtEndpoint: "https://host01.com",
common.AUTHMode: common.LDAPAuth,
common.DatabaseType: "mysql",
common.MySQLHost: "127.0.0.1",
common.MySQLPort: 3306,
common.MySQLUsername: "root",
common.MySQLPassword: "root123",
common.MySQLDatabase: "registry",
common.SQLiteFile: "/tmp/registry.db",
common.SelfRegistration: true,
common.LDAPURL: "ldap://127.0.0.1:389",
common.LDAPSearchDN: "cn=admin,dc=example,dc=com",
common.LDAPSearchPwd: "admin",
common.LDAPBaseDN: "dc=example,dc=com",
common.LDAPUID: "uid",
common.LDAPFilter: "",
common.LDAPScope: 3,
common.LDAPTimeout: 30,
common.LDAPVerifyCert: true,
common.TokenServiceURL: "http://token_service",
common.RegistryURL: "http://registry",
common.EmailHost: "127.0.0.1",
common.EmailPort: 25,
common.EmailUsername: "user01",
common.EmailPassword: "password",
common.EmailFrom: "from",
common.EmailSSL: true,
common.EmailIdentity: "",
common.ProjectCreationRestriction: common.ProCrtRestrAdmOnly,
common.MaxJobWorkers: 3,
common.TokenExpiration: 30,
common.CfgExpiration: 5,
common.AdminInitialPassword: "password",
common.AdmiralEndpoint: "http://www.vmware.com",
common.WithNotary: false,
common.WithClair: false,
}
func TestMain(t *testing.T) {
server, err := test.NewAdminserver(adminServerLdapTestConfig)
if err != nil {
@ -125,6 +165,7 @@ func TestGetSystemLdapConf(t *testing.T) {
}
func TestValidateLdapConf(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
t.Fatalf("failed to get system ldap config %v", err)
@ -138,6 +179,7 @@ func TestValidateLdapConf(t *testing.T) {
}
func TestMakeFilter(t *testing.T) {
testLdapConfig, err := GetSystemLdapConf()
if err != nil {
@ -254,3 +296,26 @@ func TestSearchUser(t *testing.T) {
t.Errorf("unexpected ldap user search result: %s = %s", "ldapUsers[0].Username", ldapUsers[0].Username)
}
}
func TestSearchAndImportUser(t *testing.T) {
userID, err := SearchAndImportUser("test")
if err != nil {
t.Fatalf("Failed on error : %v ", err)
}
if userID <= 0 {
t.Fatalf("userID= %v", userID)
}
}
func TestSearchAndImportUserNotExist(t *testing.T) {
userID, _ := SearchAndImportUser("notexist")
if userID > 0 {
t.Fatal("Can not import a non exist ldap user!")
t.Fail()
}
}

View File

@ -40,6 +40,7 @@ var (
common.LDAPFilter,
common.LDAPScope,
common.LDAPTimeout,
common.LDAPVerifyCert,
common.EmailHost,
common.EmailPort,
common.EmailUsername,
@ -80,6 +81,7 @@ var (
common.EmailSSL,
common.EmailInsecure,
common.SelfRegistration,
common.LDAPVerifyCert,
}
passwordKeys = []string{

View File

@ -17,10 +17,13 @@ package api
import (
"fmt"
"net/http"
"strings"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
ldapUtils "github.com/vmware/harbor/src/common/utils/ldap"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
)
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
@ -158,12 +161,26 @@ func (pma *ProjectMemberAPI) Post() {
var req memberReq
pma.DecodeJSONReq(&req)
username := req.Username
username := strings.TrimSpace(req.Username)
userID := checkUserExists(username)
if userID <= 0 {
log.Warningf("User does not exist, user name: %s", username)
pma.RenderError(http.StatusNotFound, "User does not exist")
return
//check current authorization mode
authMode, err := config.AuthMode()
if err != nil || authMode != "ldap_auth" {
log.Warningf("User does not exist, user name: %s", username)
pma.RenderError(http.StatusNotFound, "User does not exist")
return
}
//search and import user
newUserID, err := ldapUtils.SearchAndImportUser(username)
if err == nil || newUserID > 0 {
userID = int(newUserID)
} else {
log.Warningf("User does not exist, user name: %s", username)
pma.RenderError(http.StatusNotFound, "User does not exist")
return
}
}
rolelist, err := dao.GetUserProjectRoles(userID, projectID)
if err != nil {

View File

@ -173,7 +173,6 @@ func LDAP() (*models.LDAP, error) {
if err != nil {
return nil, err
}
ldap := &models.LDAP{}
ldap.URL = cfg[common.LDAPURL].(string)
ldap.SearchDN = cfg[common.LDAPSearchDN].(string)
@ -183,6 +182,11 @@ func LDAP() (*models.LDAP, error) {
ldap.Filter = cfg[common.LDAPFilter].(string)
ldap.Scope = int(cfg[common.LDAPScope].(float64))
ldap.Timeout = int(cfg[common.LDAPTimeout].(float64))
if cfg[common.LDAPVerifyCert] != nil {
ldap.VerifyCert = cfg[common.LDAPVerifyCert].(bool)
} else {
ldap.VerifyCert = false
}
return ldap, nil
}

View File

@ -64,6 +64,7 @@ export class Configuration {
ldap_timeout: NumberValueItem;
ldap_uid: StringValueItem;
ldap_url: StringValueItem;
ldap_verify_cert: BoolValueItem;
email_host: StringValueItem;
email_identity: StringValueItem;
email_from: StringValueItem;
@ -89,6 +90,7 @@ export class Configuration {
this.ldap_timeout = new NumberValueItem(5, true);
this.ldap_uid = new StringValueItem("", true);
this.ldap_url = new StringValueItem("", true);
this.ldap_verify_cert = new BoolValueItem(true, true);
this.email_host = new StringValueItem("", true);
this.email_identity = new StringValueItem("", true);
this.email_from = new StringValueItem("", true);

View File

@ -111,6 +111,15 @@
<span class="tooltip-content">{{'CONFIG.TOOLTIP.LDAP_SCOPE' | translate}}</span>
</a>
</div>
<div class="form-group">
<label for="ldapVerifyCert">{{'CONFIG.LDAP.VERIFY_CERT' | translate}}</label>
<clr-checkbox name="ldapVerifyCert" id="ldapVerifyCert" [clrChecked]="currentConfig.ldap_verify_cert.value" [clrDisabled]="disabled(currentConfig.ldap_scope)" (clrCheckedChange)="setVerifyCertValue($event)">
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_CERT' | translate}}</span>
</a>
</clr-checkbox>
</div>
</section>
<section class="form-block">
<div class="form-group">

View File

@ -50,6 +50,10 @@ export class ConfigurationAuthComponent {
}
}
setVerifyCertValue($event: any) {
this.currentConfig.ldap_verify_cert.value = $event;
}
disabled(prop: any): boolean {
return !(prop && prop.editable);
}

View File

@ -425,7 +425,8 @@
"TOKEN_EXPIRATION": "The expiration time (in minutes) of a token created by the token service. Default is 30 minutes.",
"PRO_CREATION_RESTRICTION": "The flag to define what users have permission to create projects. By default, everyone can create a project. Set to 'Admin Only' so that only an administrator can create a project.",
"ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.",
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday."
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday.",
"VERIFY_CERT": "Verify Cert from LDAP Server"
},
"LDAP": {
"URL": "LDAP URL",
@ -434,7 +435,8 @@
"BASE_DN": "LDAP Base DN",
"FILTER": "LDAP Filter",
"UID": "LDAP UID",
"SCOPE": "LDAP Scope"
"SCOPE": "LDAP Scope",
"VERIFY_CERT": "LDAP Verify Cert"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",

View File

@ -426,7 +426,8 @@
"TOKEN_EXPIRATION": "El tiempo de expiración (en minutos) del token creado por el servicio de tokens. Por defecto son 30 minutos.",
"PRO_CREATION_RESTRICTION": "Marca para definir qué usuarios tienen permisos para crear proyectos. Por defecto, todos pueden crear proyectos. Seleccione 'Solo Administradores' para que solamente los administradores puedan crear proyectos.",
"ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.",
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday."
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday.",
"VERIFY_CERT": "Verify Cert from LDAP Server"
},
"LDAP": {
"URL": "LDAP URL",
@ -435,7 +436,8 @@
"BASE_DN": "LDAP Base DN",
"FILTER": "LDAP Filtro",
"UID": "LDAP UID",
"SCOPE": "LDAP Ámbito"
"SCOPE": "LDAP Ámbito",
"VERIFY_CERT": "LDAP Verify Cert"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",

View File

@ -425,7 +425,8 @@
"TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间分钟默认为30分钟。",
"PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。",
"ROOT_CERT_DOWNLOAD": "下载镜像库根证书.",
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。"
"SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。".
"VERIFY_CERT": "检查来自LDAP服务端的证书"
},
"LDAP": {
"URL": "LDAP URL",
@ -434,7 +435,8 @@
"BASE_DN": "LDAP基础DN",
"FILTER": "LDAP过滤器",
"UID": "LDAP用户UID的属性",
"SCOPE": "LDAP搜索范围"
"SCOPE": "LDAP搜索范围",
"VERIFY_CERT": "LDAP 检查证书"
},
"SCANNING": {
"TRIGGER_SCAN_ALL_SUCCESS": "启动扫描所有镜像任务成功!",

View File

@ -5,11 +5,12 @@ docker rm -f $NAME 2>/dev/null
docker run --env LDAP_ORGANISATION="Harbor." \
--env LDAP_DOMAIN="example.com" \
--env LDAP_ADMIN_PASSWORD="admin" \
--env LDAP_TLS_VERIFY_CLIENT="never" \
-p 389:389 \
-p 636:636 \
--detach --name $NAME osixia/openldap:1.1.7
sleep 3
sleep 5
docker cp ldap_test.ldif ldap_server:/
docker exec ldap_server ldapadd -x -D "cn=admin,dc=example,dc=com" -w admin -f /ldap_test.ldif -ZZ

View File

@ -24,17 +24,18 @@ Init LDAP
${rc} ${output}= Run And Return Rc And Output ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'
Log ${output}
Sleep 2
Input Text xpath=//*[@id="ldapUrl"] ldap://${output}
Input Text xpath=//*[@id="ldapUrl"] ldaps://${output}
Sleep 1
Input Text xpath=//*[@id="ldapSearchDN"] cn=admin,dc=example,dc=org
Input Text xpath=//*[@id="ldapSearchDN"] cn=admin,dc=example,dc=com
Sleep 1
Input Text xpath=//*[@id="ldapSearchPwd"] admin
Sleep 1
Input Text xpath=//*[@id="ldapBaseDN"] dc=example,dc=org
Input Text xpath=//*[@id="ldapBaseDN"] dc=example,dc=com
Sleep 1
Input Text xpath=//*[@id="ldapUid"] cn
Sleep 1
Capture Page Screenshot
Disable Ldap Verify Cert Checkbox
Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[1]
Sleep 2
Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[3]
@ -45,6 +46,44 @@ Switch To Configure
Click Element xpath=/html/body/harbor-app/harbor-shell/clr-main-container/div/nav/section/section/ul/li[3]/a
Sleep 2
Test Ldap Connection
${rc} ${output}= Run And Return Rc And Output ip addr s eth0 |grep "inet "|awk '{print $2}' |awk -F "/" '{print $1}'
Log ${output}
Sleep 2
Input Text xpath=//*[@id="ldapUrl"] ldaps://${output}
Sleep 1
Input Text xpath=//*[@id="ldapSearchDN"] cn=admin,dc=example,dc=com
Sleep 1
Input Text xpath=//*[@id="ldapSearchPwd"] admin
Sleep 1
Input Text xpath=//*[@id="ldapBaseDN"] dc=example,dc=com
Sleep 1
Input Text xpath=//*[@id="ldapUid"] cn
Sleep 1
# default is checked, click test connection to verify fail as no cert.
Click Element xpath=${test_ldap_xpath}
Sleep 1
Wait Until Page Contains Failed to verify LDAP server with error
Sleep 5
Disable Ldap Verify Cert Checkbox
# ldap checkbox unchecked, click test connection to verify success.
Sleep 1
Click Element xpath=${test_ldap_xpath}
Capture Page Screenshot
Wait Until Page Contains Connection to LDAP server is verified timeout=15
Disable Ldap Verify Cert Checkbox
Mouse Down xpath=//*[@id="clr-checkbox-ldapVerifyCert"]
Mouse Up xpath=//*[@id="clr-checkbox-ldapVerifyCert"]
Sleep 2
Capture Page Screenshot
Ldap Verify Cert Checkbox Should Be Disabled
Ldap Verify Cert Checkbox Should Be Disabled
Checkbox Should Not Be Selected xpath=//*[@id="clr-checkbox-ldapVerifyCert"]
Set Pro Create Admin Only
#set limit to admin only
Sleep 2

View File

@ -17,4 +17,5 @@ Documentation This resource provides any keywords related to the Harbor private
*** Variables ***
${project_create_xpath} //project//div[@class="option-left"]/button
${self_reg_xpath} //input[@id="clr-checkbox-selfReg"]
${self_reg_xpath} //input[@id="clr-checkbox-selfReg"]
${test_ldap_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/config/div/div/div/button[3]

View File

@ -71,11 +71,12 @@ Switch To LDAP
Config Harbor cfg auth=ldap_auth http_proxy=https
Prepare
Up Harbor
${rc}= Run And Return Rc docker pull vmware/harbor-ldap-test:1.1.1
${rc}= Run And Return Rc docker pull osixia/openldap:1.1.7
Log ${rc}
Should Be Equal As Integers ${rc} 0
${rc}= Run And Return Rc docker run --name ldap-container -p 389:389 --detach vmware/harbor-ldap-test:1.1.1
${rc} ${output}= Run And Return Rc And Output cd tests && ./ldapprepare.sh
Log ${rc}
Log ${output}
Should Be Equal As Integers ${rc} 0
${rc} ${output}= Run And Return Rc And Output docker ps
Log ${output}

View File

@ -328,30 +328,38 @@ Test Case - Admin Push Signed Image
Test Case - Admin Push Un-Signed Image
${rc} ${output}= Run And Return Rc And Output docker push ${ip}/library/hello-world:latest
Log To Console ${output}
Test Case - Ldap Sign in and out
Test Case - Ldap Verify Cert
Switch To LDAP
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To Configure
Test Ldap Connection
Close Browser
Test Case - Ldap Sign in and out
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} %{HARBOR_ADMIN} %{HARBOR_PASSWORD}
Switch To Configure
Init LDAP
Logout Harbor
Sign In Harbor ${HARBOR_URL} user001 user001
Sign In Harbor ${HARBOR_URL} test 123456
Close Browser
Test Case - Ldap User Create Project
Init Chrome Driver
${d}= Get Current Date result_format=%m%s
Sign In Harbor ${HARBOR_URL} user001 user001
Sign In Harbor ${HARBOR_URL} test 123456
Create An New Project project${d}
Close Browser
Test Case - Ldap User Push An Image
Init Chrome Driver
${d}= Get Current Date result_format=%m%s
Sign In Harbor ${HARBOR_URL} user001 user001
Sign In Harbor ${HARBOR_URL} test 123456
Create An New Project project${d}
Push Image ${ip} user001 user001 project${d} hello-world:latest
Push Image ${ip} test 123456 project${d} hello-world:latest
Go Into Project project${d}
Wait Until Page Contains project${d}/hello-world
Close Browser