diff --git a/Deploy/docker-compose.yml b/Deploy/docker-compose.yml index d7a3ba547..2a223cd95 100644 --- a/Deploy/docker-compose.yml +++ b/Deploy/docker-compose.yml @@ -1,62 +1,67 @@ -log: - build: ./log/ - volumes: - - /var/log/harbor/:/var/log/docker/ - ports: - - 1514:514 -registry: - image: library/registry:2.3.0 - volumes: - - /data/registry:/storage - - ./config/registry/:/etc/registry/ - ports: - - 5001:5001 - command: - /etc/registry/config.yml - links: - - log - log_driver: "syslog" - log_opt: - syslog-address: "tcp://127.0.0.1:1514" - syslog-tag: "registry" -mysql: - build: ./db/ - volumes: - - /data/database:/var/lib/mysql - env_file: - - ./config/db/env - links: - - log - log_driver: "syslog" - log_opt: - syslog-address: "tcp://127.0.0.1:1514" - syslog-tag: "mysql" -ui: - build: ../ - env_file: - - ./config/ui/env - volumes: - - ./config/ui/app.conf:/etc/ui/app.conf - - ./config/ui/private_key.pem:/etc/ui/private_key.pem - links: - - registry - - mysql - - log - log_driver: "syslog" - log_opt: - syslog-address: "tcp://127.0.0.1:1514" - syslog-tag: "ui" -proxy: - image: library/nginx:1.9 - volumes: - - ./config/nginx:/etc/nginx - links: - - ui - - registry - - log - ports: - - 80:80 - log_driver: "syslog" - log_opt: - syslog-address: "tcp://127.0.0.1:1514" - syslog-tag: "proxy" +version: '2' +services: + log: + build: ./log/ + volumes: + - /var/log/harbor/:/var/log/docker/ + ports: + - 1514:514 + registry: + image: library/registry:2.3.0 + volumes: + - /data/registry:/storage + - ./config/registry/:/etc/registry/ + ports: + - 5001:5001 + command: + /etc/registry/config.yml + depends_on: + - log + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + syslog-tag: "registry" + mysql: + build: ./db/ + volumes: + - /data/database:/var/lib/mysql + env_file: + - ./config/db/env + depends_on: + - log + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + syslog-tag: "mysql" + ui: + build: ../ + env_file: + - ./config/ui/env + volumes: + - ./config/ui/app.conf:/etc/ui/app.conf + - ./config/ui/private_key.pem:/etc/ui/private_key.pem + depends_on: + - log + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + syslog-tag: "ui" + proxy: + image: library/nginx:1.9 + volumes: + - ./config/nginx:/etc/nginx + ports: + - 80:80 + depends_on: + - mysql + - registry + - ui + - log + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + syslog-tag: "proxy" diff --git a/Deploy/harbor.cfg b/Deploy/harbor.cfg index 3bf02a7fd..b89a5d03c 100644 --- a/Deploy/harbor.cfg +++ b/Deploy/harbor.cfg @@ -1,24 +1,35 @@ -## CONFIGURATIONS -#The endpoint for user to access UI and registry service -hostname = mydomain.com -#The protocol for accessing the UI and token/notification service, by default it is http -#User can set it to https if ssl is setup on nginx +## Configuration file of Harbor + +#The IP address or hostname to access admin UI and registry service. +#DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. +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 -#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_port = 25 email_username = sample_admin@mydomain.com email_password = abc email_from = admin -##The password of harbor admin + +##The password of Harbor admin, change this before any production use. 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 -#The url for ldap endpoint + +#The url for an ldap endpoint. 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 -#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 +#Switch for self-registration feature +self_registration = on ##### diff --git a/k8s/dockerfiles/config/db/env b/Deploy/kubernates/dockerfiles/config/db/env similarity index 100% rename from k8s/dockerfiles/config/db/env rename to Deploy/kubernates/dockerfiles/config/db/env diff --git a/k8s/dockerfiles/config/nginx/cert/.gitignore b/Deploy/kubernates/dockerfiles/config/nginx/cert/.gitignore similarity index 100% rename from k8s/dockerfiles/config/nginx/cert/.gitignore rename to Deploy/kubernates/dockerfiles/config/nginx/cert/.gitignore diff --git a/k8s/dockerfiles/config/nginx/nginx.conf b/Deploy/kubernates/dockerfiles/config/nginx/nginx.conf similarity index 100% rename from k8s/dockerfiles/config/nginx/nginx.conf rename to Deploy/kubernates/dockerfiles/config/nginx/nginx.conf diff --git a/k8s/dockerfiles/config/nginx/nginx.https.conf b/Deploy/kubernates/dockerfiles/config/nginx/nginx.https.conf similarity index 100% rename from k8s/dockerfiles/config/nginx/nginx.https.conf rename to Deploy/kubernates/dockerfiles/config/nginx/nginx.https.conf diff --git a/k8s/dockerfiles/config/registry/config.yml b/Deploy/kubernates/dockerfiles/config/registry/config.yml similarity index 85% rename from k8s/dockerfiles/config/registry/config.yml rename to Deploy/kubernates/dockerfiles/config/registry/config.yml index 15a77ac96..fa17e953b 100644 --- a/k8s/dockerfiles/config/registry/config.yml +++ b/Deploy/kubernates/dockerfiles/config/registry/config.yml @@ -19,7 +19,7 @@ http: auth: token: issuer: registry-token-issuer - realm: http://localhost/service/token + realm: http://registry/service/token rootcertbundle: /etc/registry/root.crt service: token-service @@ -27,7 +27,7 @@ notifications: endpoints: - name: harbor disabled: false - url: http://localhost/service/notifications + url: http://registry/service/notifications timeout: 500 threshold: 5 backoff: 1000 diff --git a/k8s/dockerfiles/config/registry/root.crt b/Deploy/kubernates/dockerfiles/config/registry/root.crt similarity index 100% rename from k8s/dockerfiles/config/registry/root.crt rename to Deploy/kubernates/dockerfiles/config/registry/root.crt diff --git a/k8s/dockerfiles/config/ui/app.conf b/Deploy/kubernates/dockerfiles/config/ui/app.conf similarity index 100% rename from k8s/dockerfiles/config/ui/app.conf rename to Deploy/kubernates/dockerfiles/config/ui/app.conf diff --git a/k8s/dockerfiles/config/ui/env b/Deploy/kubernates/dockerfiles/config/ui/env similarity index 100% rename from k8s/dockerfiles/config/ui/env rename to Deploy/kubernates/dockerfiles/config/ui/env diff --git a/k8s/dockerfiles/config/ui/private_key.pem b/Deploy/kubernates/dockerfiles/config/ui/private_key.pem similarity index 100% rename from k8s/dockerfiles/config/ui/private_key.pem rename to Deploy/kubernates/dockerfiles/config/ui/private_key.pem diff --git a/k8s/dockerfiles/proxy-dockerfile b/Deploy/kubernates/dockerfiles/proxy-dockerfile similarity index 100% rename from k8s/dockerfiles/proxy-dockerfile rename to Deploy/kubernates/dockerfiles/proxy-dockerfile diff --git a/k8s/dockerfiles/registry-dockerfile b/Deploy/kubernates/dockerfiles/registry-dockerfile similarity index 100% rename from k8s/dockerfiles/registry-dockerfile rename to Deploy/kubernates/dockerfiles/registry-dockerfile diff --git a/k8s/dockerfiles/ui-dockerfile b/Deploy/kubernates/dockerfiles/ui-dockerfile similarity index 100% rename from k8s/dockerfiles/ui-dockerfile rename to Deploy/kubernates/dockerfiles/ui-dockerfile diff --git a/k8s/mysql-rc.yaml b/Deploy/kubernates/mysql-rc.yaml similarity index 100% rename from k8s/mysql-rc.yaml rename to Deploy/kubernates/mysql-rc.yaml diff --git a/k8s/mysql-svc.yaml b/Deploy/kubernates/mysql-svc.yaml similarity index 100% rename from k8s/mysql-svc.yaml rename to Deploy/kubernates/mysql-svc.yaml diff --git a/k8s/proxy-rc.yaml b/Deploy/kubernates/proxy-rc.yaml similarity index 100% rename from k8s/proxy-rc.yaml rename to Deploy/kubernates/proxy-rc.yaml diff --git a/k8s/proxy-svc.yaml b/Deploy/kubernates/proxy-svc.yaml similarity index 83% rename from k8s/proxy-svc.yaml rename to Deploy/kubernates/proxy-svc.yaml index 29711bd5f..5db7b6d59 100644 --- a/k8s/proxy-svc.yaml +++ b/Deploy/kubernates/proxy-svc.yaml @@ -7,9 +7,9 @@ metadata: spec: type: LoadBalancer ports: - - name: bbb + - name: http port: 80 - - name: aaa + - name: https port: 443 selector: name: proxy diff --git a/k8s/registry-rc.yaml b/Deploy/kubernates/registry-rc.yaml similarity index 95% rename from k8s/registry-rc.yaml rename to Deploy/kubernates/registry-rc.yaml index e330cdc44..55e32faaa 100644 --- a/k8s/registry-rc.yaml +++ b/Deploy/kubernates/registry-rc.yaml @@ -20,6 +20,7 @@ spec: ports: - containerPort: 5000 - containerPort: 5001 + - containerPort: 53 volumeMounts: - name: storage mountPath: /storage diff --git a/k8s/registry-svc.yaml b/Deploy/kubernates/registry-svc.yaml similarity index 88% rename from k8s/registry-svc.yaml rename to Deploy/kubernates/registry-svc.yaml index 29c9ae017..d2b06efd8 100644 --- a/k8s/registry-svc.yaml +++ b/Deploy/kubernates/registry-svc.yaml @@ -11,5 +11,7 @@ spec: port: 5000 - name: external port: 5001 + - name: aa + port: 53 selector: name: registry diff --git a/k8s/ui-rc.yaml b/Deploy/kubernates/ui-rc.yaml similarity index 100% rename from k8s/ui-rc.yaml rename to Deploy/kubernates/ui-rc.yaml diff --git a/k8s/ui-svc.yaml b/Deploy/kubernates/ui-svc.yaml similarity index 100% rename from k8s/ui-svc.yaml rename to Deploy/kubernates/ui-svc.yaml diff --git a/Deploy/prepare b/Deploy/prepare index 76f3d0356..7a6b88966 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -25,6 +25,7 @@ auth_mode = cp.get("configuration", "auth_mode") ldap_url = cp.get("configuration", "ldap_url") ldap_basedn = cp.get("configuration", "ldap_basedn") db_password = cp.get("configuration", "db_password") +self_registration = cp.get("configuration", "self_registration") ######## base_dir = os.path.dirname(__file__) @@ -60,11 +61,13 @@ for f in conf_files: render(os.path.join(templates_dir, "ui", "env"), ui_conf_env, hostname=hostname, + db_password=db_password, ui_url=ui_url, auth_mode=auth_mode, admin_pwd=harbor_admin_password, 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"), ui_conf, diff --git a/Deploy/templates/registry/config.yml b/Deploy/templates/registry/config.yml index 83134a9f6..f460bf13e 100644 --- a/Deploy/templates/registry/config.yml +++ b/Deploy/templates/registry/config.yml @@ -27,7 +27,7 @@ notifications: endpoints: - name: harbor disabled: false - url: $ui_url/service/notifications + url: http://ui/service/notifications timeout: 500 threshold: 5 backoff: 1000 diff --git a/Deploy/templates/ui/env b/Deploy/templates/ui/env index 8fe9710c1..e20e2bc6d 100644 --- a/Deploy/templates/ui/env +++ b/Deploy/templates/ui/env @@ -1,5 +1,7 @@ MYSQL_HOST=mysql +MYSQL_PORT=3306 MYSQL_USR=root +MYSQL_PWD=$db_password REGISTRY_URL=http://registry:5000 CONFIG_PATH=/etc/ui/app.conf HARBOR_REG_URL=$hostname @@ -8,4 +10,5 @@ HARBOR_URL=$ui_url AUTH_MODE=$auth_mode LDAP_URL=$ldap_url LDAP_BASE_DN=$ldap_basedn +SELF_REGISTRATION=$self_registration LOG_LEVEL=debug diff --git a/controllers/base.go b/controllers/base.go index 23ad62c89..e76abe4be 100644 --- a/controllers/base.go +++ b/controllers/base.go @@ -16,12 +16,14 @@ package controllers import ( + "net/http" "os" "strings" - "github.com/vmware/harbor/utils/log" "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 ... @@ -38,6 +40,9 @@ func (c *CommonController) Render() error { type BaseController struct { beego.Controller i18n.Locale + SelfRegistration bool + IsAdmin bool + AuthMode string } type langType struct { @@ -93,15 +98,35 @@ func (b *BaseController) Prepare() { b.Data["CurLang"] = curLang.Name b.Data["RestLangs"] = restLangs - sessionUserID := b.GetSession("userId") - if sessionUserID != nil { - b.Data["Username"] = b.GetSession("username") - } - authMode := os.Getenv("AUTH_MODE") + authMode := strings.ToLower(os.Getenv("AUTH_MODE")) if authMode == "" { 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. diff --git a/controllers/register.go b/controllers/register.go index 466f4e4eb..dd1a80eab 100644 --- a/controllers/register.go +++ b/controllers/register.go @@ -17,11 +17,11 @@ package controllers import ( "net/http" - "os" "strings" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" + "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 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") } 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. -func (rc *CommonController) SignUp() { - username := strings.TrimSpace(rc.GetString("username")) - email := strings.TrimSpace(rc.GetString("email")) - realname := strings.TrimSpace(rc.GetString("realname")) - password := strings.TrimSpace(rc.GetString("password")) - comment := strings.TrimSpace(rc.GetString("comment")) +func (cc *CommonController) SignUp() { + + if !(cc.AuthMode == "db_auth") { + cc.CustomAbort(http.StatusForbidden, "") + } + + 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} _, err := dao.Register(user) if err != nil { 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. -func (rc *CommonController) UserExists() { - target := rc.GetString("target") - value := rc.GetString("value") +func (cc *CommonController) UserExists() { + target := cc.GetString("target") + value := cc.GetString("value") user := models.User{} switch target { @@ -73,8 +108,8 @@ func (rc *CommonController) UserExists() { exist, err := dao.UserExists(user, target) if err != nil { log.Errorf("Error occurred in UserExists: %v", err) - rc.CustomAbort(http.StatusInternalServerError, "Internal error.") + cc.CustomAbort(http.StatusInternalServerError, "Internal error.") } - rc.Data["json"] = exist - rc.ServeJSON() + cc.Data["json"] = exist + cc.ServeJSON() } diff --git a/dao/base.go b/dao/base.go index 3afb633cf..49419745e 100644 --- a/dao/base.go +++ b/dao/base.go @@ -66,18 +66,11 @@ func GenerateRandomString() (string, error) { func InitDB() { orm.RegisterDriver("mysql", orm.DRMySQL) addr := os.Getenv("MYSQL_HOST") - if len(addr) == 0 { - addr = os.Getenv("MYSQL_PORT_3306_TCP_ADDR") - } - - port := os.Getenv("MYSQL_PORT_3306_TCP_PORT") + port := os.Getenv("MYSQL_PORT") username := os.Getenv("MYSQL_USR") + password := os.Getenv("MYSQL_PWD") - password := os.Getenv("MYSQL_ENV_MYSQL_ROOT_PASSWORD") - if len(password) == 0 { - password = os.Getenv("MYSQL_PWD") - } - + log.Debugf("db url: %s:%s, db user: %s", addr, port, username) dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry" ch := make(chan int, 1) go func() { diff --git a/dao/dao_test.go b/dao/dao_test.go index 7e9e1e0d2..abb671b3f 100644 --- a/dao/dao_test.go +++ b/dao/dao_test.go @@ -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) - os.Setenv("MYSQL_PORT_3306_TCP_ADDR", dbHost) - os.Setenv("MYSQL_PORT_3306_TCP_PORT", dbPort) + os.Setenv("MYSQL_HOST", dbHost) + os.Setenv("MYSQL_PORT", dbPort) os.Setenv("MYSQL_USR", dbUser) os.Setenv("MYSQL_PWD", dbPassword) os.Setenv("AUTH_MODE", "db_auth") diff --git a/docs/installation_guide.md b/docs/installation_guide.md index c4b217493..1a7ddf3fa 100644 --- a/docs/installation_guide.md +++ b/docs/installation_guide.md @@ -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. 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. -* 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/ ### Configuration of Harbor @@ -139,4 +139,4 @@ Removing harbor_mysql_1 ... done ### 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. -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. \ No newline at end of file +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. diff --git a/routers/router.go b/routers/router.go index 764a3c7e6..6cce814bc 100644 --- a/routers/router.go +++ b/routers/router.go @@ -41,6 +41,7 @@ func init() { beego.Router("/", &controllers.IndexController{}) beego.Router("/signIn", &controllers.SignInController{}) beego.Router("/register", &controllers.RegisterController{}) + beego.Router("/addUser", &controllers.AddUserController{}) beego.Router("/forgotPassword", &controllers.ForgotPasswordController{}) beego.Router("/resetPassword", &controllers.ResetPasswordController{}) beego.Router("/changePassword", &controllers.ChangePasswordController{}) diff --git a/service/token.go b/service/token.go index b274be3f8..cd2386833 100644 --- a/service/token.go +++ b/service/token.go @@ -42,13 +42,14 @@ func (a *TokenHandler) Get() { username, password, _ := request.BasicAuth() authenticated := authenticate(username, password) 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") a.CustomAbort(http.StatusUnauthorized, "") } - access := svc_utils.GetResourceActions(scope) + access := svc_utils.GetResourceActions(scopes) for _, a := range access { svc_utils.FilterAccess(username, authenticated, a) } diff --git a/service/utils/authutils.go b/service/utils/authutils.go index 4c00dd579..c8bdd6d59 100644 --- a/service/utils/authutils.go +++ b/service/utils/authutils.go @@ -38,17 +38,19 @@ const ( ) // GetResourceActions ... -func GetResourceActions(scope string) []*token.ResourceActions { +func GetResourceActions(scopes []string) []*token.ResourceActions { var res []*token.ResourceActions - if scope == "" { - return res + for _, s := range scopes { + 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 } @@ -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 projectName := a.Name[0:strings.LastIndex(a.Name, "/")] var permission string - var err error 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) if err != nil { 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. -func GenTokenForUI(username, service, scope string) (string, error) { - access := GetResourceActions(scope) +func GenTokenForUI(username string, service string, scopes []string) (string, error) { + access := GetResourceActions(scopes) for _, a := range access { FilterAccess(username, true, a) } diff --git a/service/utils/registryutils.go b/service/utils/registryutils.go index 66c39cd1f..37ada5426 100644 --- a/service/utils/registryutils.go +++ b/service/utils/registryutils.go @@ -63,14 +63,15 @@ func RegistryAPIGet(url, username string) ([]byte, error) { authenticate := response.Header.Get("WWW-Authenticate") log.Debugf("authenticate header: %s", authenticate) 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=\"(.*?)\"`) res := re.FindStringSubmatch(authenticate) if len(res) > 2 { 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 { return nil, err } diff --git a/static/i18n/locale_en-US.ini b/static/i18n/locale_en-US.ini index 2473d2181..7a44eddda 100644 --- a/static/i18n/locale_en-US.ini +++ b/static/i18n/locale_en-US.ini @@ -3,6 +3,7 @@ page_title_sign_in = Sign In - Harbor page_title_project = Project - Harbor page_title_item_details = Details - Harbor page_title_registration = Sign Up - Harbor +page_title_add_user = Add User - Harbor page_title_forgot_password = Forgot Password - Harbor title_forgot_password = Forgot Password page_title_reset_password = Reset Password - Harbor @@ -12,6 +13,7 @@ title_change_password = Change Password page_title_search = Search - Harbor sign_in = Sign In sign_up = Sign Up +add_user = Add User log_out = Log Out search_placeholder = projects or repositories change_password = Change Password diff --git a/static/i18n/locale_messages.js b/static/i18n/locale_messages.js index 4b30bf847..93fdcaabb 100644 --- a/static/i18n/locale_messages.js +++ b/static/i18n/locale_messages.js @@ -177,6 +177,10 @@ var global_messages = { "en-US": "Sign Up", "zh-CN": "注册" }, + "title_add_user": { + "en-US": "Add User", + "zh-CN": "新增用户" + }, "registered_successfully": { "en-US": "Signed up successfully.", "zh-CN": "注册成功。" @@ -185,6 +189,14 @@ var global_messages = { "en-US": "Failed to sign up.", "zh-CN": "注册失败。" }, + "added_user_successfully": { + "en-US": "Added user successfully.", + "zh-CN": "新增用户成功。" + }, + "added_user_failed": { + "en-US": "Added user failed.", + "zh-CN": "新增用户失败。" + }, "projects" : { "en-US": "Projects", "zh-CN": "项目" diff --git a/static/i18n/locale_zh-CN.ini b/static/i18n/locale_zh-CN.ini index f28369d9c..9e49c12ae 100644 --- a/static/i18n/locale_zh-CN.ini +++ b/static/i18n/locale_zh-CN.ini @@ -3,6 +3,7 @@ page_title_sign_in = 登录 - Harbor page_title_project = 项目 - Harbor page_title_item_details = 详细信息 - Harbor page_title_registration = 注册 - Harbor +page_title_add_user = 新增用户 - Harbor page_title_forgot_password = 忘记密码 - Harbor title_forgot_password = 忘记密码 page_title_reset_password = 重置密码 - Harbor @@ -12,6 +13,7 @@ title_change_password = 修改密码 page_title_search = 搜索 - Harbor sign_in = 登录 sign_up = 注册 +add_user = 新增用户 log_out = 注销 search_placeholder = 项目或镜像名称 change_password = 修改密码 diff --git a/static/resources/js/register.js b/static/resources/js/register.js index cbd133671..e8fe31d35 100644 --- a/static/resources/js/register.js +++ b/static/resources/js/register.js @@ -30,12 +30,14 @@ jQuery(function(){ $("#btnPageSignUp").on("click", function(){ validateOptions.Validate(function() { - var username = $.trim($("#Username").val()); - var email = $.trim($("#Email").val()); - var password = $.trim($("#Password").val()); - var confirmedPassword = $.trim($("#ConfirmedPassword").val()); - var realname = $.trim($("#Realname").val()); - var comment = $.trim($("#Comment").val()); + var username = $.trim($("#Username").val()); + var email = $.trim($("#Email").val()); + var password = $.trim($("#Password").val()); + var confirmedPassword = $.trim($("#ConfirmedPassword").val()); + var realname = $.trim($("#Realname").val()); + var comment = $.trim($("#Comment").val()); + var isAdmin = $("#isAdmin").val(); + $.ajax({ url : '/signUp', data:{username: username, password: password, realname: realname, comment: comment, email: email}, @@ -47,10 +49,14 @@ jQuery(function(){ if(xhr && xhr.status == 200){ $("#dlgModal") .dialogModal({ - "title": i18n.getMessage("title_sign_up"), - "content": i18n.getMessage("registered_successfully"), - "callback": function(){ - document.location = "/signIn"; + "title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"), + "content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"), + "callback": function(){ + if(isAdmin == "true") { + document.location = "/registry/project"; + }else{ + document.location = "/signIn"; + } } }); } diff --git a/utils/log/logger.go b/utils/log/logger.go index 816594245..c713aa1b0 100644 --- a/utils/log/logger.go +++ b/utils/log/logger.go @@ -27,6 +27,8 @@ import ( var logger = New(os.Stdout, NewTextFormatter(), WarningLevel) func init() { + logger.callDepth = 3 + // TODO add item in configuaration file lvl := os.Getenv("LOG_LEVEL") if len(lvl) == 0 { @@ -46,18 +48,20 @@ func init() { // Logger provides a struct with fields that describe the details of logger. type Logger struct { - out io.Writer - fmtter Formatter - lvl Level - mu sync.Mutex + out io.Writer + fmtter Formatter + lvl Level + callDepth int + mu sync.Mutex } // New returns a customized Logger func New(out io.Writer, fmtter Formatter, lvl Level) *Logger { return &Logger{ - out: out, - fmtter: fmtter, - lvl: lvl, + out: out, + fmtter: fmtter, + lvl: lvl, + callDepth: 2, } } @@ -117,7 +121,7 @@ func (l *Logger) output(record *Record) (err error) { // Debug ... func (l *Logger) Debug(v ...interface{}) { if l.lvl <= DebugLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprint(v...), line, DebugLevel) l.output(record) } @@ -126,7 +130,7 @@ func (l *Logger) Debug(v ...interface{}) { // Debugf ... func (l *Logger) Debugf(format string, v ...interface{}) { if l.lvl <= DebugLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, DebugLevel) l.output(record) } @@ -167,7 +171,7 @@ func (l *Logger) Warningf(format string, v ...interface{}) { // Error ... func (l *Logger) Error(v ...interface{}) { if l.lvl <= ErrorLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprint(v...), line, ErrorLevel) l.output(record) } @@ -176,7 +180,7 @@ func (l *Logger) Error(v ...interface{}) { // Errorf ... func (l *Logger) Errorf(format string, v ...interface{}) { if l.lvl <= ErrorLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, ErrorLevel) l.output(record) } @@ -185,7 +189,7 @@ func (l *Logger) Errorf(format string, v ...interface{}) { // Fatal ... func (l *Logger) Fatal(v ...interface{}) { if l.lvl <= FatalLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprint(v...), line, FatalLevel) l.output(record) } @@ -195,7 +199,7 @@ func (l *Logger) Fatal(v ...interface{}) { // Fatalf ... func (l *Logger) Fatalf(format string, v ...interface{}) { if l.lvl <= FatalLevel { - line := line(2) + line := line(l.callDepth) record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, FatalLevel) l.output(record) } @@ -259,5 +263,12 @@ func line(calldepth int) string { line = 0 } - return fmt.Sprintf("%s:%d", file, line) + for i := len(file) - 2; i > 0; i-- { + if file[i] == os.PathSeparator { + file = file[i+1:] + break + } + } + + return fmt.Sprintf("[%s:%d]:", file, line) } diff --git a/views/register.tpl b/views/register.tpl index 88bd9615f..eca09415d 100644 --- a/views/register.tpl +++ b/views/register.tpl @@ -16,7 +16,11 @@
@@ -62,7 +66,13 @@
- +
diff --git a/views/segment/header-content.tpl b/views/segment/header-content.tpl index 1c6e147ea..6c88e0d25 100644 --- a/views/segment/header-content.tpl +++ b/views/segment/header-content.tpl @@ -13,6 +13,7 @@ limitations under the License. --> +