Merge remote-tracking branch 'upstream/dev' into dev-volume-info

This commit is contained in:
kunw 2016-11-02 12:46:57 +08:00
commit 560b41b5e6
146 changed files with 3059 additions and 283 deletions

View File

@ -86,7 +86,7 @@ script:
- docker-compose -f make/docker-compose.test.yml down
- docker-compose -f make/docker-compose.yml up -d
- docker-compose -f make/dev/docker-compose.yml up -d
- docker ps
- go run tests/startuptest.go http://localhost/

View File

@ -25,7 +25,7 @@
#
# package_online:
# prepare online install package
# for example: make package_online -e \
# for example: make package_online -e DEVFLAG=false\
# REGISTRYSERVER=reg-bj.eng.vmware.com \
# REGISTRYPROJECTNAME=harborrelease
#
@ -33,13 +33,13 @@
# prepare offline install package
#
# pushimage: push Harbor images to specific registry server
# for example: make pushimage -e REGISTRYUSER=admin \
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
# REGISTRYPASSWORD=***** \
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
# REGISTRYPROJECTNAME=harborrelease
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
# this value will push images directly to dockerhub.
# make pushimage -e REGISTRYUSER=vmware \
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
# REGISTRYPASSWORD=***** \
# REGISTRYPROJECTNAME=vmware
#
@ -63,7 +63,7 @@
# files with specific TAG.
# By default DEVFLAG=true, if you want to release new version of Harbor, \
# should setting the flag to false.
# make XXXX -e DEVFLAG=flase
# make XXXX -e DEVFLAG=false
SHELL := /bin/bash
BUILDPATH=$(CURDIR)
@ -99,11 +99,13 @@ GODEP=$(GOTEST) -i
GOFMT=gofmt -w
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
GOBUILDPATH=$(GOBASEPATH)/harbor
GOBUILDPATH_UI=$(GOBUILDPATH)/ui
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/jobservice
GOIMAGEBUILDCMD=/usr/local/go/bin/go
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/ui
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/jobservice
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
# binary
UISOURCECODE=$(SRCPATH)/ui
@ -139,6 +141,7 @@ DOCKERIMAGENAME_DB=vmware/harbor-db
# docker-compose files
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
DOCKERCOMPOSEFILENAME=docker-compose.yml
# version prepare
@ -166,10 +169,10 @@ PUSHSCRIPTNAME=pushimage.sh
REGISTRYUSER=user
REGISTRYPASSWORD=default
version:
@$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME)
if [ "$(DEVFLAG)" = "false" ] ; then \
$(SEDCMD) -i 's/version=\"{{.Version}}\"/version=\"$(VERSIONTAG)\"/' -i $(VERSIONFILEPATH)/$(VERSIONFILENAME) ; \
fi
check_environment:
@$(MAKEPATH)/$(CHECKENVCMD)
@ -194,11 +197,11 @@ compile_golangimage:
@echo "compiling binary for ui (golang image)..."
@echo $(GOBASEPATH)
@echo $(GOBUILDPATH)
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
@echo "Done."
@echo "compiling binary for jobservice (golang image)..."
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
@echo "Done."
compile:check_environment $(COMPILETAG)
@ -207,15 +210,11 @@ prepare:
@echo "preparing..."
$(MAKEPATH)/$(PREPARECMD) -conf $(CONFIGPATH)/$(CONFIGFILE)
build_common: prepare version
build_common: version
@echo "buildging db container for photon..."
cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
@echo "Done."
@echo "pulling nginx and registry..."
$(DOCKERPULL) registry:2.5.0
$(DOCKERPULL) nginx:1.9
build_photon: build_common
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
@ -223,16 +222,15 @@ build_ubuntu: build_common
make -f $(MAKEFILEPATH_UBUNTU)/Makefile build -e DEVFLAG=$(DEVFLAG)
build: build_$(BASEIMAGE)
modify_composefile:
@echo "preparing tag:$(VERSIONTAG) docker-compose file..."
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml
@$(SEDCMD) -i 's/image\: vmware.*/&:$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
@$(SEDCMD) -i 's/image\: vmware.*/&:$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
install: compile build modify_composefile
install: compile build prepare modify_composefile
@echo "loading harbor images..."
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.$(VERSIONTAG).yml up -d
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d
@echo "Install complete. You can visit harbor now."
package_online: modify_composefile
@ -247,7 +245,7 @@ package_online: modify_composefile
@$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/ubuntu \
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
--exclude=$(HARBORPKG)/dev --exclude=docker-compose.yml \
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
--exclude=$(HARBORPKG)/checkenv.sh \
--exclude=$(HARBORPKG)/jsminify.sh \
--exclude=$(HARBORPKG)/pushimage.sh \
@ -262,18 +260,23 @@ package_offline: compile build modify_composefile
@cp LICENSE $(HARBORPKG)/LICENSE
@cp NOTICE $(HARBORPKG)/NOTICE
@echo "pulling nginx and registry..."
$(DOCKERPULL) registry:2.5.0
$(DOCKERPULL) nginx:1.9
@echo "saving harbor docker image"
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
nginx:1.9.0 registry:2.5.0
nginx:1.9 registry:2.5.0
@$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
--exclude=$(HARBORPKG)/common/db --exclude=$(HARBORPKG)/ubuntu \
--exclude=$(HARBORPKG)/photon --exclude=$(HARBORPKG)/kubernetes \
--exclude=$(HARBORPKG)/dev --exclude=docker-compose.yml \
--exclude=$(HARBORPKG)/dev --exclude=$(DOCKERCOMPOSETPLFILENAME) \
--exclude=$(HARBORPKG)/checkenv.sh \
--exclude=$(HARBORPKG)/jsminify.sh \
--exclude=$(HARBORPKG)/pushimage.sh \
@ -343,9 +346,17 @@ cleanpackage:
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ] ; \
then rm $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ; fi
.PHONY: clean
clean: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
.PHONY: cleanall
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
clean:
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
@echo " file, specific version tag, online and offline install package"
@echo " make cleanbinary: remove ui and jobservice binary"
@echo " make cleanimage: remove Harbor images"
@echo " make cleandockercomposefile: remove specific version docker-compose"
@echo " make cleanversiontag: cleanpackageremove specific version tag"
@echo " make cleanpackage: remove online and offline install package"
all: install

View File

@ -32,7 +32,14 @@ On an Internet connected host, Harbor can be easily installed via docker-compose
2. Edit the file **make/harbor.cfg**, make necessary configuration changes such as hostname, admin password and mail server. Refer to [Installation and Configuration Guide](docs/installation_guide.md) for more info.
3. Install Harbor with the following commands. Note that the docker-compose process can take a while.
3. Install Harbor with the following methods. Note that the build container images process can take a while.
I: Automation Install
```sh
$ make install
```
II: Manual Install
```sh
$ cd make
@ -42,6 +49,8 @@ On an Internet connected host, Harbor can be easily installed via docker-compose
Generated configuration file: ./config/registry/config.yml
Generated configuration file: ./config/db/env
$ cd dev
$ docker-compose up -d
```

View File

@ -1,11 +0,0 @@
### A faster way to pull images for Chinese Harbor users
By default, Harbor not only build images according to Dockerfile but also pull images from Docker Hub. For the reason we all know, it is difficult for Chinese Harbor users to pull images from the Docker Hub. We put images on daocloud.io platform, we'll put images on other platforms later. If you have difficulty to pull images from Docker Hub, or you think it wastes too much time to build images. We recommend you to use the following way to accelerate the pulling procedure(make sure you're in the harbor diectory):
```
$ cd contrib
$ cp docker-compose.yml.daocloud ../make
$ cd ../make
$ mv docker-compose.yml docker-compose.yml.bak
$ mv docker-compose.yml.daocloud docker-compose.yml
$ docker-compose up -d
```
Then you'll see docker pulling imges faster than before.

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
docs/img/ova/ova01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
docs/img/ova/ova02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/img/ova/ova03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/img/ova/ova04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/img/ova/ova05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/img/ova/ova06.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/img/ova/ova07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/img/ova/ova08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,86 @@
# Install and Configure Harbor on vSphere using OVA
This guide takes you through the steps about installing and configuring Harbor on vSphere using OVA.
## Installation
1.Get URL or download the OVA file to your local disk from [release page](https://github.com/vmware/harbor/releases).
2.Login vSphere web client. Right click on the datacenter, cluster or host which Harbor will be deployed on. Select "Deploy OVF Template" and open the import wizard.
![ova](img/ova/ova01.png)
3.Paste the URL of OVA file or select it from local disk and click "Next".
![ova](img/ova/ova02.png)
4.Review the OVF template details and click "Next".
![ova](img/ova/ova03.png)
5.Spefify a name and location for the deployed template.
![ova](img/ova/ova04.png)
6.Select the storage and virtual disk format, click "Next".
![ova](img/ova/ova05.png)
7.Configure the networks the deployed template should use.
![ova](img/ova/ova06.png)
8.Customize the properties of Harbor. The properties are described below. Note that at the very least, you just need to set the **Root Password**, **Harbor Admin Password** and **Database Password** properties.
![ova](img/ova/ova07.png)
* Application
* **Root Password**: The password of the root user. (8-128 characters)
* **Harbor Admin Password**: The initial password of Harbor admin. It only works for the first time when Harbor starts. It has no effect after the first launch of Harbor. Change the admin password from UI after launching Harbor. (8-20 characters)
* **Database Password**: The password of the root user of MySQL database. (8-128 characters)
* **Authentication Mode**: The default authentication mode is db_auth, i.e. the credentials are stored in a local database. Set it to ldap_auth if you want to verify the user's credential against an LDAP/AD server.
* **LDAP URL**: The URL of an LDAP/AD server.
* **LDAP Search DN**: A user's DN who has the permission to search the LDAP/AD server. If your LDAP/AD server does not support anonymous search, you should configure this DN and LDAP Seach Password.
* **LDAP Search Password**: The password of the user for LDAP search.
* **LDAP Base DN**: The base DN from which to look up a user in LDAP/AD.
* **LDAP UID**: The attribute used in a search to match a user, it could be uid, cn, email, sAMAccountName or other attributes depending on your LDAP/AD server.
* **Email Server**: The mail server to send out emails to reset password.
* **Email Server Port**: The port of mail server.
* **Email Username**: The user from whom the password reset email is sent.
* **Email Password**: The password of the user from whom the password reset email is sent.
* **Email From**: The name of the email sender.
* **Email SSL**: Whether to enabled secure mail transmission.
* **SSL Cert**: Paste in the content of a certificate file. If SSL Cert and SSL Cert Key are both set, HTTPS will be used.
* **SSL Cert Key**: Paste in the content of certificate key file. If SSL Cert and SSL Cert Key are both set, HTTPS will be used.
* **Self Registration**: Determine whether the self-registration is allowed or not when the authentication mode is database. Set this to off to disable a user's self-registration in Harbor.
* **Verify Remote Cert**: Determine whether the image replication should verify the SSL certificate when it connects to a remote registry. Set this flag to off when the remote registry uses a self-signed or untrusted certificate.
* **Garbage Collection**: When setting this to true, Harbor performs garbage collection everytime it boots up.
* Networking properties
* **Default Gateway**: The default gateway address for this VM. Leave blank if DHCP is desired.
* **Domain Name**: The domain name of this VM. Leave blank if DHCP is desired.
* **Domain Search Path**: The domain search path(comma or space separated domain names) for this VM. Leave blank if DHCP is desired.
* **Domain Name Servers**: The domain name server IP Address for this VM(comma separated). Leave blank if DHCP is desired.
* **Network 1 IP Adress**: The IP address of this interface. Leave blank if DHCP is desired.
* **Network 1 Netmask**: The netmask or prefix for this interface. Leave blank if DHCP is desired.
**Notes:** If you want to enable HTTPS with a self-signed certificate and have no idea how to generate it, refer to the "Getting a certificate" part of this [guide](https://github.com/vmware/harbor/blob/master/docs/configure_https.md#getting-a-certificate).
After you complete the properties, click "Next".
9.Review your settings and click "Finish" to complete the installation.
![ova](img/ova/ova08.png)
## Reconfiguration
If you want to reconfigure the properties of Harbor, follow the steps:
1.Power off the VM which Harbor is deployed on.
2.Right click on the VM and select "Edit Settings".
![ova](img/ova/edit_settings.png)
3.Click the "vApp Options" tab, reconfigure the properties and click "OK".
![ova](img/ova/vapp_options.png)
4.Power on the VM.
**Notes:** "Harbor Admin Password" and all networking properties can not be modified using this method after Harbor launched. Change the admin password from UI and change the networking properties in the OS level manually.

View File

@ -28,11 +28,11 @@ When upgrading your existing Habor instance to a newer version, you may need to
```
4. Before upgrading Harbor, perform database migration first.
The directory **migration/** contains the tool for migration. The first step is to update values of `db_username`, `db_password`, `db_port`, `db_name` in **migration.cfg** so that they match your system's configuration.
The directory **tools/migration/** contains the tool for migration. The first step is to update values of `db_username`, `db_password`, `db_port`, `db_name` in **migration.cfg** so that they match your system's configuration.
5. The migration tool is delivered as a container, so you should build the image from its Dockerfile:
```
cd migration/
cd tools/migration/
docker build -t migrate-tool .
```

View File

@ -67,6 +67,9 @@ You can update or remove a member by clicking the icon on the right.
##Replicating images
If you are a system administrator, you can replicate images to a remote registry, which is called destination in Harbor. Only Harbor instance is supported as a destination for now.
**Note:** The replication feature is incompatible between Harbor instance before version 0.3.5(included) and after version 0.3.5.
Click "Add New Policy" on the "Replication" tab, fill the necessary fields and click "OK", a policy for this project will be created. If "Enable" is chosen, the project will be replicated to the remote immediately, and when a new repository is pushed to this project or an existing repository is deleted from this project, the same operation will also be replicated to the destination.
![browse project](img/new_create_policy.png)
@ -169,4 +172,4 @@ $ docker-compose start
Option "--dry-run" will print the progress without removing any data.
About the details of GC, please see [GC](https://github.com/docker/distribution/blob/master/docs/garbage-collection.md).
About the details of GC, please see [GC](https://github.com/docker/docker.github.io/blob/master/registry/garbage-collection.md).

View File

@ -1,7 +0,0 @@
# Logrotate configuartion file for docker.
/var/log/docker/*/*.log {
rotate 100
size 10M
copytruncate
}

26
make/common/log/rotate.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
set -e
echo "Log rotate starting..."
#The logs n days before will be compressed.
n=14
path=/var/log/docker
list=""
n_days_before=$(($(date +%s) - 3600*24*$n))
for dir in $(ls $path | grep -v "tar.gz");
do
if [ $(date --date=$dir +%s) -lt $n_days_before ]
then
echo "$dir will be compressed"
list="$list $dir"
fi
done
if [ -n "$list" ]
then
cd $path
tar --remove-files -zcvf $(date -d @$n_days_before +%F)-.tar.gz $list
fi
echo "Log rotate finished."

View File

@ -32,7 +32,7 @@ http {
# Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

View File

@ -75,7 +75,7 @@ services:
syslog-address: "tcp://127.0.0.1:1514"
tag: "jobservice"
proxy:
image: nginx:1.9.0
image: nginx:1.9
container_name: nginx
restart: always
volumes:

View File

@ -2,7 +2,7 @@
#
# Targets:
#
# build: build harbor ubuntu images
# build: build harbor photon images
# clean: clean ui and jobservice harbor images
# common
@ -29,8 +29,8 @@ JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
JOBSERVICEBINARYNAME=harbor_jobservice
# ubuntu dockerfile
DOCKERFILEPATH=$(MAKEPATH)/ubuntu
# photon dockerfile
DOCKERFILEPATH=$(MAKEPATH)/photon
DOCKERFILEPATH_UI=$(DOCKERFILEPATH)/ui
DOCKERFILENAME_UI=Dockerfile
DOCKERIMAGENAME_UI=vmware/harbor-ui
@ -55,21 +55,21 @@ endif
check_environment:
@$(MAKEPATH)/$(CHECKENVCMD)
build:
@echo "building ui container for ubuntu..."
build:
@echo "building ui container for photon..."
$(DOCKERBUILD) -f $(DOCKERFILEPATH_UI)/$(DOCKERFILENAME_UI) -t $(DOCKERIMAGENAME_UI):$(VERSIONTAG) .
@echo "Done."
@echo "building jobservice container for ubuntu..."
@echo "building jobservice container for photon..."
$(DOCKERBUILD) -f $(DOCKERFILEPATH_JOBSERVICE)/$(DOCKERFILENAME_JOBSERVICE) -t $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) .
@echo "Done."
@echo "building log container for ubuntu..."
@echo "building log container for photon..."
$(DOCKERBUILD) -f $(DOCKERFILEPATH_LOG)/$(DOCKERFILENAME_LOG) -t $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) .
@echo "Done."
cleanimage:
@echo "cleaning image for ubuntu..."
@echo "cleaning image for photon..."
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)

View File

@ -1,8 +1,7 @@
FROM library/photon:latest
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
RUN tdnf install -y cronie rsyslog logrotate shadow\
&& mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
RUN tdnf install -y cronie rsyslog shadow tar gzip \
&& mkdir /etc/rsyslog.d/ \
&& mkdir /var/spool/rsyslog \
&& groupadd syslog \
@ -10,15 +9,13 @@ RUN tdnf install -y cronie rsyslog logrotate shadow\
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
COPY make/photon/log/logrotate.conf.photon /etc/logrotate.conf
# logrotate configuration file for docker
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/
# rotate logs weekly
# notes: file name cannot contain dot, or the script will not run
ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
# rsyslog configuration file for docker
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/
VOLUME /var/log/docker/
EXPOSE 514

View File

@ -1,35 +0,0 @@
# see "man logrotate" for details
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# use date as a suffix of the rotated file
dateext
# uncomment this if you want your log files compressed
#compress
# RPM packages drop log rotation information into this directory
include /etc/logrotate.d
# no packages own wtmp and btmp -- we'll rotate them here
#/var/log/wtmp {
# monthly
# create 0664 root utmp
# minsize 1M
# rotate 1
#}
/var/log/btmp {
missingok
monthly
create 0600 root utmp
rotate 1
}
# system-specific logs may be also be configured here.

View File

@ -1,13 +1,12 @@
FROM library/ubuntu:14.04
# run logrotate hourly, disable imklog model, provides TCP/UDP syslog reception
RUN mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
&& rm /etc/rsyslog.d/* \
&& rm /etc/rsyslog.conf
RUN rm /etc/rsyslog.d/* && rm /etc/rsyslog.conf
ADD make/common/log/rsyslog.conf /etc/rsyslog.conf
# logrotate configuration file for docker
ADD make/common/log/logrotate_docker.conf /etc/logrotate.d/
# rotate logs weekly
# notes: file name cannot contain dot, or the script will not run
ADD make/common/log/rotate.sh /etc/cron.weekly/rotate
# rsyslog configuration file for docker
ADD make/common/log/rsyslog_docker.conf /etc/rsyslog.d/

View File

@ -428,7 +428,13 @@ func TestResetUserPassword(t *testing.T) {
}
func TestChangeUserPassword(t *testing.T) {
err := ChangeUserPassword(models.User{UserID: currentUser.UserID, Password: "NewHarborTester12345", Salt: currentUser.Salt})
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", Salt: currentUser.Salt})
if err != nil {
t.Errorf("Error occurred in ChangeUserPassword: %v", err)
}
@ -444,7 +450,14 @@ func TestChangeUserPassword(t *testing.T) {
}
func TestChangeUserPasswordWithOldPassword(t *testing.T) {
err := ChangeUserPassword(models.User{UserID: currentUser.UserID, Password: "NewerHarborTester12345", Salt: currentUser.Salt}, "NewHarborTester12345")
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: "NewerHarborTester12345", Salt: currentUser.Salt}, "NewHarborTester12345")
if err != nil {
t.Errorf("Error occurred in ChangeUserPassword: %v", err)
}

View File

@ -137,11 +137,12 @@ func ChangeUserPassword(u models.User, oldPassword ...string) (err error) {
o := GetOrmer()
var r sql.Result
salt := utils.GenerateRandomString()
if len(oldPassword) == 0 {
//In some cases, it may no need to check old password, just as Linux change password policies.
r, err = o.Raw(`update user set password=?, salt=? where user_id=?`, utils.Encrypt(u.Password, u.Salt), u.Salt, u.UserID).Exec()
r, err = o.Raw(`update user set password=?, salt=? where user_id=?`, utils.Encrypt(u.Password, salt), salt, u.UserID).Exec()
} else {
r, err = o.Raw(`update user set password=?, salt=? where user_id=? and password = ?`, utils.Encrypt(u.Password, u.Salt), u.Salt, u.UserID, utils.Encrypt(oldPassword[0], u.Salt)).Exec()
r, err = o.Raw(`update user set password=?, salt=? where user_id=? and password = ?`, utils.Encrypt(u.Password, salt), salt, u.UserID, utils.Encrypt(oldPassword[0], u.Salt)).Exec()
}
if err != nil {

View File

@ -35,7 +35,7 @@ type User struct {
// RoleList []Role `json:"role_list"`
HasAdminRole int `orm:"column(sysadmin_flag)" json:"has_admin_role"`
ResetUUID string `orm:"column(reset_uuid)" json:"reset_uuid"`
Salt string `orm:"column(salt)"`
Salt string `orm:"column(salt)" json:"-"`
CreationTime time.Time `orm:"creation_time" json:"creation_time"`
UpdateTime time.Time `orm:"update_time" json:"update_time"`
}

View File

@ -34,8 +34,8 @@ func CommonAddUser() {
commonUser := models.User{
Username: TestUserName,
Email: TestUserPwd,
Password: TestUserEmail,
Password: TestUserPwd,
Email: TestUserEmail,
}
_, _ = dao.Register(commonUser)

View File

@ -6,14 +6,15 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http/httptest"
"path/filepath"
"runtime"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/tests/apitests/apilib"
"github.com/vmware/harbor/src/common/utils"
"github.com/vmware/harbor/tests/apitests/apilib"
// "strconv"
// "strings"
@ -90,6 +91,11 @@ func init() {
_ = updateInitPassword(1, "Harbor12345")
//syncRegistry
if err := SyncRegistry(); err != nil {
log.Fatalf("failed to sync repositories from registry: %v", err)
}
//Init user Info
admin = &usrInfo{adminName, adminPwd}
unknownUsr = &usrInfo{"unknown", "unknown"}
@ -119,8 +125,10 @@ func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int
//The response includes the project and repository list in a proper display order.
//@param q Search parameter for project and repository name.
//@return []Search
//func (a testapi) SearchGet (q string) (apilib.Search, error) {
func (a testapi) SearchGet(q string) (apilib.Search, error) {
func (a testapi) SearchGet(q string, authInfo ...usrInfo) (int, apilib.Search, error) {
var httpCode int
var body []byte
var err error
_sling := sling.New().Get(a.basePath)
@ -134,10 +142,15 @@ func (a testapi) SearchGet(q string) (apilib.Search, error) {
_sling = _sling.QueryStruct(&QueryParams{Query: q})
_, body, err := request(_sling, jsonAcceptHeader)
if len(authInfo) > 0 {
httpCode, body, err = request(_sling, jsonAcceptHeader, authInfo[0])
} else {
httpCode, body, err = request(_sling, jsonAcceptHeader)
}
var successPayload = new(apilib.Search)
err = json.Unmarshal(body, &successPayload)
return *successPayload, err
return httpCode, *successPayload, err
}
//Create a new project.
@ -285,7 +298,7 @@ func (a testapi) ProjectsGetByPID(projectID string) (int, apilib.Project, error)
}
//Search projects by projectName and isPublic
func (a testapi) ProjectsGet(projectName string, isPublic int32) (int, []apilib.Project, error) {
func (a testapi) ProjectsGet(projectName string, isPublic int32, authInfo ...usrInfo) (int, []apilib.Project, error) {
_sling := sling.New().Get(a.basePath)
//create api path
@ -299,7 +312,15 @@ func (a testapi) ProjectsGet(projectName string, isPublic int32) (int, []apilib.
var successPayload []apilib.Project
httpStatusCode, body, err := request(_sling, jsonAcceptHeader)
var httpStatusCode int
var err error
var body []byte
if len(authInfo) > 0 {
httpStatusCode, body, err = request(_sling, jsonAcceptHeader, authInfo[0])
} else {
httpStatusCode, body, err = request(_sling, jsonAcceptHeader)
}
if err == nil && httpStatusCode == 200 {
err = json.Unmarshal(body, &successPayload)
}

View File

@ -21,7 +21,7 @@ func TestLogGet(t *testing.T) {
project.ProjectName = "my_project"
project.Public = 1
now := fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "")
statusCode, result, err := apiTest.LogGet(*admin, "0", now, "1000")
if err != nil {
t.Error("Error while get log information", err.Error())
t.Log(err)
@ -30,8 +30,7 @@ func TestLogGet(t *testing.T) {
}
logNum := len(result)
logID := result[0].LogId
fmt.Println(result)
fmt.Println("result", result)
//add the project first.
fmt.Println("add the project first.")
reply, err := apiTest.ProjectsPost(*admin, project)
@ -42,20 +41,24 @@ func TestLogGet(t *testing.T) {
assert.Equal(int(201), reply, "Case 2: Project creation status should be 201")
}
//case 1: right parameters, expect the right output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "1000")
if err != nil {
t.Error("Error while get log information", err.Error())
t.Log(err)
} else {
assert.Equal(logNum+1, len(result), "lines of logs should be equal")
assert.Equal(int32(logID+1), result[0].LogId, "LogId should be equal")
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[0].Operation, "Operation should be equal")
num, index := getLog(result)
if num != 1 {
assert.Equal(1, num, "add my_project log number should be 1")
} else {
assert.Equal("my_project/", result[index].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[index].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[index].Operation, "Operation should be equal")
}
}
fmt.Println("log ", result)
//case 2: wrong format of start_time parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "ss", now, "3")
if err != nil {
t.Error("Error occured while get log information since the format of start_time parameter is not right.", err.Error())
@ -74,7 +77,6 @@ func TestLogGet(t *testing.T) {
}
//case 4: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "s")
if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
@ -84,7 +86,6 @@ func TestLogGet(t *testing.T) {
}
//case 5: wrong format of lines parameter, expect the wrong output
now = fmt.Sprintf("%v", time.Now().Unix())
statusCode, result, err = apiTest.LogGet(*admin, "0", now, "-5")
if err != nil {
t.Error("Error occured while get log information since the format of lines parameter is not right.", err.Error())
@ -103,13 +104,17 @@ func TestLogGet(t *testing.T) {
if logNum+1 >= 10 {
logNum = 10
} else {
logNum += 1
logNum++
}
assert.Equal(logNum, len(result), "lines of logs should be equal")
assert.Equal(int32(logID+1), result[0].LogId, "LogId should be equal")
assert.Equal("my_project/", result[0].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[0].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[0].Operation, "Operation should be equal")
num, index := getLog(result)
if num != 1 {
assert.Equal(1, num, "add my_project log number should be 1")
} else {
assert.Equal("my_project/", result[index].RepoName, "RepoName should be equal")
assert.Equal("N/A", result[index].RepoTag, "RepoTag should be equal")
assert.Equal("create", result[index].Operation, "Operation should be equal")
}
}
//get the project
@ -136,5 +141,15 @@ func TestLogGet(t *testing.T) {
}
fmt.Printf("\n")
}
func getLog(result []apilib.AccessLog) (int, int) {
var num, index int
for i := 0; i < len(result); i++ {
if result[i].RepoName == "my_project/" {
num++
index = i
}
}
return num, index
}

View File

@ -16,13 +16,14 @@
package api
import (
"fmt"
"net/http"
"strconv"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
)
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
@ -98,6 +99,11 @@ func (pma *ProjectMemberAPI) Get() {
log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if len(roleList) == 0 {
pma.CustomAbort(http.StatusNotFound, fmt.Sprintf("user %d is not a member of the project", pma.memberID))
}
//return empty role list to indicate if a user is not a member
result := make(map[string]interface{})
user, err := dao.GetUser(models.User{UserID: pma.memberID})

View File

@ -4,9 +4,10 @@ import (
"fmt"
"testing"
"strconv"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
"strconv"
)
func TestMemGet(t *testing.T) {
@ -51,8 +52,19 @@ func TestMemGet(t *testing.T) {
assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404")
}
fmt.Printf("\n")
//------------case 4: Response Code=404, member does not exist-----------//
fmt.Println("case 4: Response Code=404, member does not exist")
projectID = "1"
memberID := "10000"
httpStatusCode, err = apiTest.GetMemByPIDUID(*admin, projectID, memberID)
if err != nil {
t.Fatalf("failed to get member %s of project %s: %v", memberID, projectID, err)
}
assert.Equal(int(404), httpStatusCode,
fmt.Sprintf("response status code should be 404 other than %d", httpStatusCode))
fmt.Printf("\n")
}
/**

View File

@ -20,10 +20,10 @@ import (
"net/http"
"regexp"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
"strconv"
"time"
@ -192,7 +192,8 @@ func (p *ProjectAPI) Delete() {
if err := dao.AddAccessLog(models.AccessLog{
UserID: userID,
ProjectID: p.projectID,
RepoName: p.projectName,
RepoName: p.projectName + "/",
RepoTag: "N/A",
Operation: "delete",
}); err != nil {
log.Errorf("failed to add access log: %v", err)
@ -286,6 +287,13 @@ func (p *ProjectAPI) List() {
if public != 1 {
if isAdmin {
projectList[i].Role = models.PROJECTADMIN
} else {
roles, err := dao.GetUserProjectRoles(p.userID, projectList[i].ProjectID)
if err != nil {
log.Errorf("failed to get user's project role: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList[i].Role = roles[0].RoleID
}
if projectList[i].Role == models.PROJECTADMIN {
projectList[i].Togglable = true

View File

@ -13,7 +13,7 @@ var addProject *apilib.ProjectReq
var addPID int
func InitAddPro() {
addProject = &apilib.ProjectReq{"test_project", 1}
addProject = &apilib.ProjectReq{"add_project", 1}
}
func TestAddProject(t *testing.T) {
@ -106,7 +106,49 @@ func TestProGetByName(t *testing.T) {
} else {
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200")
}
fmt.Printf("\n")
//-------------------case 3 : check admin project role------------------------//
httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *admin)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong")
assert.Equal(int32(1), result[0].CurrentUserRoleId, "User project role is wrong")
}
//-------------------case 4 : add project member and check his role ------------------------//
CommonAddUser()
roles := &apilib.RoleParam{[]int32{2}, TestUserName}
projectID := strconv.Itoa(addPID)
httpStatusCode, err = apiTest.AddProjectMember(*admin, projectID, *roles)
if err != nil {
t.Error("Error whihle add project role member", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
}
httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *testUser)
if err != nil {
t.Error("Error while search project by proName and isPublic", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong")
assert.Equal(int32(1), result[0].Public, "Public is wrong")
assert.Equal(int32(2), result[0].CurrentUserRoleId, "User project role is wrong")
}
id := strconv.Itoa(CommonGetUserID())
httpStatusCode, err = apiTest.DeleteProjectMember(*admin, projectID, id)
if err != nil {
t.Error("Error whihle add project role member", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
}
CommonDelUser()
}
//Get project by proID

View File

@ -14,19 +14,29 @@ func TestSearch(t *testing.T) {
apiTest := newHarborAPI()
var result apilib.Search
result, err := apiTest.SearchGet("library")
//fmt.Printf("%+v\n", result)
//-------------case 1 : Response Code = 200, Not sysAdmin --------------//
httpStatusCode, result, err := apiTest.SearchGet("library")
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
assert.Equal(result.Projects[0].Id, int64(1), "Project id should be equal")
assert.Equal(result.Projects[0].Name, "library", "Project name should be library")
assert.Equal(result.Projects[0].Public, int32(1), "Project public status should be 1 (true)")
//t.Log(result)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal(int64(1), result.Projects[0].Id, "Project id should be equal")
assert.Equal("library", result.Projects[0].Name, "Project name should be library")
assert.Equal(int32(1), result.Projects[0].Public, "Project public status should be 1 (true)")
}
//--------case 2 : Response Code = 200, sysAdmin and search repo--------//
httpStatusCode, result, err = apiTest.SearchGet("docker", *admin)
if err != nil {
t.Error("Error while search project or repository", err.Error())
t.Log(err)
} else {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Equal("library", result.Repositories[0].ProjectName, "Project name should be library")
assert.Equal("library/docker", result.Repositories[0].RepositoryName, "Repository name should be library/docker")
assert.Equal(int32(1), result.Repositories[0].ProjectPublic, "Project public status should be 1 (true)")
}
//if result.Response.StatusCode != 200 {
// t.Log(result.Response)
//}
}

View File

@ -10,10 +10,6 @@ import (
)
func TestStatisticGet(t *testing.T) {
if err := SyncRegistry(); err != nil {
t.Fatalf("failed to sync repositories from registry: %v", err)
}
fmt.Println("Testing Statistic API")
assert := assert.New(t)

View File

@ -23,10 +23,10 @@ import (
"strconv"
"strings"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
)
// UserAPI handles request to /api/users/{}
@ -152,7 +152,7 @@ func (ua *UserAPI) Put() {
ua.DecodeJSONReq(&user)
err := commonValidate(user)
if err != nil {
log.Warning("Bad request in change user profile: %v", err)
log.Warningf("Bad request in change user profile: %v", err)
ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error())
return
}
@ -200,7 +200,7 @@ func (ua *UserAPI) Post() {
ua.DecodeJSONReq(&user)
err := validate(user)
if err != nil {
log.Warning("Bad request in Register: %v", err)
log.Warningf("Bad request in Register: %v", err)
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
return
}
@ -241,6 +241,12 @@ func (ua *UserAPI) Delete() {
return
}
// TODO read from conifg
authMode := os.Getenv("AUTH_MODE")
if authMode == "ldap_auth" {
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
}
if ua.currentUserID == ua.userID {
ua.CustomAbort(http.StatusForbidden, "can not delete yourself")
}

View File

@ -110,7 +110,7 @@ func TestUsersPost(t *testing.T) {
//case 9: register a new user with admin auth, but bad user comment, expect 400
testUser0002.Realname = "testUser0002"
testUser0002.Comment = "vmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"
fmt.Println("Register user with admin auth, but bad user comment format")
fmt.Println("Register user with admin auth, but user comment length is illegal")
code, err = apiTest.UsersPost(testUser0002, *admin)
if err != nil {
t.Error("Error occured while add a user", err.Error())
@ -222,7 +222,7 @@ func TestUsersGetByID(t *testing.T) {
//case 3: Get user that does not exist with user2 auth, expect 404 not found.
code, user, err = apiTest.UsersGetByID(testUser0002.Username, *testUser0002Auth, 1000)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while get users", err.Error())
t.Log(err)
} else {
assert.Equal(404, code, "Get users status should be 404")
@ -230,7 +230,7 @@ func TestUsersGetByID(t *testing.T) {
// Get user3ID in order to delete at the last of the test
code, users, err := apiTest.UsersGet(testUser0003.Username, *admin)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while get users", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Get users status should be 200")
@ -250,7 +250,7 @@ func TestUsersPut(t *testing.T) {
t.Error("Error occured while change user profile", err.Error())
t.Log(err)
} else {
assert.Equal(403, code, "Get users status should be 403")
assert.Equal(403, code, "Change user profile status should be 403")
}
//case 2: change user2 profile with user2 auth, but bad parameters format.
code, err = apiTest.UsersPut(testUser0002ID, profile, *testUser0002Auth)
@ -258,7 +258,7 @@ func TestUsersPut(t *testing.T) {
t.Error("Error occured while change user profile", err.Error())
t.Log(err)
} else {
assert.Equal(400, code, "Get users status should be 400")
assert.Equal(400, code, "Change user profile status should be 400")
}
//case 3: change user2 profile with user2 auth, but duplicate email.
profile.Realname = "test user"
@ -269,7 +269,7 @@ func TestUsersPut(t *testing.T) {
t.Error("Error occured while change user profile", err.Error())
t.Log(err)
} else {
assert.Equal(409, code, "Get users status should be 409")
assert.Equal(409, code, "Change user profile status should be 409")
}
//case 4: change user2 profile with user2 auth, right parameters format.
profile.Realname = "test user"
@ -280,7 +280,8 @@ func TestUsersPut(t *testing.T) {
t.Error("Error occured while change user profile", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Get users status should be 200")
assert.Equal(200, code, "Change user profile status should be 200")
testUser0002.Email = profile.Email
}
}
@ -291,18 +292,18 @@ func TestUsersToggleAdminRole(t *testing.T) {
//case 1: toggle user2 admin role without admin auth
code, err := apiTest.UsersToggleAdminRole(testUser0002ID, *testUser0002Auth, int32(1))
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while toggle user admin role", err.Error())
t.Log(err)
} else {
assert.Equal(403, code, "Get users status should be 403")
assert.Equal(403, code, "Toggle user admin role status should be 403")
}
//case 2: toggle user2 admin role with admin auth
code, err = apiTest.UsersToggleAdminRole(testUser0002ID, *admin, int32(1))
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while toggle user admin role", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Get users status should be 200")
assert.Equal(200, code, "Toggle user admin role status should be 200")
}
}
func TestUsersUpdatePassword(t *testing.T) {
@ -321,39 +322,59 @@ func TestUsersUpdatePassword(t *testing.T) {
//case 2: update user2 password with admin auth, but oldpassword is empty
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while update user password", err.Error())
t.Log(err)
} else {
assert.Equal(400, code, "Get users status should be 400")
assert.Equal(400, code, "Update user password status should be 400")
}
//case 3: update user2 password with admin auth, but oldpassword is wrong
password.OldPassword = "000"
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while update user password", err.Error())
t.Log(err)
} else {
assert.Equal(403, code, "Get users status should be 403")
assert.Equal(403, code, "Update user password status should be 403")
}
//case 4: update user2 password with admin auth, but newpassword is empty
password.OldPassword = "testUser0002"
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while update user password", err.Error())
t.Log(err)
} else {
assert.Equal(400, code, "Get users status should be 400")
assert.Equal(400, code, "Update user password status should be 400")
}
//case 5: update user2 password with admin auth, right parameters
password.NewPassword = "TestUser0002"
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
if err != nil {
t.Error("Error occured while change user profile", err.Error())
t.Error("Error occured while update user password", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Get users status should be 200")
assert.Equal(200, code, "Update user password status should be 200")
testUser0002.Password = password.NewPassword
testUser0002Auth.Passwd = password.NewPassword
//verify the new password takes effect
code, user, err := apiTest.UsersGetByID(testUser0002.Username, *testUser0002Auth, testUser0002ID)
if err != nil {
t.Error("Error occured while get users", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Get users status should be 200")
assert.Equal(testUser0002.Username, user.Username, "Get users username should be equal")
assert.Equal(testUser0002.Email, user.Email, "Get users email should be equal")
}
}
//case 6: update user2 password setting the new password same as the old
password.OldPassword = password.NewPassword
code, err = apiTest.UsersUpdatePassword(testUser0002ID, password, *admin)
if err != nil {
t.Error("Error occured while update user password", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "When new password is same as old, update user password status should be 200")
}
}
@ -366,33 +387,33 @@ func TestUsersDelete(t *testing.T) {
//case 1:delete user without admin auth
code, err := apiTest.UsersDelete(testUser0002ID, *testUser0003Auth)
if err != nil {
t.Error("Error occured while delete a testUser", err.Error())
t.Error("Error occured while delete test user", err.Error())
t.Log(err)
} else {
assert.Equal(403, code, "Delete testUser status should be 403")
assert.Equal(403, code, "Delete test user status should be 403")
}
//case 2: delete user with admin auth, user2 has already been toggled to admin, but can not delete himself
code, err = apiTest.UsersDelete(testUser0002ID, *testUser0002Auth)
if err != nil {
t.Error("Error occured while delete a testUser", err.Error())
t.Error("Error occured while delete test user", err.Error())
t.Log(err)
} else {
assert.Equal(403, code, "Delete testUser status should be 403")
assert.Equal(403, code, "Delete test user status should be 403")
}
//case 3: delete user with admin auth
code, err = apiTest.UsersDelete(testUser0002ID, *admin)
if err != nil {
t.Error("Error occured while delete a testUser", err.Error())
t.Error("Error occured while delete test user", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Delete testUser status should be 200")
assert.Equal(200, code, "Delete test user status should be 200")
}
//delete user3 with admin auth
code, err = apiTest.UsersDelete(testUser0003ID, *admin)
if err != nil {
t.Error("Error occured while delete a testUser", err.Error())
t.Error("Error occured while delete test user", err.Error())
t.Log(err)
} else {
assert.Equal(200, code, "Delete testUser status should be 200")
assert.Equal(200, code, "Delete test user status should be 200")
}
}

View File

@ -7,5 +7,14 @@ type AccountSettingController struct {
// Get renders the account settings page
func (asc *AccountSettingController) Get() {
asc.Forward("page_title_account_setting", "account-settings.htm")
var isAdminForLdap bool
sessionUserID, ok := asc.GetSession("userId").(int)
if ok && sessionUserID == 1 {
isAdminForLdap = true
}
if asc.AuthMode == "db_auth" || isAdminForLdap {
asc.Forward("page_title_account_setting", "account-settings.htm")
} else {
asc.Redirect("/dashboard", 302)
}
}

View File

@ -1,5 +1,10 @@
package controllers
import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
)
// AdminOptionController handles requests to /admin_option
type AdminOptionController struct {
BaseController
@ -7,5 +12,16 @@ type AdminOptionController struct {
// Get renders the admin options page
func (aoc *AdminOptionController) Get() {
aoc.Forward("page_title_admin_option", "admin-options.htm")
sessionUserID, ok := aoc.GetSession("userId").(int)
if ok {
isAdmin, err := dao.IsAdminRole(sessionUserID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
}
if isAdmin {
aoc.Forward("page_title_admin_option", "admin-options.htm")
return
}
}
aoc.Redirect("/dashboard", 302)
}

View File

@ -8,10 +8,10 @@ import (
"github.com/astaxie/beego"
"github.com/beego/i18n"
"github.com/vmware/harbor/src/ui/auth"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/auth"
)
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.

View File

@ -6,6 +6,15 @@ type ChangePasswordController struct {
}
// Get renders the change password page
func (asc *ChangePasswordController) Get() {
asc.Forward("page_title_change_password", "change-password.htm")
func (cpc *ChangePasswordController) Get() {
var isAdminForLdap bool
sessionUserID, ok := cpc.GetSession("userId").(int)
if ok && sessionUserID == 1 {
isAdminForLdap = true
}
if cpc.AuthMode == "db_auth" || isAdminForLdap {
cpc.Forward("page_title_change_password", "change-password.htm")
} else {
cpc.Redirect("/dashboard", 302)
}
}

View File

@ -113,19 +113,16 @@ func TestMain(t *testing.T) {
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/account_setting' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_account_setting</title>"), "http respond should have '<title>page_title_account_setting</title>'")
r, _ = http.NewRequest("GET", "/change_password", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/change_password' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_change_password</title>"), "http respond should have '<title>page_title_change_password</title>'")
r, _ = http.NewRequest("GET", "/admin_option", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/admin_option' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), "<title>page_title_admin_option</title>"), "http respond should have '<title>page_title_admin_option</title>'")
assert.Equal(int(302), w.Code, "'/admin_option' httpStatusCode should be 302")
r, _ = http.NewRequest("GET", "/forgot_password", nil)
w = httptest.NewRecorder()

View File

@ -20,6 +20,9 @@ func (omc *OptionalMenuController) Get() {
var hasLoggedIn bool
var allowAddNew bool
var isAdminForLdap bool
var allowSettingAccount bool
if sessionUserID != nil {
hasLoggedIn = true
userID := sessionUserID.(int)
@ -34,6 +37,14 @@ func (omc *OptionalMenuController) Get() {
}
omc.Data["Username"] = u.Username
if userID == 1 {
isAdminForLdap = true
}
if omc.AuthMode == "db_auth" || isAdminForLdap {
allowSettingAccount = true
}
isAdmin, err := dao.IsAdminRole(sessionUserID.(int))
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
@ -45,6 +56,7 @@ func (omc *OptionalMenuController) Get() {
}
}
omc.Data["AddNew"] = allowAddNew
omc.Data["SettingAccount"] = allowSettingAccount
omc.Data["HasLoggedIn"] = hasLoggedIn
omc.TplName = "optional-menu.htm"
omc.Render()

View File

@ -12,7 +12,7 @@ type SignUpController struct {
// Get renders sign up page
func (suc *SignUpController) Get() {
if suc.AuthMode != "db_auth" || !suc.SelfRegistration {
suc.CustomAbort(http.StatusUnauthorized, "Status unauthorized.")
suc.CustomAbort(http.StatusForbidden, "")
}
suc.Data["AddNew"] = false
suc.Forward("page_title_sign_up", "sign-up.htm")

View File

@ -20,12 +20,12 @@ import (
"regexp"
"strings"
"github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/ui/service/cache"
"github.com/vmware/harbor/src/common/utils"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/api"
"github.com/vmware/harbor/src/ui/service/cache"
"github.com/astaxie/beego"
)
@ -35,7 +35,8 @@ type NotificationHandler struct {
beego.Controller
}
const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+json`
const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+(json|prettyjws)`
const vicPrefix = "vic/"
// Post handles POST request, and records audit log or refreshes cache based on event.
func (n *NotificationHandler) Post() {
@ -102,8 +103,8 @@ func filterEvents(notification *models.Notification) ([]*models.Event, error) {
events := []*models.Event{}
for _, event := range notification.Events {
log.Debugf("receive an event: ID-%s, target-%s:%s, digest-%s, action-%s", event.ID, event.Target.Repository, event.Target.Tag,
event.Target.Digest, event.Action)
log.Debugf("receive an event: \n----ID: %s \n----target: %s:%s \n----digest: %s \n----action: %s \n----mediatype: %s \n----user-agent: %s", event.ID, event.Target.Repository,
event.Target.Tag, event.Target.Digest, event.Action, event.Target.MediaType, event.Request.UserAgent)
isManifest, err := regexp.MatchString(manifestPattern, event.Target.MediaType)
if err != nil {
@ -115,8 +116,9 @@ func filterEvents(notification *models.Notification) ([]*models.Event, error) {
continue
}
//pull and push manifest by docker-client
if strings.HasPrefix(event.Request.UserAgent, "docker") && (event.Action == "pull" || event.Action == "push") {
//pull and push manifest by docker-client or vic
if (strings.HasPrefix(event.Request.UserAgent, "docker") || strings.HasPrefix(event.Request.UserAgent, vicPrefix)) &&
(event.Action == "pull" || event.Action == "push") {
events = append(events, &event)
log.Debugf("add event to collect: %s", event.ID)
continue

View File

@ -29,6 +29,7 @@
vm.isOpen = false;
vm.isProjectMember = false;
vm.target = $location.path().substr(1) || 'repositories';
vm.roleId = 0;
vm.isPublic = Number(getParameterByName('is_public', $location.absUrl()));
@ -87,7 +88,6 @@
function getProjectSuccess(response) {
var partialProjects = response.data || [];
for(var i in partialProjects) {
vm.projects.push(partialProjects[i]);
@ -113,11 +113,13 @@
}
}
$location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
if(vm.selectedProject) {
$location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
}
vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) {
vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'name').length;
});
@ -152,6 +154,9 @@
function getCurrentProjectMemberSuccess(data, status) {
console.log('Successful get current project member:' + status);
vm.isProjectMember = true;
if(data && data['roles'] && data['roles'].length > 0) {
vm.roleId = data['roles'][0]['role_id'];
}
}
function getCurrentProjectMemberFailed(data, status) {
@ -171,7 +176,8 @@
'isOpen': '=',
'selectedProject': '=',
'isPublic': '=',
'isProjectMember': '='
'isProjectMember': '=',
'roleId': '='
},
link: link,
controller: RetrieveProjectsController,

View File

@ -14,7 +14,7 @@
-->
<td width="30%">//vm.username//</td>
<td width="45%"><switch-role roles="vm.roles" edit-mode="vm.editMode" user-id="vm.userId" role-name="vm.roleName"></switch-role></td>
<td width="25%">
<td width="25%" ng-if="vm.currentRoleId == 1">
<a ng-show="vm.userId != vm.currentUserId" href="javascript:void(0);" ng-click="vm.updateProjectMember({projectId: vm.projectId, userId: vm.userId, roleId: vm.roleId})">
<span ng-if="!vm.editMode" class="glyphicon glyphicon-pencil" title="// 'edit' | tr //"></span><span ng-if="vm.editMode" class="glyphicon glyphicon-ok" title="// 'confirm' | tr //">
</a>

View File

@ -87,7 +87,8 @@
'roleName': '=',
'projectId': '=',
'delete': '&',
'reload': '&'
'reload': '&',
'currentRoleId': '@'
},
'controller': EditProjectMemberController,
'controllerAs': 'vm',

View File

@ -21,23 +21,23 @@
<button class="btn btn-primary" type="button" ng-click="vm.search({projectId: vm.projectId, username: vm.username})"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
<button ng-if="!vm.isOpen" class="btn btn-success" type="button" ng-click="vm.addProjectMember()"><span class="glyphicon glyphicon-plus"></span>// 'add_member' | tr //</button>
<button ng-if="vm.isOpen" class="btn btn-default" disabled="disabled" type="button"><span class="glyphicon glyphicon-plus"></span>// 'add_member' | tr //</button>
<button ng-if="vm.roleId == 1 && !vm.isOpen" class="btn btn-success" type="button" ng-click="vm.addProjectMember()"><span class="glyphicon glyphicon-plus"></span>// 'add_member' | tr //</button>
<button ng-if="vm.roleId == 1 && vm.isOpen" class="btn btn-default" disabled="disabled" type="button"><span class="glyphicon glyphicon-plus"></span>// 'add_member' | tr //</button>
</div>
<add-project-member ng-show="vm.isOpen" is-open="vm.isOpen" project-id="//vm.projectId//" reload='vm.search({projectId: vm.projectId, username: vm.username})'></add-project-member>
<add-project-member ng-if="vm.isOpen" is-open="vm.isOpen" project-id="//vm.projectId//" reload='vm.search({projectId: vm.projectId, username: vm.username})'></add-project-member>
<div class="search-pane">
<div class="sub-pane">
<div class="table-head-container">
<table class="table table-pane table-header">
<thead>
<th width="30%">// 'username' | tr //</th><th width="45%">// 'role' | tr //</th><th width="25%">// 'operation' | tr //</th>
<th width="30%">// 'username' | tr //</th><th width="45%">// 'role' | tr //</th><th width="25%" ng-if="vm.roleId == 1">// 'operation' | tr //</th>
</thead>
</table>
</div>
<div class="table-body-container">
<table class="table table-pane">
<tbody>
<tr ng-repeat="pr in vm.projectMembers" edit-project-member username="pr.username" project-id="vm.projectId" user-id="pr.user_id" delete="vm.deleteProjectMember({projectId: vm.projectId, userId: pr.user_id})" current-user-id="vm.user.user_id" role-name="pr.role_name" reload='vm.search({projectId: vm.projectId, username: vm.username})'></tr>
<tr ng-repeat="pr in vm.projectMembers" edit-project-member username="pr.username" project-id="vm.projectId" user-id="pr.user_id" delete="vm.deleteProjectMember({projectId: vm.projectId, userId: pr.user_id})" current-user-id="vm.user.user_id" role-name="pr.role_name" reload='vm.search({projectId: vm.projectId, username: vm.username})' current-role-id="//vm.roleId//"></tr>
</tbody>
</table>
</div>

View File

@ -56,11 +56,7 @@
}
function addProjectMember() {
if(vm.isOpen) {
vm.isOpen = false;
}else{
vm.isOpen = true;
}
vm.isOpen = !vm.isOpen;
}
function deleteProjectMember(e) {
@ -105,7 +101,8 @@
'restrict': 'E',
'templateUrl': '/static/resources/js/components/project-member/list-project-member.directive.html',
'scope': {
'sectionHeight': '='
'sectionHeight': '=',
'roleId': '@'
},
'link': link,
'controller': ListProjectMemberController,

View File

@ -12,5 +12,5 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<button ng-if="vm.isPublic" class="btn btn-success" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-if="!vm.isPublic" class="btn btn-danger" ng-click="vm.toggle()">// 'button_off' | tr //</button>
<button ng-if="vm.isPublic" class="btn btn-success" ng-disabled="vm.roleId != 1" ng-click="vm.toggle()">// 'button_on' | tr //</button>
<button ng-if="!vm.isPublic" class="btn btn-danger" ng-disabled="vm.roleId != 1" ng-click="vm.toggle()">// 'button_off' | tr //</button>

View File

@ -60,7 +60,8 @@
'templateUrl': '/static/resources/js/components/project/publicity-button.directive.html',
'scope': {
'isPublic': '=',
'projectId': '='
'projectId': '=',
'roleId': '@'
},
'link': link,
'controller': PublicityButtonController,

View File

@ -23,10 +23,10 @@
<a ng-if="vm.tagCount[repo] === 0" role="button" style="text-decoration: none;" data-toggle="collapse" data-parent="" aria-expanded="true" aria-controls="collapse//$index+1//">
<span class="glyphicon glyphicon-book"></span> &nbsp;//repo// &nbsp;&nbsp;<span class="badge">//vm.tagCount[repo]//</span>
</a>
<a ng-show="vm.tagCount[repo] > 0" class="pull-right" style="margin-right: 75px;" href="javascript:void(0)" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //" loading-progress hide-target="true" toggle-in-progress="vm.toggleInProgress[repo + '|']"><span class="glyphicon glyphicon-trash"></span></a>
<a ng-show="vm.tagCount[repo] > 0 && vm.roleId == 1" class="pull-right" style="margin-right: 75px;" href="javascript:void(0)" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //" loading-progress hide-target="true" toggle-in-progress="vm.toggleInProgress[repo + '|']"><span class="glyphicon glyphicon-trash"></span></a>
</h4>
</div>
<list-tag ng-show="vm.tagCount[repo] > 0" associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount" toggle-in-progress="vm.toggleInProgress" delete-by-tag="vm.deleteByTag()"></list-tag>
<list-tag ng-show="vm.tagCount[repo] > 0" associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount" toggle-in-progress="vm.toggleInProgress" delete-by-tag="vm.deleteByTag()" role-id="//vm.roleId//"></list-tag>
</div>
</div>
</div>

View File

@ -19,14 +19,14 @@
.module('harbor.repository')
.directive('listRepository', listRepository);
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName'];
ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName', '$window'];
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName) {
function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName, $window) {
$scope.subsTabPane = 30;
var vm = this;
vm.sectionHeight = {'min-height': '579px'};
vm.filterInput = '';
@ -104,7 +104,23 @@
}
function getRepositoryFailed(response) {
console.log('Failed to list repositories:' + response);
var errorMessage = '';
if(response.status === 404) {
errorMessage = $filter('tr')('project_does_not_exist');
}else{
errorMessage = $filter('tr')('failed_to_get_project');
}
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', errorMessage);
var emitInfo = {
'confirmOnly': true,
'contentType': 'text/html',
'action' : function() {
$window.location.href = '/dashboard';
}
};
$scope.$emit('raiseInfo', emitInfo);
console.log('Failed to list repositories:' + response.data);
}
function searchRepo() {
@ -155,6 +171,7 @@
function deleteRepositorySuccess(data, status) {
vm.toggleInProgress[vm.repoName + '|' + vm.tag] = false;
vm.retrieve();
$scope.$broadcast('refreshTags', true);
}
function deleteRepositoryFailed(data, status) {
@ -180,7 +197,8 @@
'restrict': 'E',
'templateUrl': '/static/resources/js/components/repository/list-repository.directive.html',
'scope': {
'sectionHeight': '='
'sectionHeight': '=',
'roleId': '@'
},
'link': link,
'controller': ListRepositoryController,

View File

@ -5,7 +5,7 @@
<th width="20%"><span class="glyphicon glyphicon-tags"></span> // 'tag' | tr //</th>
<th width="15%" style="text-align: center;">// 'image_details' | tr //</th>
<th width="40%">// 'pull_command' | tr //</th>
<th width="15%" style="text-align: center;">// 'operation' | tr //</th>
<th width="15%" ng-if="vm.roleId == 1" style="text-align: center;">// 'operation' | tr //</th>
</thead>
<tbody>
<tr ng-repeat="tag in vm.tags">
@ -14,7 +14,7 @@
<td>
<pull-command repo-name="//vm.repoName//" tag="//tag//"></pull-command>
</td>
<td style="text-align: center;"><a href="javascript:void(0);" ng-click="vm.deleteTag({repoName: vm.repoName, tag: tag})" title="// 'delete_tag' | tr //" loading-progress hide-target="true" toggle-in-progress="vm.toggleInProgress[vm.repoName +'|'+ tag]"><span class="glyphicon glyphicon-trash"></span></a></td>
<td style="text-align: center;"><a ng-if="vm.roleId == 1" href="javascript:void(0);" ng-click="vm.deleteTag({repoName: vm.repoName, tag: tag})" title="// 'delete_tag' | tr //" loading-progress hide-target="true" toggle-in-progress="vm.toggleInProgress[vm.repoName +'|'+ tag]"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</tbody>
</table>

View File

@ -85,7 +85,8 @@
'associateId': '=',
'repoName': '=',
'toggleInProgress': '=',
'deleteByTag': '&'
'deleteByTag': '&',
'roleId': '@'
},
'replace': true,
'controller': ListTagController,

View File

@ -32,7 +32,7 @@
</tr>
<tr ng-if="vm.integratedLogs.length > 0" ng-repeat="t in vm.integratedLogs">
<td width="18%">//t.username//</td>
<td width="28%"><a href="javascript:void(0);" ng-click="vm.gotoLog(t.project_id, t.username)">//t.repo_name//</a></td>
<td width="28%"><a href="javascript:void(0);" ng-click="vm.gotoRepo(t.project_id, t.repo_name)">//t.repo_name//</a></td>
<td width="15%">//t.repo_tag//</td>
<td width="14%">//t.operation//</td>
<td width="25%">//t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>

View File

@ -29,7 +29,7 @@
.success(listIntegratedLogSuccess)
.error(listIntegratedLogFailed);
vm.gotoLog = gotoLog;
vm.gotoRepo = gotoRepo;
function listIntegratedLogSuccess(data) {
vm.integratedLogs = data || [];
@ -42,8 +42,8 @@
console.log('Failed to get user logs:' + data);
}
function gotoLog(projectId, username) {
$window.location.href = '/repository#/logs?project_id=' + projectId + '#' + encodeURIComponent(username);
function gotoRepo(projectId, repoName) {
$window.location.href = '/repository#/repositories?project_id=' + projectId + '#' + encodeURIComponent(repoName);
}
}

View File

@ -31,7 +31,7 @@
<th width="20%">// 'email' | tr //</th>
<th width="35%">// 'registration_time' | tr //</th>
<th width="15%">// 'administrator' | tr //</th>
<th width="20%">// 'operation' | tr //</th>
<th width="20%" ng-if="vm.authMode === 'db_auth'">// 'operation' | tr //</th>
</thead>
</table>
</div>
@ -46,7 +46,7 @@
<td width="15%">
<toggle-admin current-user="vm.currentUser" has-admin-role="u.has_admin_role" user-id="//u.user_id//"></toggle-admin>
</td>
<td width="20%">
<td width="20%" ng-if="vm.authMode === 'db_auth'">
&nbsp;&nbsp;<a ng-if="vm.currentUser.user_id != u.user_id" href="javascript:void(0)" ng-click="vm.confirmToDelete(u.user_id, u.username)"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>

View File

@ -98,6 +98,9 @@
'restrict': 'E',
'templateUrl': '/static/resources/js/components/user/list-user.directive.html',
'link': link,
'scope': {
'authMode': '@'
},
'controller': ListUserController,
'controllerAs': 'vm',
'bindToController': true

View File

@ -26,35 +26,26 @@
var vm = this;
vm.isAdmin = (vm.hasAdminRole === 1);
vm.enabled = vm.isAdmin ? 0 : 1;
vm.toggle = toggle;
vm.editable = (vm.currentUser.user_id !== Number(vm.userId));
function toggle() {
ToggleAdminService(vm.userId, vm.enabled)
ToggleAdminService(vm.userId, vm.isAdmin ? 0 : 1)
.success(toggleAdminSuccess)
.error(toggleAdminFailed);
}
function toggleAdminSuccess(data, status) {
if(vm.isAdmin) {
vm.isAdmin = false;
}else{
vm.isAdmin = true;
}
console.log('Toggled userId:' + vm.userId + ' to admin:' + vm.isAdmin);
vm.isAdmin = !vm.isAdmin;
}
function toggleAdminFailed(data, status) {
console.log('Failed to toggle admin:' + data);
vm.isAdmin = !vm.isAdmin;
$scope.$emit('modalTitle', $filter('tr')('error'));
$scope.$emit('modalMessage', $filter('tr')('failed_to_toggle_admin'));
$scope.$emit('raiseError', true);
if(vm.isAdmin) {
vm.isAdmin = false;
}else{
vm.isAdmin = true;
}
console.log('Failed to toggle admin:' + data);
}
}

View File

@ -0,0 +1,44 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
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.
*/
(function() {
'use strict';
angular
.module('harbor.validator')
.directive('email', email);
email.$inject = ['EMAIL_REGEXP'];
function email(EMAIL_REGEXP) {
var directive = {
'require' : 'ngModel',
'link': link
};
return directive;
function link (scope, element, attrs, ctrl) {
ctrl.$validators.email = validator;
function validator(modelValue, viewValue) {
return EMAIL_REGEXP.test(modelValue);
}
}
}
})();

View File

@ -20,5 +20,7 @@
.module('harbor.validator')
.constant('INVALID_CHARS', [",","~","#", "$", "%"])
.constant('PASSWORD_REGEXP', /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,20}$/)
.constant('PROJECT_REGEXP', /^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$/);
.constant('PROJECT_REGEXP', /^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$/)
.constant('EMAIL_REGEXP', /^(([^<>()[\]\.,;:\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,}))$/);
})();

View File

@ -234,6 +234,7 @@ var locale_messages = {
'failed_to_get_project_member': 'Failed to get current project member.',
'failed_to_delete_repo': 'Failed to delete repository. ',
'failed_to_delete_repo_insuffient_permissions': 'Failed to delete repository, insuffient permissions.',
'failed_to_get_repo': 'Failed to get repositories.',
'failed_to_get_tag': 'Failed to get tag.',
'failed_to_get_log': 'Failed to get logs.',
'failed_to_get_project': 'Failed to get projects.',
@ -264,6 +265,7 @@ var locale_messages = {
'failed_to_update_destination': 'Failed to update destination.',
'failed_to_toggle_publicity_insuffient_permissions': 'Failed to toggle project publicity, insuffient permissions.',
'failed_to_toggle_publicity': 'Failed to toggle project publicity.',
'project_does_not_exist': 'Project does not exist.',
'project_admin': 'Project Admin',
'developer': 'Developer',
'guest': 'Guest',

View File

@ -234,6 +234,7 @@ var locale_messages = {
'failed_to_get_project_member': '无法获取当前项目成员。',
'failed_to_delete_repo': '无法删除镜像仓库。',
'failed_to_delete_repo_insuffient_permissions': '无法删除镜像仓库,权限不足。',
'failed_to_get_repo': '获取镜像仓库数据失败。',
'failed_to_get_tag': '获取标签数据失败。',
'failed_to_get_log': '获取日志数据失败。',
'failed_to_get_project': '获取项目数据失败。',
@ -264,6 +265,7 @@ var locale_messages = {
'failed_to_update_destination': '修改目标失败。',
'failed_to_toggle_publicity_insuffient_permissions': '切换项目公开性失败,权限不足。',
'failed_to_toggle_publicity': '切换项目公开性失败。',
'project_does_not_exist': '项目不存在。',
'project_admin': '项目管理员',
'developer': '开发人员',
'guest': '访客',

View File

@ -20,9 +20,9 @@
.module('harbor.session')
.controller('CurrentUserController', CurrentUserController);
CurrentUserController.$inject = ['$scope', 'CurrentUserService', 'currentUser', '$window', '$document'];
CurrentUserController.$inject = ['$scope', 'CurrentUserService', 'currentUser', '$window', '$document', 'LogOutService'];
function CurrentUserController($scope, CurrentUserService, currentUser, $window, $document) {
function CurrentUserController($scope, CurrentUserService, currentUser, $window, $document, LogOutService) {
var vm = this;
@ -32,16 +32,27 @@
function getCurrentUserComplete(response) {
if(angular.isDefined(response)) {
currentUser.set(response.data);
if(location.pathname === '/') {
currentUser.set(response.data);
if(location.pathname === '/') {
$window.location.href = '/dashboard';
}
}
}
function getCurrentUserFailed(e){
console.log('No session of current user.');
console.log('Failed to get current user:' + e);
LogOutService()
.success(logOutSuccess)
.error(logOutFailed);
}
function logOutSuccess(data, status) {
currentUser.unset();
}
function logOutFailed(data, status) {
console.log('Failed to log out:' + data);
}
}
})();

View File

@ -29,7 +29,7 @@
<div class="form-group">
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
<div class="col-sm-7">
<input type="email" class="form-control" id="email" ng-model="user.email" name="uEmail" required>
<input type="text" class="form-control" id="email" ng-model="user.email" name="uEmail" required email>
<div class="error-message" ng-messages="form.uEmail.$touched && form.uEmail.$error">
<span ng-message="required">// 'email_is_required' | tr //</span>
<span ng-message="email">// 'email_content_illegal' | tr //</span>

View File

@ -24,7 +24,7 @@
<span ng-if="vm.toggle">// 'system_management' | tr //</span>
<a ng-if="!vm.toggle" href="#/destinations" class="title-color" ng-click="vm.toggleAdminOption({target: 'system_management'})">// 'system_management' | tr //</a>
</h4>
<list-user ng-if="vm.target === 'users'"></list-user>
<list-user ng-if="vm.target === 'users'" auth-mode="{{ .AuthMode }}"></list-user>
<system-management ng-if="vm.target === 'system_management'"></system-management>
</div>
</div>

View File

@ -24,7 +24,7 @@
<div class="form-group">
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
<div class="col-sm-7">
<input type="email" class="form-control" id="email" ng-model="user.email" ng-model-options="{ debounce: 500 }" ng-change="vm.reset()" name="uEmail" required data-target="email">
<input type="text" class="form-control" id="email" ng-model="user.email" ng-model-options="{ debounce: 500 }" ng-change="vm.reset()" name="uEmail" required data-target="email" email>
<div class="error-message">
<div ng-messages="(form.$submitted || form.uEmail.$touched) && form.uEmail.$error">
<span ng-message="required">// 'email_is_required' | tr //</span>

View File

@ -21,7 +21,9 @@
{{ if eq .AddNew true }}
<li><a href="/add_new"><span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;// 'add_new_title' | tr //</a></li>
{{ end }}
{{ if eq .SettingAccount true }}
<li><a href="/account_setting"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;// 'account_setting' | tr //</a></li>
{{ end }}
<li class="dropdown-submenu">
<a tabindex="-1" href="#"><span class="glyphicon glyphicon-globe"></span>&nbsp;&nbsp;//vm.languageName//</a>
<ul class="dropdown-menu">

View File

@ -47,8 +47,8 @@
<th width="15%">// 'repositories' | tr //</th>
<th width="15%" ng-if="!vm.isPublic">// 'role' | tr //</th>
<th width="20%">// 'creation_time' | tr //</th>
<th width="15%">// 'publicity' | tr //</th>
<th width="10%">// 'operation' | tr //</th>
<th width="15%" ng-if="!vm.isPublic">// 'publicity' | tr //</th>
<th width="10%" ng-if="!vm.isPublic">// 'operation' | tr //</th>
</thead>
</table>
</div>
@ -63,9 +63,9 @@
<td width="15%">//p.repo_count//</td>
<td width="15%" ng-if="vm.isPublic === 0">//vm.getProjectRole(p.current_user_role_id) | tr//</td>
<td width="20%">//p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="15%"><publicity-button is-public="p.public" project-id="p.project_id"></publicity-button></td>
<td width="10%">
&nbsp;&nbsp;<a href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a>
<td width="15%" ng-if="!vm.isPublic"><publicity-button is-public="p.public" project-id="p.project_id" role-id="//p.current_user_role_id//"></publicity-button></td>
<td width="10%" ng-if="!vm.isPublic">
&nbsp;&nbsp;<a ng-if="p.current_user_role_id == 1" href="javascript:void(0)" ng-click="vm.confirmToDelete(p.project_id, p.name)"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>

View File

@ -30,13 +30,13 @@
<navigation-details target="vm.target" ng-show="vm.isProjectMember"></navigation-details>
</span>
</div>
<retrieve-projects target="vm.target" is-open="vm.isOpen" selected-project="vm.selectedProject" is-project-member="vm.isProjectMember" is-public="vm.isPublic"></retrieve-projects>
<retrieve-projects target="vm.target" is-open="vm.isOpen" selected-project="vm.selectedProject" role-id="vm.roleId" is-project-member="vm.isProjectMember" is-public="vm.isPublic"></retrieve-projects>
<!-- Tab panes -->
<div class="tab-content" ng-click="vm.closeRetrievePane()">
<input type="hidden" id="HarborRegUrl" value="{{.HarborRegUrl}}">
<list-repository ng-if="vm.target === 'repositories'" section-height="vm.sectionHeight"></list-repository>
<list-repository ng-if="vm.target === 'repositories'" section-height="vm.sectionHeight" role-id="//vm.roleId//"></list-repository>
<list-replication ng-if="vm.target === 'replication'" section-height="vm.sectionHeight"></list-replication>
<list-project-member ng-if="vm.target === 'users'" section-height="vm.sectionHeight"></list-project-member>
<list-project-member ng-if="vm.target === 'users'" section-height="vm.sectionHeight" role-id="//vm.roleId//"></list-project-member>
<list-log ng-if="vm.target === 'logs'" section-height="vm.sectionHeight" target="vm.target"></list-log>
</div>
</div>

View File

@ -130,6 +130,7 @@
<script src="/static/resources/js/components/validator/user-exist.validator.js"></script>
<script src="/static/resources/js/components/validator/invalid-chars.validator.js"></script>
<script src="/static/resources/js/components/validator/project-name.validator.js"></script>
<script src="/static/resources/js/components/validator/email.validator.js"></script>
<script src="/static/resources/js/components/search/search.module.js"></script>
<script src="/static/resources/js/components/search/search.directive.js"></script>

View File

@ -45,12 +45,11 @@
<div class="form-group">
<label for="email" class="col-sm-3 control-label">// 'email' | tr //:</label>
<div class="col-sm-7">
<input type="email" class="form-control" id="email" ng-model="user.email" name="uEmail" ng-model-options="{ updateOn: 'blur' }" required user-exists data-target="email" maxlength="50">
<div class="error-message" ng-messages="(form.$submitted || form.uEmail.$touched) &&form.uEmail.$error">
<input type="text" class="form-control" id="email" ng-model="user.email" name="uEmail" ng-model-options="{ updateOn: 'blur' }" required user-exists data-target="email" email>
<div class="error-message" ng-messages="(form.$submitted || form.uEmail.$touched) && form.uEmail.$error">
<span ng-message="required">// 'email_is_required' | tr //</span>
<span ng-message="email">// 'email_content_illegal' | tr //</span>
<span ng-message="userExists">// 'email_has_been_taken' | tr //</span>
<span ng-message="maxlength">// 'email_is_too_long' | tr //</span>
</div>
<p class="help-block small-size-fonts">// 'email_desc' | tr //</p>
</div>

View File

@ -1,7 +1,7 @@
version: '2'
services:
registry:
image: library/registry:2.4.0
image: library/registry:2.5.0
restart: always
volumes:
- /data/registry:/storage

View File

@ -0,0 +1,41 @@
Test 1-01 - User Registration (DB Mode)
=======
# Purpose:
To verify that a non-admin user can register an account (singup) when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
1. On the Harbor's home page, click "Sign Up"
2. Enter user information to register a user.
3. Use the username of the newly registered user to log in to the UI.
4. Log out from the UI.
5. Use the email of the newly registered user to log in to the UI.
6. On a Docker client host, use `docker login <harbor_host>` command to verify the user can log in by either the **username** or **email** . (verify both)
7. Log out from the UI and register another new user. Try to provide invalid values of input to see if validation works:
* username is the same as an existing user
* username is very long in length
* wrong email formatting
* email is very long in length
* password input does not compliant to password rule
* two passwords do not match
# Expected Outcome:
* A new user created in step 2.
* The new user can log in via UI in Step 3 and Step 5.
* The new user can log in via docker client in Step 6 by email and username.
* Invalid input during sign up can be rejected, proper error messages can be displayed in Step 7.
# Possible Problems:
None

View File

@ -0,0 +1,36 @@
Test 1-02 - User Log In and Log Out (DB Mode)
=======
# Purpose:
To verify that a non-admin user can log in and log out when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
**NOTE:** Use a non-admin user for this test case. Admin user has other test cases.
1. A non-admin user logs in to the UI by username.
2. The user logs out from the UI.
3. A non-admin user logs in to the UI by email.
4. The user logs out from the UI.
5. Use the incorrect password and username/email of the user to log in to the UI and check the error message.
6. On a Docker client host, use `docker login <harbor_host>` command to verify the user can log in by either the **username** or **email** . (check both)
7. Use `docker login <harbor_host>` command to log in with incorrect password by either the **username** or **email** .
# Expected Outcome:
* The user can log in via UI in Step 1 & 3, verify the dashboard and navigation bar are for a non-admin user. (should not see admin options)
* After the user logged out in Step 2 & 4, the login page will be displayed again.
* The error message in Step 5 should not show which input value is incorrect. It should only display the username(email) and password combination is incorrect.
* Docker client can log in in Step 6.
* Docker client fails to log in in Step 7.
# Possible Problems:
None

View File

@ -0,0 +1,36 @@
Test 1-03 - User update password (DB Mode)
=======
# Purpose:
To verify that a non-admin user can update password when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
**NOTE:** Use a **non-admin** user for this test case. Admin user has other test cases.
1. A non-admin user logs in to the UI by **username**.
2. The user changes his/her own password.
3. The user logs out.
4. The same user logs in to the UI by **email** using the new password.
5. The user can log in using `docker login` command using the new password.
6. The user goes to the page to change his/her own password. Provide invalid values of input to see if validation works:
* old password is incorrect
* password input does not compliant to password rule
* two passwords do not match
# Expected Outcome:
* Password can be changed in Step 2.
* User can log in using new password in Step 4.
* In Step 6, the user cannot change password due to various errors. Proper error message should be displayed.
# Possible Problems:
None

View File

@ -0,0 +1,35 @@
Test 1-04 - User update account settings (DB Mode)
=======
# Purpose:
To verify that a non-admin user can update his/her account settings when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
**NOTE:** Use a **non-admin** user for this test case. Admin user has other test cases.
1. A non-admin user logs in to UI.
2. The user changes his/her account settings, including email, full name and comments.
3. The user logs out.
4. The same user logs in again using **new email**, and verify the user's account settings had been changed.
5. The user goes to the page to change his/her settings again. Provide invalid values of input to see if validation works:
* email formatting
* very long email address string
* required fields are empty
# Expected Outcome:
* Account settings can be changed in Step 2.
* User can log in using new email in Step 4 and the settings are the same as input in Step 2.
* In Step 5, the user cannot change account settings due to various errors. Proper error message should be displayed.
# Possible Problems:
None

View File

@ -0,0 +1,41 @@
Test 1-05 - LDAP User Log In and Log Out (LDAP Mode)
=======
# Purpose:
To verify that a non-admin user can log in and log out when users are managed externally by an LDAP server (LDAP mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an LDAP server. (auth_mode is set to **ldap_auth** .) The user data is stored in an LDAP server.
* A linux host with Docker CLI installed (Docker client).
* An LDAP server has been set up and it has a few users available for testing.
# Test Steps:
1. A user has **NEVER** logged in to Harbor. He/she logs in to the UI for the first time by his/her id in LDAP. The id could be the uid attribte (or what is configured in ldap_uid) of his/her LDAP user DN.
2. The user logs out from the UI.
3. The user logs in again to the UI, should not see his/her own account settings and cannot change password(need to go to LDAP/AD for this).
4. The user logs out from the UI.
5. Use the incorrect password and username of the user to log in to the UI and check the error message.
6. On a Docker client host, use `docker login <harbor_host>` command to verify the user can log in by username/password.
7. Run `docker login <harbor_host>` command to log in with incorrect password of the user.
8. Log in as a system admin to UI, go to "admin Options" and should see above LDAP user in the list. System admin can assign or remove system admin role to the above LDAP user.
9. Disable or remove the user in LDAP.
10. The user should no longer log in to UI or by Docker client.
# Expected Outcome:
* The user can log in to UI by LDAP id in Step 1 & 3, verify the dashboard and navigation bar are for a non-admin user. (should not see admin options)
* In Step 3, also verify that the user cannot update his/her account settings and cannot change password.
* After the user logged out in Step 2 & 4, the login page will be displayed again.
* The error message in Step 5 should not show which input value is incorrect. It should only display the username(email) and password combination is incorrect.
* Docker client can log in in Step 6.
* Docker client fails to log in in Step 7.
* LDAP user should have no difference from a user in local database in Step 8.
* The user's login should fail in Step 10.
# Possible Problems:
None

View File

@ -0,0 +1,41 @@
Test 1-06 - AD (Active Directory) User Log In and Log Out (LDAP Mode)
=======
# Purpose:
To verify that a non-admin user can log in and log out when users are managed externally by an AD server (LDAP mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an AD server. (auth_mode is set to **ldap_auth** .) The user data is stored in an AD server.
* A linux host with Docker CLI installed (Docker client).
* An Active Directory (AD) server has been set up and it has a few users available for testing.
# Test Steps:
1. A user has **NEVER** logged in to Harbor. He/she logs in to the UI for the first time by his/her id in AD. The id could be the cn attribte (or what is configured in ldap_uid) of his/her AD user DN.
2. The user logs out from the UI.
3. The user logs in again to the UI, should not see his/her own account settings and cannot change password(need to go to LDAP/AD for this).
4. The user logs out from the UI.
5. Use the incorrect password and username of the user to log in to the UI and check the error message.
6. On a Docker client host, use `docker login <harbor_host>` command to verify the user can log in by username/password.
7. Run `docker login <harbor_host>` command to log in with incorrect password of the user.
8. Log in as a system admin to UI, go to "admin Options" and should see above AD user in the list. System admin can assign or remove system admin role of the above AD user.
9. Disable or remove the user in AD.
10. The user should no longer log in to UI or by Docker client.
# Expected Outcome:
* The user can log in to UI by AD id in Step 1 & 3, verify the dashboard and navigation bar are for a non-admin user. (should not see admin options)
* In Step 3, also verify that the user cannot update his/her account settings and cannot change password.
* After the user logged out in Step 2 & 4, the login page will be displayed again.
* The error message in Step 5 should not show which input value is incorrect. It should only display the username(email) and password combination is incorrect.
* Docker client can log in in Step 6.
* Docker client fails to log in in Step 7.
* AD user should have no difference from a user in local database in Step 8.
* The user's login should fail in Step 10.
# Possible Problems:
None

View File

@ -0,0 +1,37 @@
Test 1-07 - LDAP Mode general functions
=======
# Purpose:
To verify that Harbor's UI works properly in LDAP mode.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an AD or LDAP server. (auth_mode is set to **ldap_auth** .)
* An Active Directory (AD) or LDAP server has been set up and it has a few users available for testing.
# Test Steps:
1. The login page should not have "sign up" button. There is no need to allow self-registration.
2. Log in as a non system admin user(LDAP/AD) to the UI, he/she should NOT see the option of changing password or updating account settings.
3. Log out the user.
4. Log in as a system admin user to the UI, he/she should see the option of changing his/her own password or updating account settings.
5. The system admin user should NOT see the option of adding a new user.
6. The system admin user should see above LDAP/AD user in the list.
7. From the list, the system admin assigns system admin role to an AD/LDAP user A.
8. On a different browser(e.g. if the admin logs in using Chrome, then choose Safari or FireFox ), log in as the AD/LDAP user A to verify that user A has admin privilege.
9. From the list, the system admin removes system admin role from user A.
10. On a different browser, refresh the UI to verify user A has no admin privilege any more.
11. The system admin user deletes an AD/LDAP user in Harbor. **NOTE:** The user can log in again to regain access, however, all the previous projects he/she is a member of are lost.
To really disable a user's login, the user must be removed or disabled in AD or LDAP.
# Expected Outcome:
* As described in steps 1-6.
* A LDAP/AD user can be assigned or removed admin role in Step 7-10.
* The user can be deleted successfully in Step 11.
# Possible Problems:
None

View File

@ -0,0 +1,32 @@
Test 1-08 - Admin User Log In and Log Out (DB Mode or LDAP mode)
=======
# Purpose:
To verify that an admin user can log in and log out.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database or LDAP server.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
1. The admin user logs in to the UI by username.
2. The admin user logs out from the UI.
3. Use the incorrect password of the admin user to log in to the UI and check the error message.
4. On a Docker client host, use `docker login <harbor_host>` command to verify the admin user can log in.
5. Use `docker login <harbor_host>` command to log in with incorrect password.
# Expected Outcome:
* The admin user can log in/out successfully in Step 1 & 2.
* The error message in Step 3 should not show which input value is incorrect. It should only display the username and password combination is incorrect.
* Docker client can log in in Step 4.
* Docker client fails to log in in Step 5.
# Possible Problems:
None

View File

@ -0,0 +1,44 @@
Test 1-09 - Admin User Create, Delete and Recreate a User(DB Mode)
=======
# Purpose:
To verify that an admin user can create/delete/recreate a user when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
1. The admin user logs in to the UI.
2. The admin user creates a user from the UI.
3. On a different browser, log in as the newly created user.
4. The user views his/her own account settings.
5. The user create two projects in the UI.
6. On a Docker client host, use `docker login <harbor_host>` command to verify the user can log in.
7. The admin user deletes the user from the UI.
8. When clicking on any link on the page, the deleted user's session on the different browswer should be redirected to the login page and logged out.
9. On a Docker client host, use `docker login <harbor_host>` command to verify the user cannot log in.
10. The admin user re-creates a user with the same username of the deleted user.
11. On a different browser, log in as the re-created user.
12. The user views his/her own account settings.
13. The user views "My Projects" to see what projects he/she owns.
14. On a Docker client host, use `docker login <harbor_host>` command to verify the re-created user can log in.
15. The admin user view logs from the dashboard and should see two items of two project creation of the deleted user. The user has special number associated with him/her username.
# Expected Outcome:
* The newly created user can log in successfully in Step 3.
* The newly created user can view his/her own settings as entered by the admin in Step 4.
* The newly created user can create project successfully in Step 5.
* The admin should be able to re-create the user in Step 10.
* The re-created user should be able to log in, however, the previous projects no longer belong to him/her (Step 11-13).
* Docker client logs in successfully in Step 14.
* Should see special user id in logs in Step 15.
# Possible Problems:
None

View File

@ -0,0 +1,28 @@
Test 1-10 - Admin User update account settings (DB Mode)
=======
# Purpose:
To verify that the admin user can update his/her account settings.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
1. Thw admin user logs in to UI.
2. The user changes his/her account settings, including email, full name and comments.
3. The user logs out.
4. The admin user logs in again using **new email**, and verify the account settings had been changed.
# Expected Outcome:
* Account settings can be changed in Step 2.
* User can log in using new email in Step 4 and the settings are the same as input in Step 2.
# Possible Problems:
None

View File

@ -0,0 +1,29 @@
Test 1-11 - Admin User update password (DB Mode)
=======
# Purpose:
To verify that the admin user can update password.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
1. The admin user logs in to the UI by **username**.
2. The admin user changes his/her own password.
3. The admin user logs out.
4. The admin user logs in to the UI by **email** using the new password.
5. The admin user can log in using `docker login` command using the new password.
# Expected Outcome:
* Password can be changed in Step 2.
* User can log in using new password in Step 4.
# Possible Problems:
None

View File

@ -0,0 +1,28 @@
Test 1-12 - Admin User Assign and Remove Admin Role to a User.
=======
# Purpose:
To verify that Admin user can assign/remove admin role to/from a user.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
# Test Steps:
1. Log in as the admin user to the UI.
2. The admin user should see a list of users in "Admin Options".
3. From the list, the admin user assigns system admin role to a user A.
4. On a different browser(e.g. if the admin logs in using Chrome, then choose Safari or FireFox ), log in as user A to verify that user A has admin privilege.
5. From the list, the admin user removes system admin role from user A.
6. On a different browser, refresh the UI to verify user A has no admin privilege any more.
# Expected Outcome:
* As described in steps 1-6.
# Possible Problems:
None

View File

@ -0,0 +1,25 @@
Test 1-13 - Admin User Search Users.
=======
# Purpose:
To verify that Admin user can search users.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database, LDAP or AD.
# Test Steps:
1. Log in as the admin user to the UI.
2. The admin user should see a list of users in "Admin Options".
3. Enter different keywords or partial words to see if the user list can be filtered according to the criteria.
# Expected Outcome:
* As described in steps 1-3.
# Possible Problems:
None

View File

@ -0,0 +1,29 @@
Test 1-14 - LDAP Mode Admin Role General Functions
=======
# Purpose:
To verify that Harbor's UI of a user with system admin role works properly in LDAP mode.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an AD or LDAP server. (auth_mode is set to **ldap_auth** .)
* An Active Directory (AD) or LDAP server has been set up and it has a few users available for testing.
# Test Steps:
**NOTE:** The below non-admin user M should NOT be the same as the non-admin user in Test 1-07.
1. Assign an non-admin user M with system admin role and act as an admin user.
2. Repeat all steps in Test 1-07.
# Expected Outcome:
* A user with system admin role can perform all operations the same as the admin user.
* Same as Test 1-07.
# Possible Problems:
None

View File

@ -0,0 +1,29 @@
Test 1-15 - Admin Role User Create, Delete and Recreate a User(DB Mode)
=======
# Purpose:
To verify that an admin user can create/delete/recreate a user when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database.
* A linux host with Docker CLI installed (Docker client).
# Test Steps:
**NOTE:** The below non-admin user M should NOT be the same as the non-admin user in Test 1-09.
1. Assign an non-admin user M with system admin role and act as an admin user.
2. Repeat all steps in Test 1-09.
# Expected Outcome:
* A user with system admin role can perform all operations the same as the admin user.
* Same as Test 1-09.
# Possible Problems:
None

View File

@ -0,0 +1,42 @@
Test 2-01 - User Create Project (DB Mode)
=======
# Purpose:
To verify that a non-admin user can create projects when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
* At least two non-admin users are in Harbor.
# Test Steps:
**NOTE:**
In below test, user A and B are non-admin users. User A, B and project X, Y should be replaced by longer and meaningful names.
1. Log in to UI as user A (non-admin).
2. Create a new project X with publicity is off (default).
3. Create another new project Y with publicity is on .
4. While keeping user A logging on, in another browswer log in as user B (non-admin).
5. User B checks his/her public projects to see if project Y is listed and project X is not listed.
6. User A changes project X's publicity to on, project Y's publicity to off.
7. User B refreshes his/her public projects to see if project X is listed and project Y is not listed.
8. On a Docker client host, use `docker login <harbor_host>` to log in as user A.
9. User A runs `docker push` to push an image to project X, push an image to project Y.
10. User A checks in the browser that the images had been successfully pushed to project X and Y.
11. On a Docker client host, use `docker login <harbor_host>` to log in as user B.
12. User B runs `docker pull` to an image of project X, and an image of project Y.
# Expected Outcome:
* Step 5, user B should see project Y is listed and project X is not listed.
* Step 7, user B should see project X is listed and project Y is not listed.
* Step 9,10, images should be pushed to project X and Y successfully and can be viewed from UI.
* Step 11,12, user B can pull the image of project X, cannot pull from project Y.
# Possible Problems:
None

View File

@ -0,0 +1,38 @@
Test 2-02 - User Push Multiple Images (DB Mode)
=======
# Purpose:
To verify that a non-admin user can push multiple images to a project when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
* At least a non-admin user and the user has at least a project as project admin.
# Test Steps:
**NOTE:**
In below test, user A is non-admin user. User A and project X should be replaced by longer and meaningful names.
1. Log in to UI as user A (non-admin).
2. Verify User A has at least a project X with the role of project admin.
3. On a Docker client, log in as User A and run `docker push` to push an image with tag (e.g. nginx:1.5) to project X.
4. Continue to push at least 5 images with different tags, for example, nginx:1.6, nginx:1.7, nginx:1.8, nginx:release .
5. In the UI, go to "My Projects" to see if all images/tags are properly displayed.
6. Checks the detail info of each tag.
7. Enter keyword to search(filter) images/tags of a project.
8. On a Docker client, log in as User A and run `docker pull` to pull images with different tags from project X.
# Expected Outcome:
* Step 3-5, images can be pushed to project X and can be shown in UI.
* Step 6, image/tag info should be correct.
* Step 7, image/tag should be filtered and results should be matched the search criteria.
* Step 8, images can be pulled from project X.
# Possible Problems:
None

View File

@ -0,0 +1,31 @@
Test 2-03 - User Create Multiple Projects (DB Mode)
=======
# Purpose:
To verify that a non-admin user can create multiple projects when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
* At least a non-admin user.
# Test Steps:
**NOTE:**
In below test, user A is non-admin user. User A should be replaced by a longer and meaningful name.
1. Log in to UI as user A (non-admin).
2. Create 16 or more projects so that the pagination control has multiple pages.
3. Go through multiple pages of the list and click on a few projects to see if pagination work properly.
4. Search projects with keywords to see if the list and pagination update accordingly.
# Expected Outcome:
* As descirbed in step 3-4.
# Possible Problems:
None

View File

@ -0,0 +1,33 @@
Test 2-04 - User View Projects (DB Mode)
=======
# Purpose:
To verify that a non-admin user can view projects when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* There is at least a non-admin user.
* The user has at least 3 private projects.
* The registry has at least 3 public repositories.
# Test Steps:
**NOTE:**
In below test, user A is non-admin user. User A should be replaced by a longer and meaningful name.
1. Log in to UI as user A (non-admin).
2. Create at least 3 projects if he/she has less than 3 projects.
3. Switch a few times between "My Projects" and "Public projects" tab, view listed projects and click to check details of images.
4. Check logs of projects in "My Projects".
# Expected Outcome:
* Step 3, verify the information listed of projects are correctly displayed, such as creation time and role.
* Step 4, should see logs of the project.
# Possible Problems:
None

View File

@ -0,0 +1,39 @@
Test 2-05 - User Delete Images (DB Mode)
=======
# Purpose:
To verify that a non-admin user can delete images when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
* At least a non-admin user.
# Test Steps:
**NOTE:**
In below test, user A is non-admin user. User A and project X, Y should be replaced by longer and meaningful names.
1. Log in to UI as user A (non-admin).
2. Create a project X so that the user has the project admin role.
3. On a Docker client, log in as User A and run `docker push` to push an image to the project X, e.g. projectX/myimage:v1.
4. Push a second image with different tag to project X, e.g. projectX/myimage:v2 .
5. Push an image with different name to project X, e.g. projectX/newimage:v1 .
6. Run `docker pull` to verify images can be pushed successfully.
7. In UI, delete the three images one by one.
8. On a Docker client, log in as User A and run `docker pull` to pull the three deleted images of project X.
9. In UI, delete project X.
10. Run `docker pull` to pull the three deleted images of the project X.
# Expected Outcome:
* Step 7, images should be deleted successfully.
* Step 9, project X should be deleted successfully.
* Step 8,10, docker client should report error message.
# Possible Problems:
None

View File

@ -0,0 +1,51 @@
Test 2-06 - User Delete Projects (DB Mode)
=======
# Purpose:
To verify that a non-admin user can delete projects when users are managed locally by Harbor (DB mode).
# References:
User guide
# Environment:
* This test requires that two(2) Harbor instances are running and available.
* Harbor is set to authenticate against a local database. ( auth_mode is set to **db_auth** .) The user data is stored in a local database.
* A linux host with Docker CLI installed (Docker client).
* At least a non-admin user.
# Test Steps:
**NOTE:**
* In below test, user A is non-admin user. User A and project X should be replaced by longer and meaningful names.
* Must use two kinds of browsers at the same time to ensure independent sessions. For example, use Chrome and Firefox, or Chrome and Safari.
* DO NOT use the same browser to log in two users at the same time in different windows(tabs).
1. Log in to UI as user A (non-admin).
2. Create a project X so that the user has the project admin role.
3. On a Docker client, log in as User A and run `docker push` to push an image to project X, e.g. projectX/myimage:v1.
4. Push an image with different name to project X, e.g. projectX/newimage:v1 .
5. Run `docker pull` to verify images can be pushed successfully.
6. In UI, delete project X directly. (should fail with errors)
7. While keeping the current user A logged on, in a different browser, log in as admin user.
8. Under "Admin Options", create a replication policy of project X to another Harbor instance. (Do not need to activate this policy.)
9. Switch to the UI of User A, delete all images under project X.
10. In user A's UI, delete project X directly. (should fail with errors)
11. Switch to the UI of admin user, delete the replication policy of project X.
12. In user A's UI, delete project X.
13. In user A's UI, recreate project X,
14. On a Docker client, log in as User A and run `docker push` to push an image to project X, e.g. projectX/anotherimage:v1. The image name should not be the same as those deleted in previous steps.
15. Switch to the UI of admin user, view images under the re-created project X.
16. As an admin user, view the log in dashboard and should see delete and create operaions of project X.
# Expected Outcome:
* Step 6, deleting project X should fail because there are images under it.
* Step 10, deleting project X should fail because there is an image replication policy under it.
* Step 12, deleting project X should succeed.
* Step 13, re-creation of project X should succeed.
* Step 14, push should succeed.
* Step 15, project X should contain newly pushed image. The old images should not be displayed.
* Step 16, there should be logs for delete and create of project X, notice the project name of the deleted operation is displayed differently.
# Possible Problems:
None

View File

@ -0,0 +1,25 @@
Test 2-11 - User Create Project (LDAP Mode)
=======
# Purpose:
To verify that a non-admin user can create projects in (LDAP mode).
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an LDAP or AD server. ( auth_mode is set to **ldap_auth** .) The user data is stored in an LDAP or AD server.
* A linux host with Docker CLI installed (Docker client).
* At least two non-admin users are in Harbor.
# Test Steps:
Same as Test 2-01 except that users are from LDAP/AD.
# Expected Outcome:
* Same as Test 2-01.
# Possible Problems:
None

View File

@ -0,0 +1,25 @@
Test 2-12 - User Push Multiple Images (LDAP Mode)
=======
# Purpose:
To verify that a non-admin user can push multiple images to a project in LDAP mode.
# References:
User guide
# Environment:
* This test requires that a Harbor instance is running and available.
* Harbor is set to authenticate against an LDAP or AD server. ( auth_mode is set to **ldap_auth** .) The user data is stored in an LDAP or AD server.
* A linux host with Docker CLI installed (Docker client).
* At least a non-admin user and the user has at least a project as project admin.
# Test Steps:
Same as Test 2-02 except that users are from LDAP/AD.
# Expected Outcome:
* Same as Test 2-02.
# Possible Problems:
None

Some files were not shown because too many files have changed in this diff Show More