update self-registration features and merge from master.

This commit is contained in:
kunw 2016-03-31 18:45:26 +08:00
commit 21430b0ebb
14 changed files with 107 additions and 109 deletions

View File

@ -1,10 +1,15 @@
# This file lists all individuals having contributed content to the repository.
Amanda Zhang <amzhang@vmware.com>
Ben Niu Ji <benniuji@gmail.com>
Bobby Zhang <junzhang@vmware.com>
Daniel Jiang <jiangd@vmware.com>
Haining Henry Zhang <henryzhang@vmware.com>
Hao Xia <haox@vmware.com>
Jack Liu <ljack@vmware.com>
Kun Wang <kunw@vmware.com>
Shan Zhu <zhus@vmware.com>
Victoria Zheng <vzheng@vmware.com>
Wenkai Yin <yinw@vmware.com>
Yan Wang <wangyan@vmware.com>

View File

@ -22,5 +22,5 @@ ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com
#The password for root user of db
db_password = root123
#Switch for self-registration feature
self_registration = off
self_registration = on
#####

View File

@ -63,7 +63,7 @@ We welcome contributions from the community. If you wish to contribute code, we
Harbor is available under the [Apache 2 license](LICENSE).
### Partners
<a href="https://www.shurenyun.com/" border="0" target="_blank"><img alt="DataMan" src="docs/img/dataman.png"></a>
<a href="https://www.shurenyun.com/" border="0" target="_blank"><img alt="DataMan" src="docs/img/dataman.png"></a> &nbsp; &nbsp; <a href="http://www.slamtec.com" target="_blank" border="0"><img alt="SlamTec" src="docs/img/slamteclogo.png"></a>
### Users
<a href="https://www.madailicai.com/" border="0" target="_blank"><img alt="MaDaiLiCai" src="docs/img/UserMaDai.jpg"></a> <a href="http://www.slamtec.com" target="_blank" border="0"><img alt="SlamTec" src="docs/img/slamteclogo.png"></a>
<a href="https://www.madailicai.com/" border="0" target="_blank"><img alt="MaDaiLiCai" src="docs/img/UserMaDai.jpg"></a>

View File

@ -16,12 +16,14 @@
package controllers
import (
"log"
"net/http"
"os"
"strings"
"github.com/astaxie/beego"
"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 ...
@ -47,11 +49,11 @@ type langType struct {
const (
defaultLang = "en-US"
adminUserID = 1
)
var supportLanguages map[string]langType
var selfRegistration bool
var enableAddUserByAdmin bool
var isAdminLoginedUser bool
// Prepare extracts the language information from request and populate data for rendering templates.
func (b *BaseController) Prepare() {
@ -107,17 +109,23 @@ func (b *BaseController) Prepare() {
}
b.Data["AuthMode"] = authMode
strSelfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION"))
if strSelfRegistration == "on" {
selfRegistration = true
if selfRegistration == "off" {
enableAddUserByAdmin = true
}
if sessionUserID != nil && sessionUserID.(int) != adminUserID {
selfRegistration = false
if sessionUserID != nil {
var err error
isAdminLoginedUser, err = dao.IsAdminRole(sessionUserID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole:%v", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
b.Data["EnableAddUserByAdmin"] = selfRegistration
b.Data["IsAdminLoginedUser"] = isAdminLoginedUser
b.Data["EnableAddUserByAdmin"] = enableAddUserByAdmin
}
@ -141,10 +149,10 @@ func init() {
//conf/app.conf -> os.Getenv("config_path")
configPath := os.Getenv("CONFIG_PATH")
if len(configPath) != 0 {
log.Printf("Config path: %s", configPath)
log.Infof("Config path: %s", configPath)
beego.AppConfigPath = configPath
if err := beego.ParseConfig(); err != nil {
beego.Warning("Failed to parse config file: ", configPath, "error: ", err)
log.Warningf("Failed to parse config file: %s, error: %v", configPath, err)
}
}
@ -167,7 +175,7 @@ func init() {
for _, lang := range langs {
if err := i18n.SetMessage(lang, "static/i18n/"+"locale_"+lang+".ini"); err != nil {
beego.Error("Fail to set message file:" + err.Error())
log.Errorf("Fail to set message file: %s", err.Error())
}
}
}

View File

@ -21,8 +21,7 @@ import (
"os"
"github.com/vmware/harbor/dao"
"github.com/astaxie/beego"
"github.com/vmware/harbor/utils/log"
)
// ItemDetailController handles requet to /registry/detail, which shows the detail of a project.
@ -37,7 +36,7 @@ func (idc *ItemDetailController) Get() {
projectID, _ := idc.GetInt64("project_id")
if projectID <= 0 {
beego.Error("Invalid project id:", projectID)
log.Errorf("Invalid project id: %d", projectID)
idc.Redirect("/signIn", http.StatusFound)
return
}
@ -45,7 +44,7 @@ func (idc *ItemDetailController) Get() {
project, err := dao.GetProjectByID(projectID)
if err != nil {
beego.Error("Error occurred in GetProjectById:", err)
log.Errorf("Error occurred in GetProjectById: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
@ -70,13 +69,13 @@ func (idc *ItemDetailController) Get() {
roleList, err := dao.GetUserProjectRoles(userID, projectID)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
log.Errorf("Error occurred in GetUserProjectRoles: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
isAdmin, err := dao.IsAdminRole(userID)
if err != nil {
beego.Error("Error occurred in IsAdminRole:", err)
log.Errorf("Error occurred in IsAdminRole: %v", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}

View File

@ -20,8 +20,7 @@ import (
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/models"
"github.com/astaxie/beego"
"github.com/vmware/harbor/utils/log"
)
// IndexController handles request to /
@ -52,7 +51,7 @@ func (c *CommonController) Login() {
user, err := auth.Login(models.AuthModel{principal, password})
if err != nil {
beego.Error("Error occurred in UserLogin:", err)
log.Errorf("Error occurred in UserLogin: %v", err)
c.CustomAbort(http.StatusUnauthorized, "")
}

View File

@ -25,6 +25,7 @@ import (
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego"
)
@ -51,25 +52,25 @@ func (cc *CommonController) UpdatePassword() {
sessionUserID := cc.GetSession("userId")
if sessionUserID == nil {
beego.Warning("User does not login.")
log.Warning("User does not login.")
cc.CustomAbort(http.StatusUnauthorized, "please_login_first")
}
oldPassword := cc.GetString("old_password")
if oldPassword == "" {
beego.Error("Old password is blank")
log.Error("Old password is blank")
cc.CustomAbort(http.StatusBadRequest, "Old password is blank")
}
queryUser := models.User{UserID: sessionUserID.(int), Password: oldPassword}
user, err := dao.CheckUserPassword(queryUser)
if err != nil {
beego.Error("Error occurred in CheckUserPassword:", err)
log.Errorf("Error occurred in CheckUserPassword: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
beego.Warning("Password input is not correct")
log.Warning("Password input is not correct")
cc.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
}
@ -78,7 +79,7 @@ func (cc *CommonController) UpdatePassword() {
updateUser := models.User{UserID: sessionUserID.(int), Password: password, Salt: user.Salt}
err = dao.ChangeUserPassword(updateUser, oldPassword)
if err != nil {
beego.Error("Error occurred in ChangeUserPassword:", err)
log.Errorf("Error occurred in ChangeUserPassword: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
} else {
@ -116,7 +117,7 @@ func (cc *CommonController) SendEmail() {
queryUser := models.User{Email: email}
exist, err := dao.UserExists(queryUser, "email")
if err != nil {
beego.Error("Error occurred in UserExists:", err)
log.Errorf("Error occurred in UserExists: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
@ -125,7 +126,7 @@ func (cc *CommonController) SendEmail() {
messageTemplate, err := template.ParseFiles("views/reset-password-mail.tpl")
if err != nil {
beego.Error("Parse email template file failed:", err)
log.Errorf("Parse email template file failed: %v", err)
cc.CustomAbort(http.StatusInternalServerError, err.Error())
}
@ -137,7 +138,7 @@ func (cc *CommonController) SendEmail() {
}
uuid, err := dao.GenerateRandomString()
if err != nil {
beego.Error("Error occurred in GenerateRandomString:", err)
log.Errorf("Error occurred in GenerateRandomString: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
err = messageTemplate.Execute(message, messageDetail{
@ -147,13 +148,13 @@ func (cc *CommonController) SendEmail() {
})
if err != nil {
beego.Error("message template error:", err)
log.Errorf("Message template error: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
config, err := beego.AppConfig.GetSection("mail")
if err != nil {
beego.Error("Can not load app.conf:", err)
log.Errorf("Can not load app.conf: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
@ -166,7 +167,7 @@ func (cc *CommonController) SendEmail() {
err = mail.SendMail()
if err != nil {
beego.Error("send email failed:", err)
log.Errorf("Send email failed: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
}
@ -187,7 +188,7 @@ func (rpc *ResetPasswordController) Get() {
resetUUID := rpc.GetString("reset_uuid")
if resetUUID == "" {
beego.Error("Reset uuid is blank.")
log.Error("Reset uuid is blank.")
rpc.Redirect("/", http.StatusFound)
return
}
@ -195,7 +196,7 @@ func (rpc *ResetPasswordController) Get() {
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
log.Errorf("Error occurred in GetUser: %v", err)
rpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
@ -218,11 +219,11 @@ func (cc *CommonController) ResetPassword() {
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
log.Errorf("Error occurred in GetUser: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
beego.Error("User does not exist")
log.Error("User does not exist")
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
}
@ -232,7 +233,7 @@ func (cc *CommonController) ResetPassword() {
user.Password = password
err = dao.ResetUserPassword(*user)
if err != nil {
beego.Error("Error occurred in ResetUserPassword:", err)
log.Errorf("Error occurred in ResetUserPassword: %v", err)
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
} else {

View File

@ -24,8 +24,6 @@ import (
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego"
)
// RegisterController handles request to /register
@ -38,12 +36,13 @@ func (rc *RegisterController) Get() {
pageTitleKey := "page_title_registration"
if selfRegistration {
sessionUserID := rc.GetSession("userId")
if sessionUserID == nil || sessionUserID.(int) != adminUserID {
if enableAddUserByAdmin {
if !isAdminLoginedUser {
log.Error("Self registration can only be used by admin user.\n")
rc.Redirect("/signIn", http.StatusFound)
}
pageTitleKey = "page_title_add_user"
}
@ -58,12 +57,9 @@ func (rc *RegisterController) Get() {
// SignUp insert data into DB based on data in form.
func (rc *CommonController) SignUp() {
if selfRegistration {
sessionUserID := rc.GetSession("userId")
if sessionUserID == nil || sessionUserID.(int) != adminUserID {
log.Error("Self registration can only be used by admin user.\n")
rc.Redirect("/signIn", http.StatusForbidden)
}
if enableAddUserByAdmin && !isAdminLoginedUser {
log.Error("Self registration can only be used by admin user.\n")
rc.CustomAbort(http.StatusForbidden, "")
}
username := strings.TrimSpace(rc.GetString("username"))
@ -76,7 +72,7 @@ func (rc *CommonController) SignUp() {
_, err := dao.Register(user)
if err != nil {
beego.Error("Error occurred in Register:", err)
log.Errorf("Error occurred in Register: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
@ -96,7 +92,7 @@ func (rc *CommonController) UserExists() {
exist, err := dao.UserExists(user, target)
if err != nil {
beego.Error("Error occurred in UserExists:", err)
log.Errorf("Error occurred in UserExists: %v", err)
rc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
rc.Data["json"] = exist

View File

@ -4,7 +4,7 @@ Because Harbor does not ship with any certificates, it uses HTTP by default to s
##Get a certificate
Assuming that your registrys **hostname** is **reg.yourdomain.com**, and that its DNS record points to the host where you are running Harbor, you first should get a certificate from a CA. The certificate usually contains a .crt file and a .key file, for example, **yourdomain.com.crt** and **yourdomain.com.key**.
Assuming that your registry's **hostname** is **reg.yourdomain.com**, and that its DNS record points to the host where you are running Harbor. You first should get a certificate from a CA. The certificate usually contains a .crt file and a .key file, for example, **yourdomain.com.crt** and **yourdomain.com.key**.
In a test or development environment, you may choose to use a self-signed certificate instead of the one from a CA. The below commands generate your own certificate:
@ -20,9 +20,9 @@ In a test or development environment, you may choose to use a self-signed certif
-newkey rsa:4096 -nodes -sha256 -keyout yourdomain.com.key \
-out yourdomain.com.csr
```
3) Generate the certificate of your registry host
3) Generate the certificate of your registry host:
You need to configure openssl first. On Ubuntu, the config file locates at /etc/ssl/openssl.cnf. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Lets creates necessary directories and files:
You need to configure openssl first. On Ubuntu, the config file locates at /etc/ssl/openssl.cnf. Refer to openssl document for more information. The default CA directory of openssl is called demoCA. Let's create necessary directories and files:
```
mkdir demoCA
cd demoCA
@ -32,7 +32,7 @@ You need to configure openssl first. On Ubuntu, the config file locates at /etc/
```
Then run this command to generate the certificate of your registry host:
```
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key outdir .
openssl ca -in yourdomain.com.csr -out yourdomain.com.crt -cert ca.crt -keyfile ca.key -outdir .
```
##Configuration of Nginx
@ -40,7 +40,7 @@ After obtaining the **yourdomain.com.crt** and **yourdomain.com.key** files, cha
```
cd Deploy/config/nginx
```
Create a new directory “cert/” if it does not exist. Then copy **yourdomain.com.crt** and **yourdomain.com.key** to cert/.
Create a new directory cert/, if it does not exist. Then copy **yourdomain.com.crt** and **yourdomain.com.key** to cert/.
Rename the existing configuration file of Nginx:
```
@ -50,28 +50,26 @@ Copy the template **nginx.https.conf** as the new configuration file:
```
cp nginx.https.conf nginx.conf
```
Edit the file nginx.conf and replace two occurrences of **server name** harbordomain.com to your own host name: reg.yourdomain.com .
Edit the file nginx.conf and replace two occurrences of **harbordomain.com** to your own host name, such as reg.yourdomain.com .
```
server {
listen 443 ssl;
server_name harbordomain.com;
server {
listen 80;
server_name harbordomain.com;
rewrite ^/(.*) https://$server_name$1 permanent;
...
server {
listen 80;
server_name harbordomain.com;
rewrite ^/(.*) https://$server_name$1 permanent;
```
Then look for the SSL section to make sure the files of your certificates match the names in the config file. Do not change the path of the files.
```
...
# SSL
ssl_certificate /etc/nginx/cert/yourdomain.com.crt;
ssl_certificate_key /etc/nginx/cert/yourdomain.com.key;
```
Save your changes in nginx.conf.
@ -95,29 +93,30 @@ If Harbor is already running, stop and remove the existing instance. Your image
```
Finally, restart Harbor:
```
docker-compose up d
docker-compose up -d
```
After setting up HTTPS for Harbor, you can verify it by the follow steps:
1. Open a browser and enter the address: https://reg.yourdomain.com . It should display the user interface of Harbor.
2. On a machine with Docker daemon, make sure the option “--insecure-registry” does not present, run any docker command to verify the setup, e.g.
2. On a machine with Docker daemon, make sure the option "-insecure-registry" does not present, run any docker command to verify the setup, e.g.
```
docker login reg.yourdomain.com
```
##Troubleshooting
1.` `You may get an intermediate certificate from a certificate issuer. In this case, you should merge the intermediate certificate with your own certificate to create a certificate bundle. You can achieve this by the below command:
```
cat intermediate-certificate.pem >> yourdomain.com.crt
```
2.` `On some systems where docker daemon runs, you may need to trust the certificate at OS level.
On Ubuntu, this can be done by below commands:
```
cp youdomain.com.crt /usr/local/share/ca-certificates/reg.yourdomain.com.crt
update-ca-certificates
```
On Red Hat (CentOS etc), the commands are:
```
cp yourdomain.com.crt /etc/pki/ca-trust/source/anchors/reg.yourdomain.com.crt
update-ca-trust
1. You may get an intermediate certificate from a certificate issuer. In this case, you should merge the intermediate certificate with your own certificate to create a certificate bundle. You can achieve this by the below command:
```
cat intermediate-certificate.pem >> yourdomain.com.crt
```
2. On some systems where docker daemon runs, you may need to trust the certificate at OS level.
On Ubuntu, this can be done by below commands:
```sh
cp youdomain.com.crt /usr/local/share/ca-certificates/reg.yourdomain.com.crt
update-ca-certificates
```
On Red Hat (CentOS etc), the commands are:
```sh
cp yourdomain.com.crt /etc/pki/ca-trust/source/anchors/reg.yourdomain.com.crt
update-ca-trust
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -21,7 +21,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strings"
"regexp"
"github.com/vmware/harbor/utils/log"
)
@ -62,22 +62,13 @@ func RegistryAPIGet(url, username string) ([]byte, error) {
} else if response.StatusCode == http.StatusUnauthorized {
authenticate := response.Header.Get("WWW-Authenticate")
log.Debugf("authenticate header: %s", authenticate)
str := strings.Split(authenticate, " ")[1]
var service string
var scope string
strs := strings.Split(str, ",")
for _, s := range strs {
if strings.Contains(s, "service") {
service = s
} else if strings.Contains(s, "scope") {
scope = s
}
}
if arr := strings.Split(service, "\""); len(arr) > 1 {
service = arr[1]
}
if arr := strings.Split(scope, "\""); len(arr) > 1 {
scope = arr[1]
re := regexp.MustCompile(`service=\"(.*?)\".*scope=\"(.*?)\"`)
res := re.FindStringSubmatch(authenticate)
if len(res) > 2 {
service = res[1]
scope = res[2]
}
token, err := GenTokenForUI(username, service, scope)
if err != nil {

View File

@ -36,7 +36,7 @@ jQuery(function(){
var confirmedPassword = $.trim($("#ConfirmedPassword").val());
var realname = $.trim($("#Realname").val());
var comment = $.trim($("#Comment").val());
var enableAddUserByAdmin = $("#enableAddUserByAdmin").val();
var isAdminLoginedUser = $("#isAdminLoginedUser").val();
$.ajax({
url : '/signUp',
@ -49,10 +49,10 @@ jQuery(function(){
if(xhr && xhr.status == 200){
$("#dlgModal")
.dialogModal({
"title": enableAddUserByAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
"content": enableAddUserByAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
"title": isAdminLoginedUser == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"),
"content": isAdminLoginedUser == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"),
"callback": function(){
if(enableAddUserByAdmin == "true") {
if(isAdminLoginedUser == "true") {
document.location = "/registry/project";
}else{
document.location = "/signIn";

View File

@ -16,7 +16,7 @@
<div class="col-sm-4"></div>
<div class="col-sm-4">
<div class="page-header">
{{ if eq .EnableAddUserByAdmin true }}
{{ if eq .IsAdminLoginedUser true }}
<h1>{{i18n .Lang "add_user" }}</h1>
{{ else }}
<h1>{{i18n .Lang "registration"}}</h1>
@ -67,7 +67,7 @@
<div class="form-group has-feedback">
<div class="text-center">
<button type="button" class="btn btn-default" id="btnPageSignUp">
{{ if eq .EnableAddUserByAdmin true }}
{{ if eq .IsAdminLoginedUser true }}
{{i18n .Lang "add_user" }}
{{ else }}
{{i18n .Lang "sign_up"}}

View File

@ -13,7 +13,7 @@
limitations under the License.
-->
<input type="hidden" id="currentLanguage" value="{{.Lang}}">
<input type="hidden" id="enableAddUserByAdmin" value="{{.EnableAddUserByAdmin}}">
<input type="hidden" id="isAdminLoginedUser" value="{{.IsAdminLoginedUser}}">
<nav class="navbar navbar-default" role="navigation" style="margin-bottom: 0;">
<div class="navbar-header">
<button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button">
@ -56,7 +56,7 @@
<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>
{{ end }}
{{ if eq .EnableAddUserByAdmin true }}
{{ if eq .IsAdminLoginedUser true }}
<li><a id="aSelfSignUp" href="/register" target="_blank"><span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;{{i18n .Lang "add_user"}}</a></li>
{{ end}}
<li><a id="aLogout" href="#"><span class="glyphicon glyphicon-log-in"></span>&nbsp;&nbsp;{{i18n .Lang "log_out"}}</a></li>