Merge remote-tracking branch 'upstream/master'

This commit is contained in:
wy65701436 2016-04-08 00:07:37 -07:00
commit 8ae98de7fa
25 changed files with 289 additions and 156 deletions

View File

@ -101,3 +101,12 @@ create table access_log (
FOREIGN KEY (user_id) REFERENCES user(user_id), FOREIGN KEY (user_id) REFERENCES user(user_id),
FOREIGN KEY (project_id) REFERENCES project (project_id) FOREIGN KEY (project_id) REFERENCES project (project_id)
); );
create table properties (
k varchar(64) NOT NULL,
v varchar(128) NOT NULL,
primary key (k)
);
insert into properties (k, v) values
('schema_version', '0.1.1');

View File

@ -1,62 +1,69 @@
log: version: '2'
build: ./log/ services:
volumes: log:
- /var/log/harbor/:/var/log/docker/ build: ./log/
ports: volumes:
- 1514:514 - /var/log/harbor/:/var/log/docker/
registry: ports:
image: library/registry:2.3.0 - 1514:514
volumes: registry:
- /data/registry:/storage image: library/registry:2.3.0
- ./config/registry/:/etc/registry/ volumes:
ports: - /data/registry:/storage
- 5001:5001 - ./config/registry/:/etc/registry/
command: ports:
/etc/registry/config.yml - 5001:5001
links: command:
- log /etc/registry/config.yml
log_driver: "syslog" depends_on:
log_opt: - log
syslog-address: "tcp://127.0.0.1:1514" logging:
syslog-tag: "registry" driver: "syslog"
mysql: options:
build: ./db/ syslog-address: "tcp://127.0.0.1:1514"
volumes: syslog-tag: "registry"
- /data/database:/var/lib/mysql mysql:
env_file: build: ./db/
- ./config/db/env volumes:
links: - /data/database:/var/lib/mysql
- log env_file:
log_driver: "syslog" - ./config/db/env
log_opt: depends_on:
syslog-address: "tcp://127.0.0.1:1514" - log
syslog-tag: "mysql" logging:
ui: driver: "syslog"
build: ../ options:
env_file: syslog-address: "tcp://127.0.0.1:1514"
- ./config/ui/env syslog-tag: "mysql"
volumes: ui:
- ./config/ui/app.conf:/etc/ui/app.conf build:
- ./config/ui/private_key.pem:/etc/ui/private_key.pem context: ../
links: dockerfile: Dockerfile.ui
- registry env_file:
- mysql - ./config/ui/env
- log volumes:
log_driver: "syslog" - ./config/ui/app.conf:/etc/ui/app.conf
log_opt: - ./config/ui/private_key.pem:/etc/ui/private_key.pem
syslog-address: "tcp://127.0.0.1:1514" depends_on:
syslog-tag: "ui" - log
proxy: logging:
image: library/nginx:1.9 driver: "syslog"
volumes: options:
- ./config/nginx:/etc/nginx syslog-address: "tcp://127.0.0.1:1514"
links: syslog-tag: "ui"
- ui proxy:
- registry image: library/nginx:1.9
- log volumes:
ports: - ./config/nginx:/etc/nginx
- 80:80 ports:
log_driver: "syslog" - 80:80
log_opt: depends_on:
syslog-address: "tcp://127.0.0.1:1514" - mysql
syslog-tag: "proxy" - registry
- ui
- log
logging:
driver: "syslog"
options:
syslog-address: "tcp://127.0.0.1:1514"
syslog-tag: "proxy"

View File

@ -1,24 +1,35 @@
## CONFIGURATIONS ## Configuration file of Harbor
#The endpoint for user to access UI and registry service
hostname = mydomain.com #The IP address or hostname to access admin UI and registry service.
#The protocol for accessing the UI and token/notification service, by default it is http #DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
#User can set it to https if ssl is setup on nginx hostname = reg.mydomain.com
#The protocol for accessing the UI and token/notification service, by default it is http.
#It can be set to https if ssl is enabled on nginx.
ui_url_protocol = http ui_url_protocol = http
#Email settings for ui to send password resetting emails
#Email account settings for sending out password resetting emails.
email_server = smtp.mydomain.com email_server = smtp.mydomain.com
email_server_port = 25 email_server_port = 25
email_username = sample_admin@mydomain.com email_username = sample_admin@mydomain.com
email_password = abc email_password = abc
email_from = admin <sample_admin@mydomain.com> email_from = admin <sample_admin@mydomain.com>
##The password of harbor admin
##The password of Harbor admin, change this before any production use.
harbor_admin_password= Harbor12345 harbor_admin_password= Harbor12345
##By default the auth mode is db_auth, i.e. the creadentials are stored in a databse
#please set it to ldap_auth if you want to verify user's credentials against an ldap server. ##By default the auth mode is db_auth, i.e. the credentials are stored in a local database.
#Set it to ldap_auth if you want to verify a user's credentials against an LDAP server.
auth_mode = db_auth auth_mode = db_auth
#The url for ldap endpoint
#The url for an ldap endpoint.
ldap_url = ldaps://ldap.mydomain.com ldap_url = ldaps://ldap.mydomain.com
#The basedn template for verifying the user's password
#The basedn template to look up a user in LDAP and verify the user's password.
ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com
#The password for root user of db
#The password for the root user of mysql db, change this before any production use.
db_password = root123 db_password = root123
#Switch for self-registration feature
self_registration = on
##### #####

View File

@ -25,6 +25,7 @@ auth_mode = cp.get("configuration", "auth_mode")
ldap_url = cp.get("configuration", "ldap_url") ldap_url = cp.get("configuration", "ldap_url")
ldap_basedn = cp.get("configuration", "ldap_basedn") ldap_basedn = cp.get("configuration", "ldap_basedn")
db_password = cp.get("configuration", "db_password") db_password = cp.get("configuration", "db_password")
self_registration = cp.get("configuration", "self_registration")
######## ########
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
@ -60,11 +61,13 @@ for f in conf_files:
render(os.path.join(templates_dir, "ui", "env"), render(os.path.join(templates_dir, "ui", "env"),
ui_conf_env, ui_conf_env,
hostname=hostname, hostname=hostname,
db_password=db_password,
ui_url=ui_url, ui_url=ui_url,
auth_mode=auth_mode, auth_mode=auth_mode,
admin_pwd=harbor_admin_password, admin_pwd=harbor_admin_password,
ldap_url=ldap_url, ldap_url=ldap_url,
ldap_basedn=ldap_basedn) ldap_basedn=ldap_basedn,
self_registration=self_registration)
render(os.path.join(templates_dir, "ui", "app.conf"), render(os.path.join(templates_dir, "ui", "app.conf"),
ui_conf, ui_conf,

View File

@ -27,7 +27,7 @@ notifications:
endpoints: endpoints:
- name: harbor - name: harbor
disabled: false disabled: false
url: $ui_url/service/notifications url: http://ui/service/notifications
timeout: 500 timeout: 500
threshold: 5 threshold: 5
backoff: 1000 backoff: 1000

View File

@ -1,5 +1,7 @@
MYSQL_HOST=mysql MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USR=root MYSQL_USR=root
MYSQL_PWD=$db_password
REGISTRY_URL=http://registry:5000 REGISTRY_URL=http://registry:5000
CONFIG_PATH=/etc/ui/app.conf CONFIG_PATH=/etc/ui/app.conf
HARBOR_REG_URL=$hostname HARBOR_REG_URL=$hostname
@ -8,4 +10,5 @@ HARBOR_URL=$ui_url
AUTH_MODE=$auth_mode AUTH_MODE=$auth_mode
LDAP_URL=$ldap_url LDAP_URL=$ldap_url
LDAP_BASE_DN=$ldap_basedn LDAP_BASE_DN=$ldap_basedn
SELF_REGISTRATION=$self_registration
LOG_LEVEL=debug LOG_LEVEL=debug

View File

@ -9,30 +9,28 @@ RUN apt-get update \
COPY . /go/src/github.com/vmware/harbor COPY . /go/src/github.com/vmware/harbor
#golang.org is blocked in China #golang.org is blocked in China
COPY ./vendor/golang.org /go/src/golang.org COPY ./vendor/golang.org /go/src/golang.org
WORKDIR /go/src/github.com/vmware/harbor WORKDIR /go/src/github.com/vmware/harbor/ui
ENV GO15VENDOREXPERIMENT 1 ENV GO15VENDOREXPERIMENT 1
RUN go get -d github.com/docker/distribution \ RUN go get -d github.com/docker/distribution \
&& go get -d github.com/docker/libtrust \ && go get -d github.com/docker/libtrust \
&& go get -d github.com/go-sql-driver/mysql \ && go get -d github.com/go-sql-driver/mysql \
&& go install -v -a && go build -v -a -o /go/bin/harbor_ui
ENV MYSQL_USR root \ ENV MYSQL_USR root \
MYSQL_PWD root \ MYSQL_PWD root \
MYSQL_PORT_3306_TCP_ADDR localhost \
MYSQL_PORT_3306_TCP_PORT 3306 \
REGISTRY_URL localhost:5000 REGISTRY_URL localhost:5000
COPY views /go/bin/views COPY views /go/bin/views
COPY static /go/bin/static COPY static /go/bin/static
COPY favicon.ico /go/bin/favicon.ico COPY favicon.ico /go/bin/favicon.ico
RUN chmod u+x /go/bin/harbor \ RUN chmod u+x /go/bin/harbor_ui \
&& sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \ && sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf \
&& sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf && sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf
WORKDIR /go/bin/ WORKDIR /go/bin/
ENTRYPOINT ["/go/bin/harbor"] ENTRYPOINT ["/go/bin/harbor_ui"]
EXPOSE 80 EXPOSE 80

1
contrib/README.md Normal file
View File

@ -0,0 +1 @@
The `contrib` directory contains documents, scripts, and other helpful things which are contributed by community.

View File

@ -16,12 +16,14 @@
package controllers package controllers
import ( import (
"net/http"
"os" "os"
"strings" "strings"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/beego/i18n" "github.com/beego/i18n"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/utils/log"
) )
// CommonController handles request from UI that doesn't expect a page, such as /login /logout ... // CommonController handles request from UI that doesn't expect a page, such as /login /logout ...
@ -38,6 +40,9 @@ func (c *CommonController) Render() error {
type BaseController struct { type BaseController struct {
beego.Controller beego.Controller
i18n.Locale i18n.Locale
SelfRegistration bool
IsAdmin bool
AuthMode string
} }
type langType struct { type langType struct {
@ -93,15 +98,35 @@ func (b *BaseController) Prepare() {
b.Data["CurLang"] = curLang.Name b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs b.Data["RestLangs"] = restLangs
sessionUserID := b.GetSession("userId") authMode := strings.ToLower(os.Getenv("AUTH_MODE"))
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
}
authMode := os.Getenv("AUTH_MODE")
if authMode == "" { if authMode == "" {
authMode = "db_auth" authMode = "db_auth"
} }
b.Data["AuthMode"] = authMode b.AuthMode = authMode
b.Data["AuthMode"] = b.AuthMode
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
if selfRegistration == "on" {
b.SelfRegistration = true
}
sessionUserID := b.GetSession("userId")
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
b.Data["UserId"] = sessionUserID.(int)
var err error
b.IsAdmin, err = dao.IsAdminRole(sessionUserID.(int))
if err != nil {
log.Errorf("Error occurred in IsAdminRole:%v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
b.Data["IsAdmin"] = b.IsAdmin
b.Data["SelfRegistration"] = b.SelfRegistration
} }
// ForwardTo setup layout and template for content for a page. // ForwardTo setup layout and template for content for a page.

View File

@ -17,11 +17,11 @@ package controllers
import ( import (
"net/http" "net/http"
"os"
"strings" "strings"
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
) )
@ -32,35 +32,70 @@ type RegisterController struct {
// Get renders the Sign In page, it only works if the auth mode is set to db_auth // Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (rc *RegisterController) Get() { func (rc *RegisterController) Get() {
authMode := os.Getenv("AUTH_MODE")
if authMode == "" || authMode == "db_auth" { if !rc.SelfRegistration {
log.Warning("Registration is disabled when self-registration is off.")
rc.Redirect("/signIn", http.StatusFound)
}
if rc.AuthMode == "db_auth" {
rc.ForwardTo("page_title_registration", "register") rc.ForwardTo("page_title_registration", "register")
} else { } else {
rc.Redirect("/signIn", http.StatusNotFound) rc.Redirect("/signIn", http.StatusFound)
}
}
// AddUserController handles request for adding user with an admin role user
type AddUserController struct {
BaseController
}
// Get renders the Sign In page, it only works if the auth mode is set to db_auth
func (ac *AddUserController) Get() {
if !ac.IsAdmin {
log.Warning("Add user can only be used by admin role user.")
ac.Redirect("/signIn", http.StatusFound)
}
if ac.AuthMode == "db_auth" {
ac.ForwardTo("page_title_add_user", "register")
} else {
ac.Redirect("/signIn", http.StatusFound)
} }
} }
// SignUp insert data into DB based on data in form. // SignUp insert data into DB based on data in form.
func (rc *CommonController) SignUp() { func (cc *CommonController) SignUp() {
username := strings.TrimSpace(rc.GetString("username"))
email := strings.TrimSpace(rc.GetString("email")) if !(cc.AuthMode == "db_auth") {
realname := strings.TrimSpace(rc.GetString("realname")) cc.CustomAbort(http.StatusForbidden, "")
password := strings.TrimSpace(rc.GetString("password")) }
comment := strings.TrimSpace(rc.GetString("comment"))
if !(cc.SelfRegistration || cc.IsAdmin) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
cc.CustomAbort(http.StatusForbidden, "")
}
username := strings.TrimSpace(cc.GetString("username"))
email := strings.TrimSpace(cc.GetString("email"))
realname := strings.TrimSpace(cc.GetString("realname"))
password := strings.TrimSpace(cc.GetString("password"))
comment := strings.TrimSpace(cc.GetString("comment"))
user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment} user := models.User{Username: username, Email: email, Realname: realname, Password: password, Comment: comment}
_, err := dao.Register(user) _, err := dao.Register(user)
if err != nil { if err != nil {
log.Errorf("Error occurred in Register: %v", err) log.Errorf("Error occurred in Register: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.") cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
} }
} }
// UserExists checks if user exists when user input value in sign in form. // UserExists checks if user exists when user input value in sign in form.
func (rc *CommonController) UserExists() { func (cc *CommonController) UserExists() {
target := rc.GetString("target") target := cc.GetString("target")
value := rc.GetString("value") value := cc.GetString("value")
user := models.User{} user := models.User{}
switch target { switch target {
@ -73,8 +108,8 @@ func (rc *CommonController) UserExists() {
exist, err := dao.UserExists(user, target) exist, err := dao.UserExists(user, target)
if err != nil { if err != nil {
log.Errorf("Error occurred in UserExists: %v", err) log.Errorf("Error occurred in UserExists: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.") cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
} }
rc.Data["json"] = exist cc.Data["json"] = exist
rc.ServeJSON() cc.ServeJSON()
} }

View File

@ -66,18 +66,11 @@ func GenerateRandomString() (string, error) {
func InitDB() { func InitDB() {
orm.RegisterDriver("mysql", orm.DRMySQL) orm.RegisterDriver("mysql", orm.DRMySQL)
addr := os.Getenv("MYSQL_HOST") addr := os.Getenv("MYSQL_HOST")
if len(addr) == 0 { port := os.Getenv("MYSQL_PORT")
addr = os.Getenv("MYSQL_PORT_3306_TCP_ADDR")
}
port := os.Getenv("MYSQL_PORT_3306_TCP_PORT")
username := os.Getenv("MYSQL_USR") username := os.Getenv("MYSQL_USR")
password := os.Getenv("MYSQL_PWD")
password := os.Getenv("MYSQL_ENV_MYSQL_ROOT_PASSWORD") log.Debugf("db url: %s:%s, db user: %s", addr, port, username)
if len(password) == 0 {
password = os.Getenv("MYSQL_PWD")
}
dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry" dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
ch := make(chan int, 1) ch := make(chan int, 1)
go func() { go func() {

View File

@ -128,8 +128,8 @@ func TestMain(m *testing.M) {
log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword) log.Infof("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
os.Setenv("MYSQL_PORT_3306_TCP_ADDR", dbHost) os.Setenv("MYSQL_HOST", dbHost)
os.Setenv("MYSQL_PORT_3306_TCP_PORT", dbPort) os.Setenv("MYSQL_PORT", dbPort)
os.Setenv("MYSQL_USR", dbUser) os.Setenv("MYSQL_USR", dbUser)
os.Setenv("MYSQL_PWD", dbPassword) os.Setenv("MYSQL_PWD", dbPassword)
os.Setenv("AUTH_MODE", "db_auth") os.Setenv("AUTH_MODE", "db_auth")

View File

@ -5,7 +5,7 @@ Harbor can be installed from the source code by using "docker-compose up" comman
Harbor is deployed as several Docker containers. Hence, it can be deployed on any Linux distribution that supports Docker. Harbor is deployed as several Docker containers. Hence, it can be deployed on any Linux distribution that supports Docker.
Before deploying Harbor, the target machine requires Python, Docker, Docker Compose to be installed. Before deploying Harbor, the target machine requires Python, Docker, Docker Compose to be installed.
* Python should be version 2.7 or higher. Some Linux distributions (Gentoo, Arch) may not have a Python interpreter installed by default. On those systems, you need to install Python manually. * Python should be version 2.7 or higher. Some Linux distributions (Gentoo, Arch) may not have a Python interpreter installed by default. On those systems, you need to install Python manually.
* The Docker engine should be version 1.8 or higher. For the details to install Docker engine, please refer to: https://docs.docker.com/engine/installation/ * The Docker engine should be version 1.10 or higher. For the details to install Docker engine, please refer to: https://docs.docker.com/engine/installation/
* The Docker Compose needs to be version 1.6.0 or higher. For the details to install Docker compose, please refer to: https://docs.docker.com/compose/install/ * The Docker Compose needs to be version 1.6.0 or higher. For the details to install Docker compose, please refer to: https://docs.docker.com/compose/install/
### Configuration of Harbor ### Configuration of Harbor
@ -139,4 +139,4 @@ Removing harbor_mysql_1 ... done
### Persistent data and log files ### Persistent data and log files
By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged. By default, the data of database and image files in the registry are persisted in the directory **/data/** of the target machine. When Harbor's containers are removed and recreated, the data remain unchanged.
Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host. Harbor leverages rsyslog to collect the logs of each container, by default the log files are stored in the directory **/var/log/harbor/** on Harbor's host.

View File

@ -42,13 +42,14 @@ func (a *TokenHandler) Get() {
username, password, _ := request.BasicAuth() username, password, _ := request.BasicAuth()
authenticated := authenticate(username, password) authenticated := authenticate(username, password)
service := a.GetString("service") service := a.GetString("service")
scope := a.GetString("scope") scopes := a.GetStrings("scope")
log.Debugf("scopes: %+v", scopes)
if len(scope) == 0 && !authenticated { if len(scopes) == 0 && !authenticated {
log.Info("login request with invalid credentials") log.Info("login request with invalid credentials")
a.CustomAbort(http.StatusUnauthorized, "") a.CustomAbort(http.StatusUnauthorized, "")
} }
access := svc_utils.GetResourceActions(scope) access := svc_utils.GetResourceActions(scopes)
for _, a := range access { for _, a := range access {
svc_utils.FilterAccess(username, authenticated, a) svc_utils.FilterAccess(username, authenticated, a)
} }

View File

@ -38,17 +38,19 @@ const (
) )
// GetResourceActions ... // GetResourceActions ...
func GetResourceActions(scope string) []*token.ResourceActions { func GetResourceActions(scopes []string) []*token.ResourceActions {
var res []*token.ResourceActions var res []*token.ResourceActions
if scope == "" { for _, s := range scopes {
return res if s == "" {
continue
}
items := strings.Split(s, ":")
res = append(res, &token.ResourceActions{
Type: items[0],
Name: items[1],
Actions: strings.Split(items[2], ","),
})
} }
items := strings.Split(scope, ":")
res = append(res, &token.ResourceActions{
Type: items[0],
Name: items[1],
Actions: strings.Split(items[2], ","),
})
return res return res
} }
@ -66,9 +68,12 @@ func FilterAccess(username string, authenticated bool, a *token.ResourceActions)
if strings.Contains(a.Name, "/") { //Only check the permission when the requested image has a namespace, i.e. project if strings.Contains(a.Name, "/") { //Only check the permission when the requested image has a namespace, i.e. project
projectName := a.Name[0:strings.LastIndex(a.Name, "/")] projectName := a.Name[0:strings.LastIndex(a.Name, "/")]
var permission string var permission string
var err error
if authenticated { if authenticated {
if username == "admin" { isAdmin, err := dao.IsAdminRole(username)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
}
if isAdmin {
exist, err := dao.ProjectExists(projectName) exist, err := dao.ProjectExists(projectName)
if err != nil { if err != nil {
log.Errorf("Error occurred in CheckExistProject: %v", err) log.Errorf("Error occurred in CheckExistProject: %v", err)
@ -100,8 +105,8 @@ func FilterAccess(username string, authenticated bool, a *token.ResourceActions)
} }
// GenTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy. // GenTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy.
func GenTokenForUI(username, service, scope string) (string, error) { func GenTokenForUI(username string, service string, scopes []string) (string, error) {
access := GetResourceActions(scope) access := GetResourceActions(scopes)
for _, a := range access { for _, a := range access {
FilterAccess(username, true, a) FilterAccess(username, true, a)
} }

View File

@ -63,14 +63,15 @@ func RegistryAPIGet(url, username string) ([]byte, error) {
authenticate := response.Header.Get("WWW-Authenticate") authenticate := response.Header.Get("WWW-Authenticate")
log.Debugf("authenticate header: %s", authenticate) log.Debugf("authenticate header: %s", authenticate)
var service string var service string
var scope string var scopes []string
//Disregard the case for hanlding multiple scopes for http call initiated from UI, as there's refactor planned.
re := regexp.MustCompile(`service=\"(.*?)\".*scope=\"(.*?)\"`) re := regexp.MustCompile(`service=\"(.*?)\".*scope=\"(.*?)\"`)
res := re.FindStringSubmatch(authenticate) res := re.FindStringSubmatch(authenticate)
if len(res) > 2 { if len(res) > 2 {
service = res[1] service = res[1]
scope = res[2] scopes = append(scopes, res[2])
} }
token, err := GenTokenForUI(username, service, scope) token, err := GenTokenForUI(username, service, scopes)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,6 +3,7 @@ page_title_sign_in = Sign In - Harbor
page_title_project = Project - Harbor page_title_project = Project - Harbor
page_title_item_details = Details - Harbor page_title_item_details = Details - Harbor
page_title_registration = Sign Up - Harbor page_title_registration = Sign Up - Harbor
page_title_add_user = Add User - Harbor
page_title_forgot_password = Forgot Password - Harbor page_title_forgot_password = Forgot Password - Harbor
title_forgot_password = Forgot Password title_forgot_password = Forgot Password
page_title_reset_password = Reset Password - Harbor page_title_reset_password = Reset Password - Harbor
@ -12,6 +13,7 @@ title_change_password = Change Password
page_title_search = Search - Harbor page_title_search = Search - Harbor
sign_in = Sign In sign_in = Sign In
sign_up = Sign Up sign_up = Sign Up
add_user = Add User
log_out = Log Out log_out = Log Out
search_placeholder = projects or repositories search_placeholder = projects or repositories
change_password = Change Password change_password = Change Password

View File

@ -177,6 +177,10 @@ var global_messages = {
"en-US": "Sign Up", "en-US": "Sign Up",
"zh-CN": "注册" "zh-CN": "注册"
}, },
"title_add_user": {
"en-US": "Add User",
"zh-CN": "新增用户"
},
"registered_successfully": { "registered_successfully": {
"en-US": "Signed up successfully.", "en-US": "Signed up successfully.",
"zh-CN": "注册成功。" "zh-CN": "注册成功。"
@ -185,6 +189,14 @@ var global_messages = {
"en-US": "Failed to sign up.", "en-US": "Failed to sign up.",
"zh-CN": "注册失败。" "zh-CN": "注册失败。"
}, },
"added_user_successfully": {
"en-US": "Added user successfully.",
"zh-CN": "新增用户成功。"
},
"added_user_failed": {
"en-US": "Added user failed.",
"zh-CN": "新增用户失败。"
},
"projects" : { "projects" : {
"en-US": "Projects", "en-US": "Projects",
"zh-CN": "项目" "zh-CN": "项目"

View File

@ -3,6 +3,7 @@ page_title_sign_in = 登录 - Harbor
page_title_project = 项目 - Harbor page_title_project = 项目 - Harbor
page_title_item_details = 详细信息 - Harbor page_title_item_details = 详细信息 - Harbor
page_title_registration = 注册 - Harbor page_title_registration = 注册 - Harbor
page_title_add_user = 新增用户 - Harbor
page_title_forgot_password = 忘记密码 - Harbor page_title_forgot_password = 忘记密码 - Harbor
title_forgot_password = 忘记密码 title_forgot_password = 忘记密码
page_title_reset_password = 重置密码 - Harbor page_title_reset_password = 重置密码 - Harbor
@ -12,6 +13,7 @@ title_change_password = 修改密码
page_title_search = 搜索 - Harbor page_title_search = 搜索 - Harbor
sign_in = 登录 sign_in = 登录
sign_up = 注册 sign_up = 注册
add_user = 新增用户
log_out = 注销 log_out = 注销
search_placeholder = 项目或镜像名称 search_placeholder = 项目或镜像名称
change_password = 修改密码 change_password = 修改密码

View File

@ -30,12 +30,14 @@ jQuery(function(){
$("#btnPageSignUp").on("click", function(){ $("#btnPageSignUp").on("click", function(){
validateOptions.Validate(function() { validateOptions.Validate(function() {
var username = $.trim($("#Username").val()); var username = $.trim($("#Username").val());
var email = $.trim($("#Email").val()); var email = $.trim($("#Email").val());
var password = $.trim($("#Password").val()); var password = $.trim($("#Password").val());
var confirmedPassword = $.trim($("#ConfirmedPassword").val()); var confirmedPassword = $.trim($("#ConfirmedPassword").val());
var realname = $.trim($("#Realname").val()); var realname = $.trim($("#Realname").val());
var comment = $.trim($("#Comment").val()); var comment = $.trim($("#Comment").val());
var isAdmin = $("#isAdmin").val();
$.ajax({ $.ajax({
url : "/api/users", url : "/api/users",
data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}), data: JSON.stringify({username: username, password: password, realname: realname, comment: comment, email: email}),
@ -48,10 +50,14 @@ jQuery(function(){
if(xhr && xhr.status == 200){ if(xhr && xhr.status == 200){
$("#dlgModal") $("#dlgModal")
.dialogModal({ .dialogModal({
"title": i18n.getMessage("title_sign_up"), "title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
"content": i18n.getMessage("registered_successfully"), "content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
"callback": function(){ "callback": function(){
document.location = "/signIn"; if(isAdmin == "true") {
document.location = "/registry/project";
}else{
document.location = "/signIn";
}
} }
}); });
} }

View File

@ -24,7 +24,6 @@ import (
_ "github.com/vmware/harbor/auth/ldap" _ "github.com/vmware/harbor/auth/ldap"
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
_ "github.com/vmware/harbor/routers"
"os" "os"
@ -71,5 +70,6 @@ func main() {
if err := updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD")); err != nil { if err := updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD")); err != nil {
log.Error(err) log.Error(err)
} }
initRouters()
beego.Run() beego.Run()
} }

View File

@ -13,7 +13,7 @@
limitations under the License. limitations under the License.
*/ */
package routers package main
import ( import (
"github.com/vmware/harbor/api" "github.com/vmware/harbor/api"
@ -23,7 +23,7 @@ import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
func init() { func initRouters() {
beego.SetStaticPath("registry/static/i18n", "static/i18n") beego.SetStaticPath("registry/static/i18n", "static/i18n")
beego.SetStaticPath("registry/static/resources", "static/resources") beego.SetStaticPath("registry/static/resources", "static/resources")
@ -41,6 +41,7 @@ func init() {
beego.Router("/", &controllers.IndexController{}) beego.Router("/", &controllers.IndexController{})
beego.Router("/signIn", &controllers.SignInController{}) beego.Router("/signIn", &controllers.SignInController{})
beego.Router("/register", &controllers.RegisterController{}) beego.Router("/register", &controllers.RegisterController{})
beego.Router("/addUser", &controllers.AddUserController{})
beego.Router("/forgotPassword", &controllers.ForgotPasswordController{}) beego.Router("/forgotPassword", &controllers.ForgotPasswordController{})
beego.Router("/resetPassword", &controllers.ResetPasswordController{}) beego.Router("/resetPassword", &controllers.ResetPasswordController{})
beego.Router("/changePassword", &controllers.ChangePasswordController{}) beego.Router("/changePassword", &controllers.ChangePasswordController{})

View File

@ -263,12 +263,12 @@ func line(calldepth int) string {
line = 0 line = 0
} }
for i := len(file) - 1; i > 0; i-- { for i := len(file) - 2; i > 0; i-- {
if file[i] == '/' { if file[i] == os.PathSeparator {
file = file[i+1:] file = file[i+1:]
break break
} }
} }
return fmt.Sprintf("%s:%d", file, line) return fmt.Sprintf("[%s:%d]:", file, line)
} }

View File

@ -16,7 +16,11 @@
<div class="col-sm-4"></div> <div class="col-sm-4"></div>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="page-header"> <div class="page-header">
{{ if eq .IsAdmin true }}
<h1>{{i18n .Lang "add_user" }}</h1>
{{ else }}
<h1>{{i18n .Lang "registration"}}</h1> <h1>{{i18n .Lang "registration"}}</h1>
{{ end }}
</div> </div>
<form class="form"> <form class="form">
<div class="alert alert-danger" role="alert" id="divErrMsg"></div> <div class="alert alert-danger" role="alert" id="divErrMsg"></div>
@ -62,7 +66,13 @@
</div> </div>
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<div class="text-center"> <div class="text-center">
<button type="button" class="btn btn-default" id="btnPageSignUp">{{i18n .Lang "sign_up"}}</button> <button type="button" class="btn btn-default" id="btnPageSignUp">
{{ if eq .IsAdmin true }}
{{i18n .Lang "add_user" }}
{{ else }}
{{i18n .Lang "sign_up"}}
{{ end }}
</button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -13,6 +13,7 @@
limitations under the License. limitations under the License.
--> -->
<input type="hidden" id="currentLanguage" value="{{.Lang}}"> <input type="hidden" id="currentLanguage" value="{{.Lang}}">
<input type="hidden" id="isAdmin" value="{{.IsAdmin}}">
<nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;"> <nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;">
<div class="navbar-header"> <div class="navbar-header">
<button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button"> <button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
@ -55,6 +56,11 @@
<li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;{{i18n .Lang "change_password"}}</a></li> <li><a id="aChangePassword" href="/changePassword" target="_blank"><span class="glyphicon glyphicon-pencil"></span>&nbsp;&nbsp;{{i18n .Lang "change_password"}}</a></li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
{{ end }} {{ end }}
{{ if eq .AuthMode "db_auth" }}
{{ if eq .IsAdmin true }}
<li><a id="aAddUser" href="/addUser" target="_blank"><span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;{{i18n .Lang "add_user"}}</a></li>
{{ end }}
{{ end}}
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{i18n .Lang "log_out"}}</a></li> <li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{i18n .Lang "log_out"}}</a></li>
</ul> </ul>
</li> </li>
@ -63,7 +69,9 @@
{{ else if eq .AuthMode "db_auth" }} {{ else if eq .AuthMode "db_auth" }}
<div class="input-group"> <div class="input-group">
&nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button> &nbsp;<button type="button" class="btn btn-default" id="btnSignIn">{{i18n .Lang "sign_in"}}</button>
{{ if eq .SelfRegistration true }}
&nbsp;<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button> &nbsp;<button type="button" class="btn btn-success" id="btnSignUp">{{i18n .Lang "sign_up"}}</button>
{{ end }}
</div> </div>
{{ else }} {{ else }}
<div class="input-group"> <div class="input-group">