Merge pull request #4 from reasonerjt/master

merge from dev repos
This commit is contained in:
reasonerjt 2016-02-29 13:32:41 +08:00
commit 2319de381d
65 changed files with 2523 additions and 1320 deletions

3
.gitignore vendored
View File

@ -1,8 +1,5 @@
harbor
my_start.sh
Deploy/config/registry/config.yml
Deploy/config/ui/env
Deploy/config/ui/app.conf
Deploy/config/db/env
Deploy/prepare.my
Deploy/harbor.cfg.my

View File

@ -8,6 +8,8 @@ RUN mv /etc/cron.daily/logrotate /etc/cron.hourly/ \
&& sed 's/#$UDPServerRun 514/$UDPServerRun 514/' -i /etc/rsyslog.conf \
&& sed 's/#$ModLoad imtcp/$ModLoad imtcp/' -i /etc/rsyslog.conf \
&& sed 's/#$InputTCPServerRun 514/$InputTCPServerRun 514/' -i /etc/rsyslog.conf \
&& sed 's/$PrivDropToUser syslog/#$PrivDropToUser syslog/' -i /etc/rsyslog.conf \
&& sed 's/$PrivDropToGroup syslog/#$PrivDropToGroup syslog/' -i /etc/rsyslog.conf \
&& rm /etc/rsyslog.d/*
# logrotate configuration file for docker
@ -20,5 +22,5 @@ VOLUME /var/log/docker/
EXPOSE 514
CMD cron && chown -R syslog:syslog /var/log/docker/ && rsyslogd -n
CMD cron && rsyslogd -n

View File

@ -1,24 +0,0 @@
FROM ubuntu:14.04
ENV MYSQL_USR root
ENV MYSQL_PWD root
ENV MYSQL_PORT_3306_TCP_ADDR localhost
ENV MYSQL_PORT_3306_TCP_PORT 3306
ENV REGISTRY_URL localhost:5000
RUN apt-get update -qqy && apt-get install -qqy libldap2-dev
ADD harbor /go/bin/harbor
ADD views /go/bin/views
ADD static /go/bin/static
RUN chmod u+x /go/bin/harbor
RUN sed -i 's/TLS_CACERT/#TLS_CAERT/g' /etc/ldap/ldap.conf
RUN sed -i '$a\TLS_REQCERT allow' /etc/ldap/ldap.conf
WORKDIR /go/bin/
ENTRYPOINT ["/go/bin/harbor"]
EXPOSE 80

202
LICENSE
View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

1381
License.txt Normal file

File diff suppressed because it is too large Load Diff

11
Notice.txt Normal file
View File

@ -0,0 +1,11 @@
Harbor 0.1.0 Beta
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
You may not use this product except in compliance with the License.
This product may include a number of subcomponents with
separate copyright notices and license terms. Your use of the source
code for the these subcomponents is subject to the terms and
conditions of the subcomponent's license, as noted in the LICENSE file.

View File

@ -22,5 +22,5 @@ $ docker-compose up
```
### License
Harbor is available under the [Apache 2 license](LICENSE).
Harbor is available under the [Apache 2 license](License.txt).

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -24,19 +25,23 @@ import (
"github.com/astaxie/beego"
)
// BaseAPI wraps common methods for controllers to host API
type BaseAPI struct {
beego.Controller
}
// Render returns nil as it won't render template
func (b *BaseAPI) Render() error {
return nil
}
// RenderError provides shortcut to render http error
func (b *BaseAPI) RenderError(code int, text string) {
http.Error(b.Ctx.ResponseWriter, text, code)
}
func (b *BaseAPI) DecodeJsonReq(v interface{}) {
// DecodeJSONReq decodes a json request
func (b *BaseAPI) DecodeJSONReq(v interface{}) {
err := json.Unmarshal(b.Ctx.Input.CopyBody(1<<32), v)
if err != nil {
beego.Error("Error while decoding the json request:", err)
@ -44,22 +49,23 @@ func (b *BaseAPI) DecodeJsonReq(v interface{}) {
}
}
// ValidateUser checks if the request triggered by a valid user
func (b *BaseAPI) ValidateUser() int {
sessionUserId := b.GetSession("userId")
if sessionUserId == nil {
sessionUserID := b.GetSession("userId")
if sessionUserID == nil {
beego.Warning("No user id in session, canceling request")
b.CustomAbort(http.StatusUnauthorized, "")
}
userId := sessionUserId.(int)
u, err := dao.GetUser(models.User{UserId: userId})
userID := sessionUserID.(int)
u, err := dao.GetUser(models.User{UserID: userID})
if err != nil {
beego.Error("Error occurred in GetUser:", err)
b.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
beego.Warning("User was deleted already, user id: ", userId, " canceling request.")
beego.Warning("User was deleted already, user id: ", userID, " canceling request.")
b.CustomAbort(http.StatusUnauthorized, "")
}
return userId
return userID
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -24,19 +25,21 @@ import (
"github.com/astaxie/beego"
)
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
type ProjectMemberAPI struct {
BaseAPI
memberId int
currentUserId int
memberID int
currentUserID int
project *models.Project
}
type memberReq struct {
Username string `json:"user_name"`
UserId int `json:"user_id"`
UserID int `json:"user_id"`
Roles []int `json:"roles"`
}
// Prepare validates the URL and parms
func (pma *ProjectMemberAPI) Prepare() {
pid, err := strconv.ParseInt(pma.Ctx.Input.Param(":pid"), 10, 64)
if err != nil {
@ -44,7 +47,7 @@ func (pma *ProjectMemberAPI) Prepare() {
pma.CustomAbort(http.StatusBadRequest, "invalid project Id")
return
}
p, err := dao.GetProjectById(models.Project{ProjectId: pid})
p, err := dao.GetProjectByID(pid)
if err != nil {
beego.Error("Error occurred in GetProjectById:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
@ -55,34 +58,34 @@ func (pma *ProjectMemberAPI) Prepare() {
pma.CustomAbort(http.StatusNotFound, "Project does not exist")
}
pma.project = p
pma.currentUserId = pma.ValidateUser()
pma.currentUserID = pma.ValidateUser()
mid := pma.Ctx.Input.Param(":mid")
if mid == "current" {
pma.memberId = pma.currentUserId
pma.memberID = pma.currentUserID
} else if len(mid) == 0 {
pma.memberId = 0
pma.memberID = 0
} else if len(mid) > 0 {
memberId, err := strconv.Atoi(mid)
memberID, err := strconv.Atoi(mid)
if err != nil {
beego.Error("Invalid member Id, error:", err)
pma.CustomAbort(http.StatusBadRequest, "Invalid member id")
}
pma.memberId = memberId
pma.memberID = memberID
}
}
// Get ...
func (pma *ProjectMemberAPI) Get() {
pid := pma.project.ProjectId
if !CheckProjectPermission(pma.currentUserId, pid) {
beego.Warning("Current user, user id :", pma.currentUserId, "does not have permission for project, id:", pid)
pid := pma.project.ProjectID
if !checkProjectPermission(pma.currentUserID, pid) {
beego.Warning("Current user, user id :", pma.currentUserID, "does not have permission for project, id:", pid)
pma.RenderError(http.StatusForbidden, "")
return
}
if pma.memberId == 0 { //member id not set return list of the members
queryProject := models.Project{ProjectId: pid}
if pma.memberID == 0 { //member id not set return list of the members
username := pma.GetString("username")
queryUser := models.User{Username: "%" + username + "%"}
userList, err := dao.GetUserByProject(queryProject, queryUser)
userList, err := dao.GetUserByProject(pid, queryUser)
if err != nil {
beego.Error("Failed to query database for member list, error:", err)
pma.RenderError(http.StatusInternalServerError, "Internal Server Error")
@ -90,86 +93,88 @@ func (pma *ProjectMemberAPI) Get() {
}
pma.Data["json"] = userList
} else { //return detail of a member
roleList, err := dao.GetUserProjectRoles(models.User{UserId: pma.memberId}, pid)
roleList, err := dao.GetUserProjectRoles(models.User{UserID: pma.memberID}, pid)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
//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})
user, err := dao.GetUser(models.User{UserID: pma.memberID})
if err != nil {
beego.Error("Error occurred in GetUser:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
result["user_name"] = user.Username
result["user_id"] = pma.memberId
result["user_id"] = pma.memberID
result["roles"] = roleList
pma.Data["json"] = result
}
pma.ServeJSON()
}
// Post ...
func (pma *ProjectMemberAPI) Post() {
pid := pma.project.ProjectId
userQuery := models.User{UserId: pma.currentUserId, RoleId: models.PROJECTADMIN}
pid := pma.project.ProjectID
userQuery := models.User{UserID: pma.currentUserID, RoleID: models.PROJECTADMIN}
rolelist, err := dao.GetUserProjectRoles(userQuery, pid)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if len(rolelist) == 0 {
beego.Warning("Current user, id:", pma.currentUserId, "does not have project admin role for project, id:", pid)
beego.Warning("Current user, id:", pma.currentUserID, "does not have project admin role for project, id:", pid)
pma.RenderError(http.StatusForbidden, "")
return
}
var req memberReq
pma.DecodeJsonReq(&req)
pma.DecodeJSONReq(&req)
username := req.Username
userId := CheckUserExists(username)
if userId <= 0 {
userID := checkUserExists(username)
if userID <= 0 {
beego.Warning("User does not exist, user name:", username)
pma.RenderError(http.StatusNotFound, "User does not exist")
return
}
rolelist, err = dao.GetUserProjectRoles(models.User{UserId: userId}, pid)
rolelist, err = dao.GetUserProjectRoles(models.User{UserID: userID}, pid)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if len(rolelist) > 0 {
beego.Warning("user is already added to project, user id:", userId, ", project id:", pid)
beego.Warning("user is already added to project, user id:", userID, ", project id:", pid)
pma.RenderError(http.StatusConflict, "user is ready in project")
return
}
for _, rid := range req.Roles {
err = dao.AddUserProjectRole(userId, pid, int(rid))
err = dao.AddUserProjectRole(userID, pid, int(rid))
if err != nil {
beego.Error("Failed to update DB to add project user role, project id:", pid, ", user id:", userId, ", role id:", rid)
beego.Error("Failed to update DB to add project user role, project id:", pid, ", user id:", userID, ", role id:", rid)
pma.RenderError(http.StatusInternalServerError, "Failed to update data in database")
return
}
}
}
// Put ...
func (pma *ProjectMemberAPI) Put() {
pid := pma.project.ProjectId
mid := pma.memberId
userQuery := models.User{UserId: pma.currentUserId, RoleId: models.PROJECTADMIN}
pid := pma.project.ProjectID
mid := pma.memberID
userQuery := models.User{UserID: pma.currentUserID, RoleID: models.PROJECTADMIN}
rolelist, err := dao.GetUserProjectRoles(userQuery, pid)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if len(rolelist) == 0 {
beego.Warning("Current user, id:", pma.currentUserId, ", does not have project admin role for project, id:", pid)
beego.Warning("Current user, id:", pma.currentUserID, ", does not have project admin role for project, id:", pid)
pma.RenderError(http.StatusForbidden, "")
return
}
var req memberReq
pma.DecodeJsonReq(&req)
roleList, err := dao.GetUserProjectRoles(models.User{UserId: mid}, pid)
pma.DecodeJSONReq(&req)
roleList, err := dao.GetUserProjectRoles(models.User{UserID: mid}, pid)
if len(roleList) == 0 {
beego.Warning("User is not in project, user id:", mid, ", project id:", pid)
pma.RenderError(http.StatusNotFound, "user not exist in project")
@ -194,13 +199,14 @@ func (pma *ProjectMemberAPI) Put() {
}
}
// Delete ...
func (pma *ProjectMemberAPI) Delete() {
pid := pma.project.ProjectId
mid := pma.memberId
userQuery := models.User{UserId: pma.currentUserId, RoleId: models.PROJECTADMIN}
pid := pma.project.ProjectID
mid := pma.memberID
userQuery := models.User{UserID: pma.currentUserID, RoleID: models.PROJECTADMIN}
rolelist, err := dao.GetUserProjectRoles(userQuery, pid)
if len(rolelist) == 0 {
beego.Warning("Current user, id:", pma.currentUserId, ", does not have project admin role for project, id:", pid)
beego.Warning("Current user, id:", pma.currentUserID, ", does not have project admin role for project, id:", pid)
pma.RenderError(http.StatusForbidden, "")
return
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -28,10 +29,11 @@ import (
"github.com/astaxie/beego"
)
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
type ProjectAPI struct {
BaseAPI
userId int
projectId int64
userID int
projectID int64
}
type projectReq struct {
@ -39,33 +41,35 @@ type projectReq struct {
Public bool `json:"public"`
}
const PROJECT_NAME_MAX_LEN int = 30
const projectNameMaxLen int = 30
// Prepare validates the URL and the user
func (p *ProjectAPI) Prepare() {
p.userId = p.ValidateUser()
id_str := p.Ctx.Input.Param(":id")
if len(id_str) > 0 {
p.userID = p.ValidateUser()
idStr := p.Ctx.Input.Param(":id")
if len(idStr) > 0 {
var err error
p.projectId, err = strconv.ParseInt(id_str, 10, 64)
p.projectID, err = strconv.ParseInt(idStr, 10, 64)
if err != nil {
log.Printf("Error parsing project id: %s, error: %v", id_str, err)
log.Printf("Error parsing project id: %s, error: %v", idStr, err)
p.CustomAbort(http.StatusBadRequest, "invalid project id")
}
exist, err := dao.ProjectExists(p.projectId)
exist, err := dao.ProjectExists(p.projectID)
if err != nil {
log.Printf("Error occurred in ProjectExists: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectId))
p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
}
}
}
// Post ...
func (p *ProjectAPI) Post() {
var req projectReq
var public int
p.DecodeJsonReq(&req)
p.DecodeJSONReq(&req)
if req.Public {
public = 1
}
@ -84,14 +88,15 @@ func (p *ProjectAPI) Post() {
p.RenderError(http.StatusConflict, "")
return
}
project := models.Project{OwnerId: p.userId, Name: projectName, CreationTime: time.Now(), Public: public}
project := models.Project{OwnerID: p.userID, Name: projectName, CreationTime: time.Now(), Public: public}
err = dao.AddProject(project)
if err != nil {
beego.Error("Failed to add project, error: %v", err)
beego.Error("Failed to add project, error: ", err)
p.RenderError(http.StatusInternalServerError, "Failed to add project")
}
}
// Head ...
func (p *ProjectAPI) Head() {
projectName := p.GetString("project_name")
result, err := dao.ProjectExists(projectName)
@ -106,8 +111,9 @@ func (p *ProjectAPI) Head() {
}
}
// Get ...
func (p *ProjectAPI) Get() {
queryProject := models.Project{UserId: p.userId}
queryProject := models.Project{UserID: p.userID}
projectName := p.GetString("project_name")
if len(projectName) > 0 {
queryProject.Name = "%" + projectName + "%"
@ -121,7 +127,7 @@ func (p *ProjectAPI) Get() {
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
for i := 0; i < len(projectList); i++ {
if isProjectAdmin(p.userId, projectList[i].ProjectId) {
if isProjectAdmin(p.userID, projectList[i].ProjectID) {
projectList[i].Togglable = true
}
}
@ -129,37 +135,39 @@ func (p *ProjectAPI) Get() {
p.ServeJSON()
}
// Put ...
func (p *ProjectAPI) Put() {
var req projectReq
var public int
projectId, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
if err != nil {
beego.Error("Error parsing project id:", projectId, ", error: ", err)
beego.Error("Error parsing project id:", projectID, ", error: ", err)
p.RenderError(http.StatusBadRequest, "invalid project id")
return
}
p.DecodeJsonReq(&req)
p.DecodeJSONReq(&req)
if req.Public {
public = 1
}
if !isProjectAdmin(p.userId, projectId) {
beego.Warning("Current user, id:", p.userId, ", does not have project admin role for project, id:", projectId)
if !isProjectAdmin(p.userID, projectID) {
beego.Warning("Current user, id:", p.userID, ", does not have project admin role for project, id:", projectID)
p.RenderError(http.StatusForbidden, "")
return
}
err = dao.ToggleProjectPublicity(p.projectId, public)
err = dao.ToggleProjectPublicity(p.projectID, public)
if err != nil {
beego.Error("Error while updating project, project id:", projectId, ", error:", err)
beego.Error("Error while updating project, project id:", projectID, ", error:", err)
p.RenderError(http.StatusInternalServerError, "Failed to update project")
}
}
// FilterAccessLog handles GET to /api/projects/{}/logs
func (p *ProjectAPI) FilterAccessLog() {
var filter models.AccessLog
p.DecodeJsonReq(&filter)
p.DecodeJSONReq(&filter)
username := filter.Username
keywords := filter.Keywords
@ -167,7 +175,7 @@ func (p *ProjectAPI) FilterAccessLog() {
beginTime := time.Unix(filter.BeginTimestamp, 0)
endTime := time.Unix(filter.EndTimestamp, 0)
query := models.AccessLog{ProjectId: p.projectId, Username: "%" + username + "%", Keywords: keywords, BeginTime: beginTime, BeginTimestamp: filter.BeginTimestamp, EndTime: endTime, EndTimestamp: filter.EndTimestamp}
query := models.AccessLog{ProjectID: p.projectID, Username: "%" + username + "%", Keywords: keywords, BeginTime: beginTime, BeginTimestamp: filter.BeginTimestamp, EndTime: endTime, EndTimestamp: filter.EndTimestamp}
log.Printf("Query AccessLog: begin: %v, end: %v, keywords: %s", query.BeginTime, query.EndTime, query.Keywords)
@ -180,8 +188,8 @@ func (p *ProjectAPI) FilterAccessLog() {
p.ServeJSON()
}
func isProjectAdmin(userId int, pid int64) bool {
userQuery := models.User{UserId: userId, RoleId: models.PROJECTADMIN}
func isProjectAdmin(userID int, pid int64) bool {
userQuery := models.User{UserID: userID, RoleID: models.PROJECTADMIN}
rolelist, err := dao.GetUserProjectRoles(userQuery, pid)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err, ", returning false")
@ -195,7 +203,7 @@ func validateProjectReq(req projectReq) error {
if len(pn) == 0 {
return fmt.Errorf("Project name can not be empty")
}
if len(pn) > PROJECT_NAME_MAX_LEN {
if len(pn) > projectNameMaxLen {
return fmt.Errorf("Project name is too long")
}
return nil

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -28,21 +29,23 @@ import (
"github.com/astaxie/beego"
)
//For repostiories, we won't check the session in this API due to search functionality, querying manifest will be contorlled by
//the security of registry
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
// in the query string as the web framework can not parse the URL if it contains veriadic sectors.
// For repostiories, we won't check the session in this API due to search functionality, querying manifest will be contorlled by
// the security of registry
type RepositoryAPI struct {
BaseAPI
userId int
userID int
username string
}
// Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission.
func (ra *RepositoryAPI) Prepare() {
userId, ok := ra.GetSession("userId").(int)
userID, ok := ra.GetSession("userId").(int)
if !ok {
ra.userId = dao.NON_EXIST_USER_ID
ra.userID = dao.NonExistUserID
} else {
ra.userId = userId
ra.userID = userID
}
username, ok := ra.GetSession("username").(string)
if !ok {
@ -53,25 +56,25 @@ func (ra *RepositoryAPI) Prepare() {
}
}
// Get ...
func (ra *RepositoryAPI) Get() {
projectId, err0 := ra.GetInt64("project_id")
projectID, err0 := ra.GetInt64("project_id")
if err0 != nil {
beego.Error("Failed to get project id, error:", err0)
ra.RenderError(http.StatusBadRequest, "Invalid project id")
return
}
projectQuery := models.Project{ProjectId: projectId}
p, err := dao.GetProjectById(projectQuery)
p, err := dao.GetProjectByID(projectID)
if err != nil {
beego.Error("Error occurred in GetProjectById:", err)
ra.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if p == nil {
beego.Warning("Project with Id:", projectId, ", does not exist", projectId)
beego.Warning("Project with Id:", projectID, ", does not exist")
ra.RenderError(http.StatusNotFound, "")
return
}
if p.Public == 0 && !CheckProjectPermission(ra.userId, projectId) {
if p.Public == 0 && !checkProjectPermission(ra.userID, projectID) {
ra.RenderError(http.StatusForbidden, "")
return
}
@ -103,34 +106,35 @@ func (ra *RepositoryAPI) Get() {
ra.ServeJSON()
}
type Tag struct {
Name string `json: "name"`
type tag struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}
type HistroyItem struct {
type histroyItem struct {
V1Compatibility string `json:"v1Compatibility"`
}
type Manifest struct {
type manifest struct {
Name string `json:"name"`
Tag string `json:"tag"`
Architecture string `json:"architecture"`
SchemaVersion int `json:"schemaVersion"`
History []HistroyItem `json:"history"`
History []histroyItem `json:"history"`
}
// GetTags handles GET /api/repositories/tags
func (ra *RepositoryAPI) GetTags() {
var tags []string
repoName := ra.GetString("repo_name")
result, err := svc_utils.RegistryApiGet(svc_utils.BuildRegistryUrl(repoName, "tags", "list"), ra.username)
result, err := svc_utils.RegistryAPIGet(svc_utils.BuildRegistryURL(repoName, "tags", "list"), ra.username)
if err != nil {
beego.Error("Failed to get repo tags, repo name:", repoName, ", error: ", err)
ra.RenderError(http.StatusInternalServerError, "Failed to get repo tags")
} else {
t := Tag{}
t := tag{}
json.Unmarshal(result, &t)
tags = t.Tags
}
@ -138,38 +142,36 @@ func (ra *RepositoryAPI) GetTags() {
ra.ServeJSON()
}
// GetManifests handles GET /api/repositories/manifests
func (ra *RepositoryAPI) GetManifests() {
repoName := ra.GetString("repo_name")
tag := ra.GetString("tag")
item := models.RepoItem{}
result, err := svc_utils.RegistryApiGet(svc_utils.BuildRegistryUrl(repoName, "manifests", tag), ra.username)
result, err := svc_utils.RegistryAPIGet(svc_utils.BuildRegistryURL(repoName, "manifests", tag), ra.username)
if err != nil {
beego.Error("Failed to get manifests for repo, repo name:", repoName, ", tag:", tag, ", error:", err)
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
return
} else {
mani := Manifest{}
err = json.Unmarshal(result, &mani)
if err != nil {
beego.Error("Failed to decode json from response for manifests, repo name:", repoName, ", tag:", tag, ", error:", err)
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
return
} else {
v1Compatibility := mani.History[0].V1Compatibility
err = json.Unmarshal([]byte(v1Compatibility), &item)
if err != nil {
beego.Error("Failed to decode V1 field for repo, repo name:", repoName, ", tag:", tag, ", error:", err)
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
return
} else {
item.CreatedStr = item.Created.Format("2006-01-02 15:04:05")
item.DurationDays = strconv.Itoa(int(time.Since(item.Created).Hours()/24)) + " days"
}
}
}
mani := manifest{}
err = json.Unmarshal(result, &mani)
if err != nil {
beego.Error("Failed to decode json from response for manifests, repo name:", repoName, ", tag:", tag, ", error:", err)
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
return
}
v1Compatibility := mani.History[0].V1Compatibility
err = json.Unmarshal([]byte(v1Compatibility), &item)
if err != nil {
beego.Error("Failed to decode V1 field for repo, repo name:", repoName, ", tag:", tag, ", error:", err)
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
return
}
item.CreatedStr = item.Created.Format("2006-01-02 15:04:05")
item.DurationDays = strconv.Itoa(int(time.Since(item.Created).Hours()/24)) + " days"
ra.Data["json"] = item
ra.ServeJSON()

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -27,24 +28,26 @@ import (
"github.com/astaxie/beego"
)
// SearchAPI handles requesst to /api/search
type SearchAPI struct {
BaseAPI
}
type SearchResult struct {
type searchResult struct {
Project []map[string]interface{} `json:"project"`
Repository []map[string]interface{} `json:"repository"`
}
// Get ...
func (n *SearchAPI) Get() {
userId, ok := n.GetSession("userId").(int)
userID, ok := n.GetSession("userId").(int)
if !ok {
userId = dao.NON_EXIST_USER_ID
userID = dao.NonExistUserID
}
keyword := n.GetString("q")
projects, err := dao.QueryRelevantProjects(userId)
projects, err := dao.QueryRelevantProjects(userID)
if err != nil {
beego.Error("Failed to get projects of user id:", userId, ", error:", err)
beego.Error("Failed to get projects of user id:", userID, ", error:", err)
n.CustomAbort(http.StatusInternalServerError, "Failed to get project search result")
}
projectSorter := &utils.ProjectSorter{Projects: projects}
@ -57,7 +60,7 @@ func (n *SearchAPI) Get() {
}
if match {
entry := make(map[string]interface{})
entry["id"] = p.ProjectId
entry["id"] = p.ProjectID
entry["name"] = p.Name
entry["public"] = p.Public
projectResult = append(projectResult, entry)
@ -71,7 +74,7 @@ func (n *SearchAPI) Get() {
}
sort.Strings(repositories)
repositoryResult := filterRepositories(repositories, projects, keyword)
result := &SearchResult{Project: projectResult, Repository: repositoryResult}
result := &searchResult{Project: projectResult, Repository: repositoryResult}
n.Data["json"] = result
n.ServeJSON()
}
@ -93,7 +96,7 @@ func filterRepositories(repositories []string, projects []models.Project, keywor
entry := make(map[string]interface{})
entry["repository_name"] = r.Name
entry["project_name"] = projects[j].Name
entry["project_id"] = projects[j].ProjectId
entry["project_id"] = projects[j].ProjectID
entry["project_public"] = projects[j].Public
result = append(result, entry)
} else {

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -24,48 +25,51 @@ import (
"github.com/astaxie/beego"
)
// UserAPI handles request to /api/users/{}
type UserAPI struct {
BaseAPI
currentUid int
userId int
currentUserID int
userID int
}
// Prepare validates the URL and parms
func (ua *UserAPI) Prepare() {
ua.currentUid = ua.ValidateUser()
ua.currentUserID = ua.ValidateUser()
id := ua.Ctx.Input.Param(":id")
if id == "current" {
ua.userId = ua.currentUid
ua.userID = ua.currentUserID
} else if len(id) > 0 {
var err error
ua.userId, err = strconv.Atoi(id)
ua.userID, err = strconv.Atoi(id)
if err != nil {
beego.Error("Invalid user id, error:", err)
ua.CustomAbort(http.StatusBadRequest, "Invalid user Id")
}
userQuery := models.User{UserId: ua.userId}
userQuery := models.User{UserID: ua.userID}
u, err := dao.GetUser(userQuery)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if u == nil {
beego.Error("User with Id:", ua.userId, "does not exist")
beego.Error("User with Id:", ua.userID, "does not exist")
ua.CustomAbort(http.StatusNotFound, "")
}
}
}
// Get ...
func (ua *UserAPI) Get() {
exist, err := dao.IsAdminRole(ua.currentUid)
exist, err := dao.IsAdminRole(ua.currentUserID)
if err != nil {
beego.Error("Error occurred in IsAdminRole:", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if ua.userId == 0 { //list users
if ua.userID == 0 { //list users
if !exist {
beego.Error("Current user, id:", ua.currentUid, ", does not have admin role, can not list users")
beego.Error("Current user, id:", ua.currentUserID, ", does not have admin role, can not list users")
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
@ -82,8 +86,8 @@ func (ua *UserAPI) Get() {
}
ua.Data["json"] = userList
} else if ua.userId == ua.currentUid || exist {
userQuery := models.User{UserId: ua.userId}
} else if ua.userID == ua.currentUserID || exist {
userQuery := models.User{UserID: ua.userID}
u, err := dao.GetUser(userQuery)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
@ -91,40 +95,42 @@ func (ua *UserAPI) Get() {
}
ua.Data["json"] = u
} else {
beego.Error("Current user, id:", ua.currentUid, "does not have admin role, can not view other user's detail")
beego.Error("Current user, id:", ua.currentUserID, "does not have admin role, can not view other user's detail")
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
ua.ServeJSON()
}
// Put ...
func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body
exist, err := dao.IsAdminRole(ua.currentUid)
exist, err := dao.IsAdminRole(ua.currentUserID)
if err != nil {
beego.Error("Error occurred in IsAdminRole:", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
beego.Warning("current user, id:", ua.currentUid, ", does not have admin role, can not update other user's role")
beego.Warning("current user, id:", ua.currentUserID, ", does not have admin role, can not update other user's role")
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
userQuery := models.User{UserId: ua.userId}
userQuery := models.User{UserID: ua.userID}
dao.ToggleUserAdminRole(userQuery)
}
// Delete ...
func (ua *UserAPI) Delete() {
exist, err := dao.IsAdminRole(ua.currentUid)
exist, err := dao.IsAdminRole(ua.currentUserID)
if err != nil {
beego.Error("Error occurred in IsAdminRole:", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
beego.Warning("current user, id:", ua.currentUid, ", does not have admin role, can not remove user")
beego.Warning("current user, id:", ua.currentUserID, ", does not have admin role, can not remove user")
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
err = dao.DeleteUser(ua.userId)
err = dao.DeleteUser(ua.userID)
if err != nil {
beego.Error("Failed to delete data from database, error:", err)
ua.RenderError(http.StatusInternalServerError, "Failed to delete User")

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
import (
@ -21,8 +22,8 @@ import (
"github.com/astaxie/beego"
)
func CheckProjectPermission(userId int, projectId int64) bool {
exist, err := dao.IsAdminRole(userId)
func checkProjectPermission(userID int, projectID int64) bool {
exist, err := dao.IsAdminRole(userID)
if err != nil {
beego.Error("Error occurred in IsAdminRole:", err)
return false
@ -30,7 +31,7 @@ func CheckProjectPermission(userId int, projectId int64) bool {
if exist {
return true
}
roleList, err := dao.GetUserProjectRoles(models.User{UserId: userId}, projectId)
roleList, err := dao.GetUserProjectRoles(models.User{UserID: userID}, projectID)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
return false
@ -38,14 +39,14 @@ func CheckProjectPermission(userId int, projectId int64) bool {
return len(roleList) > 0
}
func CheckUserExists(name string) int {
func checkUserExists(name string) int {
u, err := dao.GetUser(models.User{Username: name})
if err != nil {
beego.Error("Error occurred in GetUser:", err)
return 0
}
if u != nil {
return u.UserId
return u.UserID
}
return 0
}

View File

@ -12,10 +12,12 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package opt_auth
package auth
import (
"fmt"
"log"
"os"
"github.com/vmware/harbor/models"
@ -23,31 +25,36 @@ import (
"github.com/astaxie/beego"
)
type OptAuth interface {
Validate(auth models.AuthModel) (*models.User, error)
// Authenticator provides interface to authenticate user credentials.
type Authenticator interface {
// Authenticate ...
Authenticate(m models.AuthModel) (*models.User, error)
}
var registry = make(map[string]OptAuth)
var registry = make(map[string]Authenticator)
func Register(name string, optAuth OptAuth) {
// Register add different authenticators to registry map.
func Register(name string, authenticator Authenticator) {
if _, dup := registry[name]; dup {
panic(name + " already exist.")
log.Printf("authenticator: %s has been registered", name)
return
}
registry[name] = optAuth
registry[name] = authenticator
}
func Login(auth models.AuthModel) (*models.User, error) {
// Login authenticates user credentials based on setting.
func Login(m models.AuthModel) (*models.User, error) {
var authMode string = os.Getenv("AUTH_MODE")
if authMode == "" || auth.Principal == "admin" {
var authMode = os.Getenv("AUTH_MODE")
if authMode == "" || m.Principal == "admin" {
authMode = "db_auth"
}
beego.Debug("Current AUTH_MODE is ", authMode)
optAuth := registry[authMode]
if optAuth == nil {
authenticator, ok := registry[authMode]
if !ok {
return nil, fmt.Errorf("Unrecognized auth_mode: %s", authMode)
}
return optAuth.Validate(auth)
return authenticator.Authenticate(m)
}

View File

@ -12,18 +12,21 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/opt_auth"
)
type DbAuth struct{}
// Auth implements Authenticator interface to authenticate user against DB.
type Auth struct{}
func (d *DbAuth) Validate(auth models.AuthModel) (*models.User, error) {
u, err := dao.LoginByDb(auth)
// Authenticate calls dao to authenticate user.
func (d *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
u, err := dao.LoginByDb(m)
if err != nil {
return nil, err
}
@ -31,5 +34,5 @@ func (d *DbAuth) Validate(auth models.AuthModel) (*models.User, error) {
}
func init() {
opt_auth.Register("db_auth", &DbAuth{})
auth.Register("db_auth", &Auth{})
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package ldap
import (
@ -21,35 +22,38 @@ import (
"os"
"strings"
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/opt_auth"
"github.com/astaxie/beego"
"github.com/mqu/openldap"
)
type LdapAuth struct{}
// Auth implements Authenticator interface to authenticate against LDAP
type Auth struct{}
const META_CHARS = "&|!=~*<>()"
const metaChars = "&|!=~*<>()"
func (l *LdapAuth) Validate(auth models.AuthModel) (*models.User, error) {
// Authenticate checks user's credential agains LDAP based on basedn template and LDAP URL,
// if the check is successful a dummy record will be insert into DB, such that this user can
// be associated to other entities in the system.
func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
ldapUrl := os.Getenv("LDAP_URL")
if ldapUrl == "" {
ldapURL := os.Getenv("LDAP_URL")
if ldapURL == "" {
return nil, errors.New("Can not get any available LDAP_URL.")
}
beego.Debug("ldapUrl:", ldapUrl)
beego.Debug("ldapURL:", ldapURL)
p := auth.Principal
for _, c := range META_CHARS {
p := m.Principal
for _, c := range metaChars {
if strings.ContainsRune(p, c) {
log.Printf("The principal contains meta char: %q", c)
return nil, nil
return nil, fmt.Errorf("the principal contains meta char: %q", c)
}
}
ldap, err := openldap.Initialize(ldapUrl)
ldap, err := openldap.Initialize(ldapURL)
if err != nil {
return nil, err
}
@ -62,10 +66,10 @@ func (l *LdapAuth) Validate(auth models.AuthModel) (*models.User, error) {
return nil, errors.New("Can not get any available LDAP_BASE_DN.")
}
baseDn := fmt.Sprintf(ldapBaseDn, auth.Principal)
baseDn := fmt.Sprintf(ldapBaseDn, m.Principal)
beego.Debug("baseDn:", baseDn)
err = ldap.Bind(baseDn, auth.Password)
err = ldap.Bind(baseDn, m.Password)
if err != nil {
return nil, err
}
@ -108,19 +112,19 @@ func (l *LdapAuth) Validate(auth models.AuthModel) (*models.User, error) {
if err != nil {
return nil, err
}
u.UserId = currentUser.UserId
u.UserID = currentUser.UserID
} else {
u.Password = "12345678AbC"
u.Comment = "registered from LDAP."
userId, err := dao.Register(u)
userID, err := dao.Register(u)
if err != nil {
return nil, err
}
u.UserId = int(userId)
u.UserID = int(userID)
}
return &u, nil
}
func init() {
opt_auth.Register("ldap_auth", &LdapAuth{})
auth.Register("ldap_auth", &Auth{})
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
@ -23,14 +24,17 @@ import (
"github.com/beego/i18n"
)
// CommonController handles request from UI that doesn't expect a page, such as /login /logout ...
type CommonController struct {
BaseController
}
// Render returns nil.
func (c *CommonController) Render() error {
return nil
}
// BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers.
type BaseController struct {
beego.Controller
i18n.Locale
@ -42,14 +46,15 @@ type langType struct {
}
const (
DEFAULT_LANG = "en-US"
defaultLang = "en-US"
)
var supportLanguages map[string]langType
// Prepare extracts the language information from request and populate data for rendering templates.
func (b *BaseController) Prepare() {
var lang string = ""
var lang string
al := b.Ctx.Request.Header.Get("Accept-Language")
if len(al) > 4 {
@ -60,7 +65,7 @@ func (b *BaseController) Prepare() {
}
if _, exist := supportLanguages[lang]; exist == false { //Check if support the request language.
lang = DEFAULT_LANG //Set default language if not supported.
lang = defaultLang //Set default language if not supported.
}
sessionLang := b.GetSession("lang")
@ -88,8 +93,8 @@ func (b *BaseController) Prepare() {
b.Data["CurLang"] = curLang.Name
b.Data["RestLangs"] = restLangs
sessionUserId := b.GetSession("userId")
if sessionUserId != nil {
sessionUserID := b.GetSession("userId")
if sessionUserID != nil {
b.Data["Username"] = b.GetSession("username")
}
authMode := os.Getenv("AUTH_MODE")
@ -99,6 +104,7 @@ func (b *BaseController) Prepare() {
b.Data["AuthMode"] = authMode
}
// ForwardTo setup layout and template for content for a page.
func (b *BaseController) ForwardTo(pageTitle string, pageName string) {
b.Layout = "segment/base-layout.tpl"
b.TplName = "segment/base-layout.tpl"

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
@ -25,55 +26,24 @@ import (
"github.com/astaxie/beego"
)
// ItemDetailController handles requet to /registry/detail, which shows the detail of a project.
type ItemDetailController struct {
BaseController
}
var SYS_ADMIN int = 1
var PROJECT_ADMIN int = 2
var DEVELOPER int = 3
var GUEST int = 4
func CheckProjectRole(userId int, projectId int64) bool {
if projectId == 0 {
return false
}
userQuery := models.User{UserId: int(userId)}
if userId == SYS_ADMIN {
return true
}
roleList, err := dao.GetUserProjectRoles(userQuery, projectId)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
return false
}
return len(roleList) > 0
}
func CheckPublicProject(projectId int64) bool {
projectQuery := models.Project{ProjectId: projectId}
project, err := dao.GetProjectById(projectQuery)
if err != nil {
beego.Error("Error occurred in GetProjectById:", err)
return false
}
if project != nil && project.Public == 1 {
return true
}
return false
}
// Get will check if user has permission to view a certain project, if not user will be redirected to signin or his homepage.
// If the check is passed it renders the project detail page.
func (idc *ItemDetailController) Get() {
sessionUserId := idc.GetSession("userId")
projectId, _ := idc.GetInt64("project_id")
projectID, _ := idc.GetInt64("project_id")
if CheckPublicProject(projectId) == false && (sessionUserId == nil || !CheckProjectRole(sessionUserId.(int), projectId)) {
idc.Redirect("/signIn?uri="+url.QueryEscape(idc.Ctx.Input.URI()), http.StatusFound)
if projectID <= 0 {
beego.Error("Invalid project id:", projectID)
idc.Redirect("/signIn", http.StatusFound)
return
}
projectQuery := models.Project{ProjectId: projectId}
project, err := dao.GetProjectById(projectQuery)
project, err := dao.GetProjectByID(projectID)
if err != nil {
beego.Error("Error occurred in GetProjectById:", err)
@ -82,26 +52,42 @@ func (idc *ItemDetailController) Get() {
if project == nil {
idc.Redirect("/signIn", http.StatusFound)
return
}
idc.Data["ProjectId"] = project.ProjectId
idc.Data["ProjectName"] = project.Name
idc.Data["OwnerName"] = project.OwnerName
idc.Data["OwnerId"] = project.OwnerId
sessionUserID := idc.GetSession("userId")
if project.Public != 1 && sessionUserID == nil {
idc.Redirect("/signIn?uri="+url.QueryEscape(idc.Ctx.Input.URI()), http.StatusFound)
return
}
if sessionUserID != nil {
if sessionUserId != nil {
idc.Data["Username"] = idc.GetSession("username")
idc.Data["UserId"] = sessionUserId.(int)
roleList, err := dao.GetUserProjectRoles(models.User{UserId: sessionUserId.(int)}, projectId)
idc.Data["UserId"] = sessionUserID.(int)
roleList, err := dao.GetUserProjectRoles(models.User{UserID: sessionUserID.(int)}, projectID)
if err != nil {
beego.Error("Error occurred in GetUserProjectRoles:", err)
idc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if project.Public == 0 && len(roleList) == 0 {
idc.Redirect("/registry/project", http.StatusFound)
return
}
if len(roleList) > 0 {
idc.Data["RoleId"] = roleList[0].RoleId
idc.Data["RoleId"] = roleList[0].RoleID
}
}
idc.Data["ProjectId"] = project.ProjectID
idc.Data["ProjectName"] = project.Name
idc.Data["OwnerName"] = project.OwnerName
idc.Data["OwnerId"] = project.OwnerID
idc.Data["HarborRegUrl"] = os.Getenv("HARBOR_REG_URL")
idc.Data["RepoName"] = idc.GetString("repo_name")

View File

@ -12,39 +12,45 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"net/http"
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/opt_auth"
"github.com/astaxie/beego"
)
// IndexController handles request to /
type IndexController struct {
BaseController
}
// Get renders the index page.
func (c *IndexController) Get() {
c.Data["Username"] = c.GetSession("username")
c.ForwardTo("page_title_index", "index")
}
// SignInController handles request to /signIn
type SignInController struct {
BaseController
}
// Get renders Sign In page.
func (sic *SignInController) Get() {
sic.ForwardTo("page_title_sign_in", "sign-in")
}
// Login handles login request from UI.
func (c *CommonController) Login() {
principal := c.GetString("principal")
password := c.GetString("password")
user, err := opt_auth.Login(models.AuthModel{principal, password})
user, err := auth.Login(models.AuthModel{principal, password})
if err != nil {
beego.Error("Error occurred in UserLogin:", err)
c.CustomAbort(http.StatusInternalServerError, "Internal error.")
@ -54,10 +60,11 @@ func (c *CommonController) Login() {
c.CustomAbort(http.StatusUnauthorized, "")
}
c.SetSession("userId", user.UserId)
c.SetSession("userId", user.UserID)
c.SetSession("username", user.Username)
}
// SwitchLanguage handles UI request to switch between different languages and re-render template based on language.
func (c *CommonController) SwitchLanguage() {
lang := c.GetString("lang")
if lang == "en-US" || lang == "zh-CN" {
@ -67,6 +74,7 @@ func (c *CommonController) SwitchLanguage() {
c.Redirect(c.Ctx.Request.Header.Get("Referer"), http.StatusFound)
}
// Logout handles UI request to logout.
func (c *CommonController) Logout() {
c.DestroySession()
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
@ -28,160 +29,170 @@ import (
"github.com/astaxie/beego"
)
// ChangePasswordController handles request to /changePassword
type ChangePasswordController struct {
BaseController
}
// Get renders the page for user to change password.
func (cpc *ChangePasswordController) Get() {
sessionUserId := cpc.GetSession("userId")
if sessionUserId == nil {
sessionUserID := cpc.GetSession("userId")
if sessionUserID == nil {
cpc.Redirect("/signIn", http.StatusFound)
return
}
cpc.Data["Username"] = cpc.GetSession("username")
cpc.ForwardTo("page_title_change_password", "change-password")
}
func (cpc *CommonController) UpdatePassword() {
// UpdatePassword handles UI request to update user's password, it only works when the auth mode is db_auth.
func (cc *CommonController) UpdatePassword() {
sessionUserId := cpc.GetSession("userId")
sessionUserID := cc.GetSession("userId")
if sessionUserId == nil {
if sessionUserID == nil {
beego.Warning("User does not login.")
cpc.CustomAbort(http.StatusUnauthorized, "please_login_first")
cc.CustomAbort(http.StatusUnauthorized, "please_login_first")
}
oldPassword := cpc.GetString("old_password")
oldPassword := cc.GetString("old_password")
if oldPassword == "" {
beego.Error("Old password is blank")
cpc.CustomAbort(http.StatusBadRequest, "Old password is blank")
cc.CustomAbort(http.StatusBadRequest, "Old password is blank")
}
queryUser := models.User{UserId: sessionUserId.(int), Password: oldPassword}
queryUser := models.User{UserID: sessionUserID.(int), Password: oldPassword}
user, err := dao.CheckUserPassword(queryUser)
if err != nil {
beego.Error("Error occurred in CheckUserPassword:", err)
cpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
beego.Warning("Password input is not correct")
cpc.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
cc.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
}
password := cpc.GetString("password")
password := cc.GetString("password")
if password != "" {
updateUser := models.User{UserId: sessionUserId.(int), Password: password, Salt: user.Salt}
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)
cpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
} else {
cpc.CustomAbort(http.StatusBadRequest, "please_input_new_password")
cc.CustomAbort(http.StatusBadRequest, "please_input_new_password")
}
}
// ForgotPasswordController handles request to /forgotPassword
type ForgotPasswordController struct {
BaseController
}
type MessageDetail struct {
Hint string
Url string
Uuid string
}
// Get Renders the page for user to input Email to reset password.
func (fpc *ForgotPasswordController) Get() {
fpc.ForwardTo("page_title_forgot_password", "forgot-password")
}
func (fpc *CommonController) SendEmail() {
type messageDetail struct {
Hint string
URL string
UUID string
}
email := fpc.GetString("email")
// SendEmail verifies the Email address and contact SMTP server to send reset password Email.
func (cc *CommonController) SendEmail() {
email := cc.GetString("email")
pass, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\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,}))$`, email)
if !pass {
fpc.CustomAbort(http.StatusBadRequest, "email_content_illegal")
cc.CustomAbort(http.StatusBadRequest, "email_content_illegal")
} else {
queryUser := models.User{Email: email}
exist, err := dao.UserExists(queryUser, "email")
if err != nil {
beego.Error("Error occurred in UserExists:", err)
fpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if !exist {
fpc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
cc.CustomAbort(http.StatusNotFound, "email_does_not_exist")
}
messageTemplate, err := template.ParseFiles("views/reset-password-mail.tpl")
if err != nil {
beego.Error("Parse email template file failed:", err)
fpc.CustomAbort(http.StatusInternalServerError, err.Error())
cc.CustomAbort(http.StatusInternalServerError, err.Error())
}
message := new(bytes.Buffer)
harborUrl := os.Getenv("HARBOR_URL")
if harborUrl == "" {
harborUrl = "localhost"
harborURL := os.Getenv("HARBOR_URL")
if harborURL == "" {
harborURL = "localhost"
}
uuid, err := dao.GenerateRandomString()
if err != nil {
beego.Error("Error occurred in GenerateRandomString:", err)
fpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
err = messageTemplate.Execute(message, MessageDetail{
Hint: fpc.Tr("reset_email_hint"),
Url: harborUrl,
Uuid: uuid,
err = messageTemplate.Execute(message, messageDetail{
Hint: cc.Tr("reset_email_hint"),
URL: harborURL,
UUID: uuid,
})
if err != nil {
beego.Error("message template error:", err)
fpc.CustomAbort(http.StatusInternalServerError, "internal_error")
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
config, err := beego.AppConfig.GetSection("mail")
if err != nil {
beego.Error("Can not load app.conf:", err)
fpc.CustomAbort(http.StatusInternalServerError, "internal_error")
cc.CustomAbort(http.StatusInternalServerError, "internal_error")
}
mail := utils.Mail{
From: config["from"],
To: []string{email},
Subject: fpc.Tr("reset_email_subject"),
Subject: cc.Tr("reset_email_subject"),
Message: message.String()}
err = mail.SendMail()
if err != nil {
beego.Error("send email failed:", err)
fpc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
cc.CustomAbort(http.StatusInternalServerError, "send_email_failed")
}
user := models.User{ResetUuid: uuid, Email: email}
dao.UpdateUserResetUuid(user)
user := models.User{ResetUUID: uuid, Email: email}
dao.UpdateUserResetUUID(user)
}
}
// ResetPasswordController handles request to /resetPassword
type ResetPasswordController struct {
BaseController
}
// Get checks if reset_uuid in the reset link is valid and render the result page for user to reset password.
func (rpc *ResetPasswordController) Get() {
resetUuid := rpc.GetString("reset_uuid")
if resetUuid == "" {
resetUUID := rpc.GetString("reset_uuid")
if resetUUID == "" {
beego.Error("Reset uuid is blank.")
rpc.Redirect("/", http.StatusFound)
return
}
queryUser := models.User{ResetUuid: resetUuid}
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
@ -189,41 +200,42 @@ func (rpc *ResetPasswordController) Get() {
}
if user != nil {
rpc.Data["ResetUuid"] = user.ResetUuid
rpc.Data["ResetUuid"] = user.ResetUUID
rpc.ForwardTo("page_title_reset_password", "reset-password")
} else {
rpc.Redirect("/", http.StatusFound)
}
}
func (rpc *CommonController) ResetPassword() {
// ResetPassword handles request from the reset page and reset password
func (cc *CommonController) ResetPassword() {
resetUuid := rpc.GetString("reset_uuid")
if resetUuid == "" {
rpc.CustomAbort(http.StatusBadRequest, "Reset uuid is blank.")
resetUUID := cc.GetString("reset_uuid")
if resetUUID == "" {
cc.CustomAbort(http.StatusBadRequest, "Reset uuid is blank.")
}
queryUser := models.User{ResetUuid: resetUuid}
queryUser := models.User{ResetUUID: resetUUID}
user, err := dao.GetUser(queryUser)
if err != nil {
beego.Error("Error occurred in GetUser:", err)
rpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
beego.Error("User does not exist")
rpc.CustomAbort(http.StatusBadRequest, "User does not exist")
cc.CustomAbort(http.StatusBadRequest, "User does not exist")
}
password := rpc.GetString("password")
password := cc.GetString("password")
if password != "" {
user.Password = password
err = dao.ResetUserPassword(*user)
if err != nil {
beego.Error("Error occurred in ResetUserPassword:", err)
rpc.CustomAbort(http.StatusInternalServerError, "Internal error.")
cc.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
} else {
rpc.CustomAbort(http.StatusBadRequest, "password_is_required")
cc.CustomAbort(http.StatusBadRequest, "password_is_required")
}
}

View File

@ -1,23 +1,26 @@
/*
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.
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.
*/
package controllers
// ProjectController handles request to /registry/project
type ProjectController struct {
BaseController
}
// Get renders project page.
func (p *ProjectController) Get() {
p.Data["Username"] = p.GetSession("username")
p.ForwardTo("page_title_project", "project")

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
@ -25,10 +26,12 @@ import (
"github.com/astaxie/beego"
)
// RegisterController handles request to /register
type RegisterController struct {
BaseController
}
// 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" {
@ -38,6 +41,7 @@ func (rc *RegisterController) Get() {
}
}
// 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"))
@ -54,6 +58,7 @@ func (rc *CommonController) SignUp() {
}
}
// 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")

View File

@ -1,23 +1,26 @@
/*
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.
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.
*/
package controllers
// SearchController handles request to /search
type SearchController struct {
BaseController
}
// Get renders page for displaying search result.
func (sc *SearchController) Get() {
sc.Data["Username"] = sc.GetSession("username")
sc.Data["QueryParam"] = sc.GetString("q")

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -22,6 +23,7 @@ import (
"github.com/astaxie/beego/orm"
)
// AddAccessLog persists the access logs
func AddAccessLog(accessLog models.AccessLog) error {
o := orm.NewOrm()
p, err := o.Raw(`insert into access_log
@ -32,11 +34,12 @@ func AddAccessLog(accessLog models.AccessLog) error {
}
defer p.Close()
_, err = p.Exec(accessLog.UserId, accessLog.ProjectId, accessLog.RepoName, accessLog.Guid, accessLog.Operation)
_, err = p.Exec(accessLog.UserID, accessLog.ProjectID, accessLog.RepoName, accessLog.GUID, accessLog.Operation)
return err
}
//GetAccessLogs gets access logs according to different conditions
func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
o := orm.NewOrm()
@ -44,11 +47,11 @@ func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
from access_log a left join user u on a.user_id = u.user_id
where a.project_id = ? `
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, accessLog.ProjectId)
queryParam = append(queryParam, accessLog.ProjectID)
if accessLog.UserId != 0 {
if accessLog.UserID != 0 {
sql += ` and a.user_id = ? `
queryParam = append(queryParam, accessLog.UserId)
queryParam = append(queryParam, accessLog.UserID)
}
if accessLog.Operation != "" {
sql += ` and a.operation = ? `
@ -92,6 +95,7 @@ func GetAccessLogs(accessLog models.AccessLog) ([]models.AccessLog, error) {
return accessLogList, nil
}
// AccessLog ...
func AccessLog(username, projectName, repoName, action string) error {
o := orm.NewOrm()
sql := "insert into access_log (user_id, project_id, repo_name, operation, op_time) " +

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -22,12 +23,12 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
_ "github.com/go-sql-driver/mysql" //register mysql driver
)
const NON_EXIST_USER_ID = 0
// NonExistUserID : if a user does not exist, the ID of the user will be 0.
const NonExistUserID = 0
func isIllegalLength(s string, min int, max int) bool {
if min == -1 {
@ -48,6 +49,7 @@ func isContainIllegalChar(s string, illegalChar []string) bool {
return false
}
// GenerateRandomString generates a random string
func GenerateRandomString() (string, error) {
o := orm.NewOrm()
var uuid string
@ -59,6 +61,7 @@ func GenerateRandomString() (string, error) {
}
//InitDB initializes the database
func InitDB() {
orm.RegisterDriver("mysql", orm.DRMySQL)
addr := os.Getenv("MYSQL_HOST")
@ -74,26 +77,7 @@ func InitDB() {
password = os.Getenv("MYSQL_PWD")
}
var flag bool = true
if addr == "" {
beego.Error("Unset env of MYSQL_HOST")
flag = false
} else if port == "" {
beego.Error("Unset env of MYSQL_PORT_3306_TCP_PORT")
flag = false
} else if username == "" {
beego.Error("Unset env of MYSQL_USR")
flag = false
} else if password == "" {
beego.Error("Unset env of MYSQL_PWD")
flag = false
}
if !flag {
os.Exit(1)
}
db_str := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
dbStr := username + ":" + password + "@tcp(" + addr + ":" + port + ")/registry"
ch := make(chan int, 1)
go func() {
var err error
@ -114,7 +98,7 @@ func InitDB() {
case <-time.After(60 * time.Second):
panic("Failed to connect to DB after 60 seconds")
}
err := orm.RegisterDataBase("default", "mysql", db_str)
err := orm.RegisterDataBase("default", "mysql", dbStr)
if err != nil {
panic(err)
}

View File

@ -12,17 +12,16 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
package dao
import (
"fmt"
// "fmt"
"log"
"os"
"testing"
"time"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
"github.com/astaxie/beego/orm"
@ -84,15 +83,15 @@ func clearUp(username string) {
o.Commit()
}
const USERNAME string = "Tester01"
const PROJECT_NAME string = "test_project"
const SYS_ADMIN int = 1
const PROJECT_ADMIN int = 2
const DEVELOPER int = 3
const GUEST int = 4
const username string = "Tester01"
const projectName string = "test_project"
const SysAdmin int = 1
const projectAdmin int = 2
const developer int = 3
const guest int = 4
const PUBLICITY_ON = 1
const PUBLICITY_OFF = 0
const publicityOn = 1
const publicityOff = 0
func TestMain(m *testing.M) {
@ -109,9 +108,6 @@ func TestMain(m *testing.M) {
log.Fatalf("environment variable DB_PORT is not set")
}
dbPassword := os.Getenv("DB_PWD")
if len(dbPassword) == 0 {
log.Fatalf("environment variable DB_PWD is not set")
}
fmt.Printf("DB_HOST: %s, DB_USR: %s, DB_PORT: %s, DB_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
@ -120,8 +116,8 @@ func TestMain(m *testing.M) {
os.Setenv("MYSQL_USR", dbUser)
os.Setenv("MYSQL_PWD", dbPassword)
os.Setenv("AUTH_MODE", "db_auth")
dao.InitDB()
clearUp(USERNAME)
InitDB()
clearUp(username)
os.Exit(m.Run())
}
@ -129,29 +125,29 @@ func TestMain(m *testing.M) {
func TestRegister(t *testing.T) {
user := models.User{
Username: USERNAME,
Username: username,
Email: "tester01@vmware.com",
Password: "Abc12345",
Realname: "tester01",
Comment: "register",
}
_, err := dao.Register(user)
_, err := Register(user)
if err != nil {
t.Errorf("Error occurred in Register: %v", err)
}
//Check if user registered successfully.
queryUser := models.User{
Username: USERNAME,
Username: username,
}
newUser, err := dao.GetUser(queryUser)
newUser, err := GetUser(queryUser)
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
if newUser.Username != USERNAME {
t.Errorf("Username does not match, expected: %s, actual: %s", USERNAME, newUser.Username)
if newUser.Username != username {
t.Errorf("Username does not match, expected: %s, actual: %s", username, newUser.Username)
}
if newUser.Email != "tester01@vmware.com" {
t.Errorf("Email does not match, expected: %s, actual: %s", "tester01@vmware.com", newUser.Email)
@ -162,14 +158,14 @@ func TestUserExists(t *testing.T) {
var exists bool
var err error
exists, err = dao.UserExists(models.User{Username: USERNAME}, "username")
exists, err = UserExists(models.User{Username: username}, "username")
if err != nil {
t.Errorf("Error occurred in UserExists: %v", err)
}
if !exists {
t.Errorf("User %s was inserted but does not exist", USERNAME)
t.Errorf("User %s was inserted but does not exist", username)
}
exists, err = dao.UserExists(models.User{Email: "tester01@vmware.com"}, "email")
exists, err = UserExists(models.User{Email: "tester01@vmware.com"}, "email")
if err != nil {
t.Errorf("Error occurred in UserExists: %v", err)
@ -177,7 +173,7 @@ func TestUserExists(t *testing.T) {
if !exists {
t.Errorf("User with email %s inserted but does not exist", "tester01@vmware.com")
}
exists, err = dao.UserExists(models.User{Username: "NOTHERE"}, "username")
exists, err = UserExists(models.User{Username: "NOTHERE"}, "username")
if err != nil {
t.Errorf("Error occurred in UserExists: %v", err)
}
@ -189,11 +185,11 @@ func TestUserExists(t *testing.T) {
func TestLoginByUserName(t *testing.T) {
userQuery := models.User{
Username: USERNAME,
Username: username,
Password: "Abc12345",
}
loginUser, err := dao.LoginByDb(models.AuthModel{userQuery.Username, userQuery.Password})
loginUser, err := LoginByDb(models.AuthModel{userQuery.Username, userQuery.Password})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
@ -201,8 +197,8 @@ func TestLoginByUserName(t *testing.T) {
t.Errorf("No found for user logined by username and password: %v", userQuery)
}
if loginUser.Username != USERNAME {
t.Errorf("User's username does not match after login, expected: %s, actual: %s", USERNAME, loginUser.Username)
if loginUser.Username != username {
t.Errorf("User's username does not match after login, expected: %s, actual: %s", username, loginUser.Username)
}
}
@ -213,15 +209,15 @@ func TestLoginByEmail(t *testing.T) {
Password: "Abc12345",
}
loginUser, err := dao.LoginByDb(models.AuthModel{userQuery.Email, userQuery.Password})
loginUser, err := LoginByDb(models.AuthModel{userQuery.Email, userQuery.Password})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
if loginUser == nil {
t.Errorf("No found for user logined by email and password : %v", userQuery)
}
if loginUser.Username != USERNAME {
t.Errorf("User's username does not match after login, expected: %s, actual: %s", USERNAME, loginUser.Username)
if loginUser.Username != username {
t.Errorf("User's username does not match after login, expected: %s, actual: %s", username, loginUser.Username)
}
}
@ -229,10 +225,10 @@ var currentUser *models.User
func TestGetUser(t *testing.T) {
queryUser := models.User{
Username: USERNAME,
Username: username,
}
var err error
currentUser, err = dao.GetUser(queryUser)
currentUser, err = GetUser(queryUser)
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
@ -245,84 +241,84 @@ func TestGetUser(t *testing.T) {
}
func TestListUsers(t *testing.T) {
users, err := dao.ListUsers(models.User{})
users, err := ListUsers(models.User{})
if err != nil {
t.Errorf("Error occurred in ListUsers: %v", err)
}
if len(users) != 1 {
t.Errorf("Expect one user in list, but the acutal length is %d, the list: %+v", len(users), users)
}
users2, err := dao.ListUsers(models.User{Username: USERNAME})
users2, err := ListUsers(models.User{Username: username})
if len(users2) != 1 {
t.Errorf("Expect one user in list, but the acutal length is %d, the list: %+v", len(users), users)
}
if users2[0].Username != USERNAME {
t.Errorf("The username in result list does not match, expected: %s, actual: %s", USERNAME, users2[0].Username)
if users2[0].Username != username {
t.Errorf("The username in result list does not match, expected: %s, actual: %s", username, users2[0].Username)
}
}
func TestResetUserPassword(t *testing.T) {
uuid, err := dao.GenerateRandomString()
uuid, err := GenerateRandomString()
if err != nil {
t.Errorf("Error occurred in GenerateRandomString: %v", err)
}
err = dao.UpdateUserResetUuid(models.User{ResetUuid: uuid, Email: currentUser.Email})
err = UpdateUserResetUUID(models.User{ResetUUID: uuid, Email: currentUser.Email})
if err != nil {
t.Errorf("Error occurred in UpdateUserResetUuid: %v", err)
}
err = dao.ResetUserPassword(models.User{UserId: currentUser.UserId, Password: "HarborTester12345", ResetUuid: uuid, Salt: currentUser.Salt})
err = ResetUserPassword(models.User{UserID: currentUser.UserID, Password: "HarborTester12345", ResetUUID: uuid, Salt: currentUser.Salt})
if err != nil {
t.Errorf("Error occurred in ResetUserPassword: %v", err)
}
loginedUser, err := dao.LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "HarborTester12345"})
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "HarborTester12345"})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
if loginedUser.Username != USERNAME {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", USERNAME, loginedUser.Username)
if loginedUser.Username != username {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", username, loginedUser.Username)
}
}
func TestChangeUserPassword(t *testing.T) {
err := dao.ChangeUserPassword(models.User{UserId: currentUser.UserId, Password: "NewHarborTester12345", Salt: currentUser.Salt})
err := ChangeUserPassword(models.User{UserID: currentUser.UserID, Password: "NewHarborTester12345", Salt: currentUser.Salt})
if err != nil {
t.Errorf("Error occurred in ChangeUserPassword: %v", err)
}
loginedUser, err := dao.LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NewHarborTester12345"})
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NewHarborTester12345"})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
if loginedUser.Username != USERNAME {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", USERNAME, loginedUser.Username)
if loginedUser.Username != username {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", username, loginedUser.Username)
}
}
func TestChangeUserPasswordWithOldPassword(t *testing.T) {
err := dao.ChangeUserPassword(models.User{UserId: currentUser.UserId, Password: "NewerHarborTester12345", Salt: currentUser.Salt}, "NewHarborTester12345")
err := ChangeUserPassword(models.User{UserID: currentUser.UserID, Password: "NewerHarborTester12345", Salt: currentUser.Salt}, "NewHarborTester12345")
if err != nil {
t.Errorf("Error occurred in ChangeUserPassword: %v", err)
}
loginedUser, err := dao.LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NewerHarborTester12345"})
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NewerHarborTester12345"})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
if loginedUser.Username != USERNAME {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", USERNAME, loginedUser.Username)
if loginedUser.Username != username {
t.Errorf("The username returned by Login does not match, expected: %s, acutal: %s", username, loginedUser.Username)
}
}
func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
err := dao.ChangeUserPassword(models.User{UserId: currentUser.UserId, Password: "NNewerHarborTester12345", Salt: currentUser.Salt}, "WrongNewerHarborTester12345")
err := ChangeUserPassword(models.User{UserID: currentUser.UserID, Password: "NNewerHarborTester12345", Salt: currentUser.Salt}, "WrongNewerHarborTester12345")
if err == nil {
t.Errorf("Error does not occurred due to old password is incorrect.")
}
loginedUser, err := dao.LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NNewerHarborTester12345"})
loginedUser, err := LoginByDb(models.AuthModel{Principal: currentUser.Username, Password: "NNewerHarborTester12345"})
if err != nil {
t.Errorf("Error occurred in LoginByDb: %v", err)
}
@ -332,7 +328,7 @@ func TestChangeUserPasswordWithIncorrectOldPassword(t *testing.T) {
}
func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
projects, err := dao.QueryRelevantProjects(currentUser.UserId)
projects, err := QueryRelevantProjects(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
}
@ -347,23 +343,23 @@ func TestQueryRelevantProjectsWhenNoProjectAdded(t *testing.T) {
func TestAddProject(t *testing.T) {
project := models.Project{
OwnerId: currentUser.UserId,
Name: PROJECT_NAME,
OwnerID: currentUser.UserID,
Name: projectName,
CreationTime: time.Now(),
OwnerName: currentUser.Username,
}
err := dao.AddProject(project)
err := AddProject(project)
if err != nil {
t.Errorf("Error occurred in AddProject: %v", err)
}
newProject, err := dao.GetProjectByName(PROJECT_NAME)
newProject, err := GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if newProject == nil {
t.Errorf("No project found queried by project name: %v", PROJECT_NAME)
t.Errorf("No project found queried by project name: %v", projectName)
}
}
@ -371,25 +367,25 @@ var currentProject *models.Project
func TestGetProject(t *testing.T) {
var err error
currentProject, err = dao.GetProjectByName(PROJECT_NAME)
currentProject, err = GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject == nil {
t.Errorf("No project found queried by project name: %v", PROJECT_NAME)
t.Errorf("No project found queried by project name: %v", projectName)
}
if currentProject.Name != PROJECT_NAME {
t.Errorf("Project name does not match, expected: %s, actual: %s", PROJECT_NAME, currentProject.Name)
if currentProject.Name != projectName {
t.Errorf("Project name does not match, expected: %s, actual: %s", projectName, currentProject.Name)
}
}
func getProjectRole(projectId int64) []models.Role {
func getProjectRole(projectID int64) []models.Role {
o := orm.NewOrm()
var r []models.Role
_, err := o.Raw(`select r.role_id, r.name
from project_role pr
left join role r on pr.role_id = r.role_id
where project_id = ?`, projectId).QueryRows(&r)
where project_id = ?`, projectID).QueryRows(&r)
if err != nil {
log.Printf("Error occurred in querying project_role: %v", err)
}
@ -397,12 +393,12 @@ func getProjectRole(projectId int64) []models.Role {
}
func TestCheckProjectRoles(t *testing.T) {
r := getProjectRole(currentProject.ProjectId)
r := getProjectRole(currentProject.ProjectID)
if len(r) != 3 {
t.Errorf("The length of project roles is not 3")
}
if r[1].RoleId != 3 {
t.Errorf("The role id does not match, expected: 3, acutal: %d", r[1].RoleId)
if r[1].RoleID != 3 {
t.Errorf("The role id does not match, expected: 3, acutal: %d", r[1].RoleID)
}
if r[1].Name != "developer" {
t.Errorf("The name of role id: 3 should be developer, actual:%s", r[1].Name)
@ -411,32 +407,32 @@ func TestCheckProjectRoles(t *testing.T) {
func TestGetAccessLog(t *testing.T) {
queryAccessLog := models.AccessLog{
UserId: currentUser.UserId,
ProjectId: currentProject.ProjectId,
UserID: currentUser.UserID,
ProjectID: currentProject.ProjectID,
}
accessLogs, err := dao.GetAccessLogs(queryAccessLog)
accessLogs, err := GetAccessLogs(queryAccessLog)
if err != nil {
t.Errorf("Error occurred in GetAccessLog: %v", err)
}
if len(accessLogs) != 1 {
t.Errorf("The length of accesslog list should be 1, actual: %d", len(accessLogs))
}
if accessLogs[0].RepoName != PROJECT_NAME+"/" {
t.Errorf("The project name does not match, expected: %s, actual: %s", PROJECT_NAME+"/", accessLogs[0].RepoName)
if accessLogs[0].RepoName != projectName+"/" {
t.Errorf("The project name does not match, expected: %s, actual: %s", projectName+"/", accessLogs[0].RepoName)
}
}
func TestProjectExists(t *testing.T) {
var exists bool
var err error
exists, err = dao.ProjectExists(currentProject.ProjectId)
exists, err = ProjectExists(currentProject.ProjectID)
if err != nil {
t.Errorf("Error occurred in ProjectExists: %v", err)
}
if !exists {
t.Errorf("The project with id: %d, does not exist", currentProject.ProjectId)
t.Errorf("The project with id: %d, does not exist", currentProject.ProjectID)
}
exists, err = dao.ProjectExists(currentProject.Name)
exists, err = ProjectExists(currentProject.Name)
if err != nil {
t.Errorf("Error occurred in ProjectExists: %v", err)
}
@ -445,63 +441,154 @@ func TestProjectExists(t *testing.T) {
}
}
func TestToggleProjectPublicity(t *testing.T) {
err := dao.ToggleProjectPublicity(currentProject.ProjectId, PUBLICITY_ON)
func TestGetProjectById(t *testing.T) {
id := currentProject.ProjectID
p, err := GetProjectByID(id)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
t.Errorf("Error in GetProjectById: %v, id: %d", err, id)
}
currentProject, err = dao.GetProjectByName(PROJECT_NAME)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != PUBLICITY_ON {
t.Errorf("project, id: %d, its publicity is not on", currentProject.ProjectId)
}
err = dao.ToggleProjectPublicity(currentProject.ProjectId, PUBLICITY_OFF)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
currentProject, err = dao.GetProjectByName(PROJECT_NAME)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != PUBLICITY_OFF {
t.Errorf("project, id: %d, its publicity is not off", currentProject.ProjectId)
if p.Name != currentProject.Name {
t.Errorf("project name does not match, expected: %s, actual: %s", currentProject.Name, p.Name)
}
}
func getUserProjectRole(projectId int64, userId int) []models.Role {
func TestGetUserByProject(t *testing.T) {
pid := currentProject.ProjectID
u1 := models.User{
Username: "%%Tester%%",
}
u2 := models.User{
Username: "nononono",
}
users, err := GetUserByProject(pid, u1)
if err != nil {
t.Errorf("Error happened in GetUserByProject: %v, project Id: %d, user: %+v", err, pid, u1)
}
if len(users) != 1 {
t.Errorf("unexpected length of user list, expected: 1, the users list: %+v", users)
}
users, err = GetUserByProject(pid, u2)
if err != nil {
t.Errorf("Error happened in GetUserByProject: %v, project Id: %d, user: %+v", err, pid, u2)
}
if len(users) != 0 {
t.Errorf("unexpected length of user list, expected: 0, the users list: %+v", users)
}
}
func TestToggleProjectPublicity(t *testing.T) {
err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
currentProject, err = GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != publicityOn {
t.Errorf("project, id: %d, its publicity is not on", currentProject.ProjectID)
}
err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff)
if err != nil {
t.Errorf("Error occurred in ToggleProjectPublicity: %v", err)
}
currentProject, err = GetProjectByName(projectName)
if err != nil {
t.Errorf("Error occurred in GetProjectByName: %v", err)
}
if currentProject.Public != publicityOff {
t.Errorf("project, id: %d, its publicity is not off", currentProject.ProjectID)
}
}
func TestIsProjectPublic(t *testing.T) {
if isPublic := IsProjectPublic(projectName); isPublic {
t.Errorf("project, id: %d, its publicity is not false after turning off", currentProject.ProjectID)
}
}
func TestQueryProject(t *testing.T) {
query1 := models.Project{
UserID: 1,
}
projects, err := QueryProject(query1)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query1)
}
if len(projects) != 2 {
t.Errorf("Expecting get 2 projects, but actual: %d, the list: %+v", len(projects), projects)
}
query2 := models.Project{
Public: 1,
}
projects, err = QueryProject(query2)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query2)
}
if len(projects) != 1 {
t.Errorf("Expecting get 1 project, but actual: %d, the list: %+v", len(projects), projects)
}
query3 := models.Project{
UserID: 9,
}
projects, err = QueryProject(query3)
if err != nil {
t.Errorf("Error in Query Project: %v, query: %+v", err, query3)
}
if len(projects) != 0 {
t.Errorf("Expecting get 0 project, but actual: %d, the list: %+v", len(projects), projects)
}
}
func getUserProjectRole(projectID int64, userID int) []models.Role {
o := orm.NewOrm()
var r []models.Role
_, err := o.Raw(`select r.role_id, r.name
from user_project_role upr
left join project_role pr on upr.pr_id = pr.pr_id
left join role r on r.role_id = pr.role_id
where pr.project_id = ? and upr.user_id = ?`, projectId, userId).QueryRows(&r)
where pr.project_id = ? and upr.user_id = ?`, projectID, userID).QueryRows(&r)
if err != nil {
log.Fatalf("Error occurred in querying user_project_role: %v", err)
}
return r
}
func TestGetUserProjectRole(t *testing.T) {
r := getUserProjectRole(currentProject.ProjectId, currentUser.UserId)
func TestGetUserProjectRoles(t *testing.T) {
user := *currentUser
r, err := GetUserProjectRoles(user, currentProject.ProjectID)
if err != nil {
t.Errorf("Error happened in GetUserProjectRole: %v, user: %+v, project Id: %d", err, user, currentProject.ProjectID)
}
//Get the size of current user project role.
if len(r) != 1 {
t.Errorf("The user, id: %d, should only have one role in project, id: %d, but actual: %d", currentUser.UserId, currentProject.ProjectId, len(r))
t.Errorf("The user, id: %d, should only have one role in project, id: %d, but actual: %d", currentUser.UserID, currentProject.ProjectID, len(r))
}
if r[0].Name != "projectAdmin" {
t.Errorf("the expected rolename is: projectAdmin, actual: %s", r[0].Name)
}
user.RoleID = 1
r, err = GetUserProjectRoles(user, currentProject.ProjectID)
if err != nil {
t.Errorf("Error happened in GetUserProjectRole: %v, user: %+v, project Id: %d", err, user, currentProject.ProjectID)
}
//Get the size of current user project role.
if len(r) != 0 {
t.Errorf("The user, id: %d, should not have role id: 1 in project id: %d, actual role list: %v", currentUser.UserID, currentProject.ProjectID, r)
}
}
func TestProjectPermission(t *testing.T) {
roleCode, err := dao.GetPermission(currentUser.Username, currentProject.Name)
roleCode, err := GetPermission(currentUser.Username, currentProject.Name)
if err != nil {
t.Errorf("Error occurred in GetPermission: %v", err)
}
@ -511,59 +598,84 @@ func TestProjectPermission(t *testing.T) {
}
func TestQueryRelevantProjects(t *testing.T) {
projects, err := dao.QueryRelevantProjects(currentUser.UserId)
projects, err := QueryRelevantProjects(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in QueryRelevantProjects: %v", err)
}
if len(projects) != 2 {
t.Errorf("Expected length of relevant projects is 2, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[1].Name != PROJECT_NAME {
t.Errorf("Expected project name in the list: %s, actual: %s", PROJECT_NAME, projects[1].Name)
if projects[1].Name != projectName {
t.Errorf("Expected project name in the list: %s, actual: %s", projectName, projects[1].Name)
}
}
func TestAssignUserProjectRole(t *testing.T) {
err := dao.AddUserProjectRole(currentUser.UserId, currentProject.ProjectId, DEVELOPER)
err := AddUserProjectRole(currentUser.UserID, currentProject.ProjectID, developer)
if err != nil {
t.Errorf("Error occurred in AddUserProjectRole: %v", err)
}
r := getUserProjectRole(currentProject.ProjectId, currentUser.UserId)
r := getUserProjectRole(currentProject.ProjectID, currentUser.UserID)
//Get the size of current user project role info.
if len(r) != 2 {
t.Errorf("Expected length of role list is 2, actual: %d", len(r))
}
if r[1].RoleId != 3 {
t.Errorf("Expected role id of the second role in list is 3, actual: %d", r[1].RoleId)
if r[1].RoleID != 3 {
t.Errorf("Expected role id of the second role in list is 3, actual: %d", r[1].RoleID)
}
}
func TestDeleteUserProjectRole(t *testing.T) {
err := dao.DeleteUserProjectRoles(currentUser.UserId, currentProject.ProjectId)
err := DeleteUserProjectRoles(currentUser.UserID, currentProject.ProjectID)
if err != nil {
t.Errorf("Error occurred in DeleteUserProjectRoles: %v", err)
}
r := getUserProjectRole(currentProject.ProjectId, currentUser.UserId)
r := getUserProjectRole(currentProject.ProjectID, currentUser.UserID)
//Get the size of current user project role.
if len(r) != 0 {
t.Errorf("Expected role list length is 0, actual: %d, role list: %+v", len(r), r)
}
}
func TestToggleAdminRole(t *testing.T) {
err := ToggleUserAdminRole(*currentUser)
if err != nil {
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
}
isAdmin, err := IsAdminRole(currentUser.UserID)
if err != nil {
t.Errorf("Error in IsAdminRole: %v, user id: %d", err, currentUser.UserID)
}
if !isAdmin {
t.Errorf("User is not admin after toggled, user id: %d", currentUser.UserID)
}
err = ToggleUserAdminRole(*currentUser)
if err != nil {
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
}
isAdmin, err = IsAdminRole(currentUser.UserID)
if err != nil {
t.Errorf("Error in IsAdminRole: %v, user id: %d", err, currentUser.UserID)
}
if isAdmin {
t.Errorf("User is still admin after toggled, user id: %d", currentUser.UserID)
}
}
func TestDeleteUser(t *testing.T) {
err := dao.DeleteUser(currentUser.UserId)
err := DeleteUser(currentUser.UserID)
if err != nil {
t.Errorf("Error occurred in DeleteUser: %v", err)
}
user, err := dao.GetUser(*currentUser)
user, err := GetUser(*currentUser)
if err != nil {
t.Errorf("Error occurred in GetUser: %v", err)
}
if user != nil {
t.Error("user is not nil after deletion, user: %+v", user)
t.Errorf("user is not nil after deletion, user: %+v", user)
}
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -20,7 +21,8 @@ import (
"github.com/astaxie/beego/orm"
)
func GetUserByProject(queryProject models.Project, queryUser models.User) ([]models.User, error) {
// GetUserByProject gets all members of the project.
func GetUserByProject(projectID int64, queryUser models.User) ([]models.User, error) {
o := orm.NewOrm()
u := []models.User{}
sql := `select
@ -35,14 +37,11 @@ func GetUserByProject(queryProject models.Project, queryUser models.User) ([]mod
and pr.project_id = ? `
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, queryProject.ProjectId)
queryParam = append(queryParam, projectID)
if queryUser.Username != "" {
sql += " and u.username like ? "
queryParam = append(queryParam, queryUser.Username)
} else if queryUser.RoleId != 0 {
sql += ` and r.role_id <= ? `
queryParam = append(queryParam, queryUser.RoleId)
}
sql += ` order by u.user_id `
_, err := o.Raw(sql, queryParam).QueryRows(&u)

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -26,6 +27,8 @@ import (
)
//TODO:transaction, return err
// AddProject adds a project to the database along with project roles information and access log records.
func AddProject(project models.Project) error {
if isIllegalLength(project.Name, 4, 30) {
@ -42,46 +45,47 @@ func AddProject(project models.Project) error {
return err
}
r, err := p.Exec(project.OwnerId, project.Name, project.Deleted, project.Public)
r, err := p.Exec(project.OwnerID, project.Name, project.Deleted, project.Public)
if err != nil {
return err
}
projectId, err := r.LastInsertId()
projectID, err := r.LastInsertId()
if err != nil {
return err
}
projectAdminRole := models.ProjectRole{ProjectId: projectId, RoleId: models.PROJECTADMIN}
projectAdminRole := models.ProjectRole{ProjectID: projectID, RoleID: models.PROJECTADMIN}
_, err = AddProjectRole(projectAdminRole)
if err != nil {
return err
}
projectDeveloperRole := models.ProjectRole{ProjectId: projectId, RoleId: models.DEVELOPER}
projectDeveloperRole := models.ProjectRole{ProjectID: projectID, RoleID: models.DEVELOPER}
_, err = AddProjectRole(projectDeveloperRole)
if err != nil {
return err
}
projectGuestRole := models.ProjectRole{ProjectId: projectId, RoleId: models.GUEST}
projectGuestRole := models.ProjectRole{ProjectID: projectID, RoleID: models.GUEST}
_, err = AddProjectRole(projectGuestRole)
if err != nil {
return err
}
//Add all project roles, after that when assigning a user to a project just update the upr table
err = AddUserProjectRole(project.OwnerId, projectId, models.PROJECTADMIN)
err = AddUserProjectRole(project.OwnerID, projectID, models.PROJECTADMIN)
if err != nil {
return err
}
accessLog := models.AccessLog{UserId: project.OwnerId, ProjectId: projectId, RepoName: project.Name + "/", Guid: "N/A", Operation: "create", OpTime: time.Now()}
accessLog := models.AccessLog{UserID: project.OwnerID, ProjectID: projectID, RepoName: project.Name + "/", GUID: "N/A", Operation: "create", OpTime: time.Now()}
err = AddAccessLog(accessLog)
return err
}
// IsProjectPublic ...
func IsProjectPublic(projectName string) bool {
project, err := GetProjectByName(projectName)
if err != nil {
@ -94,6 +98,7 @@ func IsProjectPublic(projectName string) bool {
return project.Public == 1
}
// QueryProject querys the projects based on publicity and user, disregarding the names etc.
func QueryProject(query models.Project) ([]models.Project, error) {
o := orm.NewOrm()
@ -110,10 +115,10 @@ func QueryProject(query models.Project) ([]models.Project, error) {
if query.Public == 1 {
sql += ` and p.public = ?`
queryParam = append(queryParam, query.Public)
} else if isAdmin, _ := IsAdminRole(query.UserId); isAdmin == false {
} else if isAdmin, _ := IsAdminRole(query.UserID); isAdmin == false {
sql += ` and (p.owner_id = ? or u.user_id = ?) `
queryParam = append(queryParam, query.UserId)
queryParam = append(queryParam, query.UserId)
queryParam = append(queryParam, query.UserID)
queryParam = append(queryParam, query.UserID)
}
if query.Name != "" {
@ -132,21 +137,22 @@ func QueryProject(query models.Project) ([]models.Project, error) {
return r, nil
}
func ProjectExists(nameOrId interface{}) (bool, error) {
//ProjectExists returns whether the project exists according to its name of ID.
func ProjectExists(nameOrID interface{}) (bool, error) {
o := orm.NewOrm()
type dummy struct{}
sql := `select project_id from project where deleted = 0 and `
switch nameOrId.(type) {
switch nameOrID.(type) {
case int64:
sql += `project_id = ?`
case string:
sql += `name = ?`
default:
return false, errors.New(fmt.Sprintf("Invalid nameOrId: %v", nameOrId))
return false, fmt.Errorf("Invalid nameOrId: %v", nameOrID)
}
var d []dummy
num, err := o.Raw(sql, nameOrId).QueryRows(&d)
num, err := o.Raw(sql, nameOrID).QueryRows(&d)
if err != nil {
return false, err
}
@ -154,17 +160,14 @@ func ProjectExists(nameOrId interface{}) (bool, error) {
}
func GetProjectById(query models.Project) (*models.Project, error) {
// GetProjectByID ...
func GetProjectByID(projectID int64) (*models.Project, error) {
o := orm.NewOrm()
sql := `select p.project_id, p.name, u.username as owner_name, p.owner_id, p.creation_time, p.public
from project p left join user u on p.owner_id = u.user_id where p.deleted = 0 and p.project_id = ?`
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, query.ProjectId)
if query.Public != 0 {
sql += " and p.public = ? "
queryParam = append(queryParam, query.Public)
}
queryParam = append(queryParam, projectID)
p := []models.Project{}
count, err := o.Raw(sql, queryParam).QueryRows(&p)
@ -178,6 +181,7 @@ func GetProjectById(query models.Project) (*models.Project, error) {
}
}
// GetProjectByName ...
func GetProjectByName(projectName string) (*models.Project, error) {
o := orm.NewOrm()
var p []models.Project
@ -191,6 +195,7 @@ func GetProjectByName(projectName string) (*models.Project, error) {
}
}
// GetPermission gets roles that the user has according to the project.
func GetPermission(username, projectName string) (string, error) {
o := orm.NewOrm()
@ -212,21 +217,23 @@ func GetPermission(username, projectName string) (string, error) {
}
}
func ToggleProjectPublicity(projectId int64, publicity int) error {
// ToggleProjectPublicity toggles the publicity of the project.
func ToggleProjectPublicity(projectID int64, publicity int) error {
o := orm.NewOrm()
sql := "update project set public = ? where project_id = ?"
_, err := o.Raw(sql, publicity, projectId).Exec()
_, err := o.Raw(sql, publicity, projectID).Exec()
return err
}
func QueryRelevantProjects(userId int) ([]models.Project, error) {
// QueryRelevantProjects returns all projects that the user is a member of.
func QueryRelevantProjects(userID int) ([]models.Project, error) {
o := orm.NewOrm()
sql := `SELECT distinct p.project_id, p.name, p.public FROM registry.project p
left join project_role pr on p.project_id = pr.project_id
left join user_project_role upr on upr.pr_id = pr.pr_id
where upr.user_id = ? or p.public = 1 and p.deleted = 0`
var res []models.Project
_, err := o.Raw(sql, userId).QueryRows(&res)
_, err := o.Raw(sql, userID).QueryRows(&res)
if err != nil {
return nil, err
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -20,6 +21,7 @@ import (
"github.com/astaxie/beego/orm"
)
// AddProjectRole ...
func AddProjectRole(projectRole models.ProjectRole) (int64, error) {
o := orm.NewOrm()
p, err := o.Raw("insert into project_role (project_id, role_id) values (?, ?)").Prepare()
@ -27,7 +29,7 @@ func AddProjectRole(projectRole models.ProjectRole) (int64, error) {
return 0, err
}
defer p.Close()
r, err := p.Exec(projectRole.ProjectId, projectRole.RoleId)
r, err := p.Exec(projectRole.ProjectID, projectRole.RoleID)
if err != nil {
return 0, err
}
@ -35,16 +37,17 @@ func AddProjectRole(projectRole models.ProjectRole) (int64, error) {
return id, err
}
func AddUserProjectRole(userId int, projectId int64, roleId int) error {
// AddUserProjectRole inserts role information to table project_role and user_project_role.
func AddUserProjectRole(userID int, projectID int64, roleID int) error {
o := orm.NewOrm()
var pr []models.ProjectRole
var prId int
var prID int
sql := `select pr.pr_id, pr.project_id, pr.role_id from project_role pr where pr.project_id = ? and pr.role_id = ?`
n, err := o.Raw(sql, projectId, roleId).QueryRows(&pr)
n, err := o.Raw(sql, projectID, roleID).QueryRows(&pr)
if err != nil {
return err
}
@ -55,7 +58,7 @@ func AddUserProjectRole(userId int, projectId int64, roleId int) error {
return err
}
defer p.Close()
r, err := p.Exec(projectId, roleId)
r, err := p.Exec(projectID, roleID)
if err != nil {
return err
}
@ -63,20 +66,21 @@ func AddUserProjectRole(userId int, projectId int64, roleId int) error {
if err != nil {
return err
}
prId = int(id)
prID = int(id)
} else if n > 0 {
prId = pr[0].PrId
prID = pr[0].PrID
}
p, err := o.Raw("insert into user_project_role (user_id, pr_id) values (?, ?)").Prepare()
if err != nil {
return err
}
defer p.Close()
_, err = p.Exec(userId, prId)
_, err = p.Exec(userID, prID)
return err
}
func DeleteUserProjectRoles(userId int, projectId int64) error {
// DeleteUserProjectRoles ...
func DeleteUserProjectRoles(userID int, projectID int64) error {
o := orm.NewOrm()
sql := `delete from user_project_role where user_id = ? and pr_id in
(select pr_id from project_role where project_id = ?)`
@ -84,6 +88,6 @@ func DeleteUserProjectRoles(userId int, projectId int64) error {
if err != nil {
return err
}
_, err = p.Exec(userId, projectId)
_, err = p.Exec(userID, projectID)
return err
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -24,6 +25,7 @@ import (
"github.com/astaxie/beego/orm"
)
// Register is used for user to register, the password is encrypted before the record is inserted into database.
func Register(user models.User) (int64, error) {
err := validate(user)
@ -48,12 +50,12 @@ func Register(user models.User) (int64, error) {
if err != nil {
return 0, err
}
userId, err := r.LastInsertId()
userID, err := r.LastInsertId()
if err != nil {
return 0, err
}
return userId, nil
return userID, nil
}
func validate(user models.User) error {
@ -99,6 +101,7 @@ func validate(user models.User) error {
return nil
}
// UserExists returns whether a user exists according username or Email.
func UserExists(user models.User, target string) (bool, error) {
if user.Username == "" && user.Email == "" {

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -20,7 +21,8 @@ import (
"github.com/astaxie/beego/orm"
)
func GetUserProjectRoles(userQuery models.User, projectId int64) ([]models.Role, error) {
// GetUserProjectRoles returns roles that the user has according to the project.
func GetUserProjectRoles(userQuery models.User, projectID int64) ([]models.Role, error) {
o := orm.NewOrm()
@ -32,15 +34,15 @@ func GetUserProjectRoles(userQuery models.User, projectId int64) ([]models.Role,
where u.deleted = 0
and u.user_id = ? `
queryParam := make([]interface{}, 1)
queryParam = append(queryParam, userQuery.UserId)
queryParam = append(queryParam, userQuery.UserID)
if projectId > 0 {
if projectID > 0 {
sql += ` and pr.project_id = ? `
queryParam = append(queryParam, projectId)
queryParam = append(queryParam, projectID)
}
if userQuery.RoleId > 0 {
if userQuery.RoleID > 0 {
sql += ` and r.role_id = ? `
queryParam = append(queryParam, userQuery.RoleId)
queryParam = append(queryParam, userQuery.RoleID)
}
var roleList []models.Role
@ -52,9 +54,10 @@ func GetUserProjectRoles(userQuery models.User, projectId int64) ([]models.Role,
return roleList, nil
}
func IsAdminRole(userId int) (bool, error) {
// IsAdminRole returns whether the user is admin.
func IsAdminRole(userID int) (bool, error) {
//role_id == 1 means the user is system admin
userQuery := models.User{UserId: userId, RoleId: models.SYSADMIN}
userQuery := models.User{UserID: userID, RoleID: models.SYSADMIN}
adminRoleList, err := GetUserProjectRoles(userQuery, 0)
if err != nil {
return false, err

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
@ -25,6 +26,7 @@ import (
"github.com/astaxie/beego/orm"
)
// GetUser ...
func GetUser(query models.User) (*models.User, error) {
o := orm.NewOrm()
@ -38,9 +40,9 @@ func GetUser(query models.User) (*models.User, error) {
from user u
where deleted = 0 `
queryParam := make([]interface{}, 1)
if query.UserId != 0 {
if query.UserID != 0 {
sql += ` and user_id = ? `
queryParam = append(queryParam, query.UserId)
queryParam = append(queryParam, query.UserID)
}
if query.Username != "" {
@ -48,9 +50,9 @@ func GetUser(query models.User) (*models.User, error) {
queryParam = append(queryParam, query.Username)
}
if query.ResetUuid != "" {
if query.ResetUUID != "" {
sql += ` and reset_uuid = ? `
queryParam = append(queryParam, query.ResetUuid)
queryParam = append(queryParam, query.ResetUUID)
}
var u []models.User
@ -65,6 +67,7 @@ func GetUser(query models.User) (*models.User, error) {
}
}
// LoginByDb is used for user to login with database auth mode.
func LoginByDb(auth models.AuthModel) (*models.User, error) {
query := models.User{Username: auth.Principal, Email: auth.Principal}
@ -84,6 +87,7 @@ func LoginByDb(auth models.AuthModel) (*models.User, error) {
}
// ListUsers lists all users according to different conditions.
func ListUsers(query models.User) ([]models.User, error) {
o := orm.NewOrm()
u := []models.User{}
@ -106,15 +110,16 @@ func ListUsers(query models.User) ([]models.User, error) {
return u, err
}
// ToggleUserAdminRole gives a user admim role.
func ToggleUserAdminRole(u models.User) error {
projectRole := models.ProjectRole{PrId: 1} //admin project role
projectRole := models.ProjectRole{PrID: 1} //admin project role
o := orm.NewOrm()
var pr []models.ProjectRole
n, err := o.Raw(`select user_id from user_project_role where user_id = ? and pr_id = ? `, u.UserId, projectRole.PrId).QueryRows(&pr)
n, err := o.Raw(`select user_id from user_project_role where user_id = ? and pr_id = ? `, u.UserID, projectRole.PrID).QueryRows(&pr)
if err != nil {
return err
}
@ -131,20 +136,21 @@ func ToggleUserAdminRole(u models.User) error {
return err
}
defer p.Close()
_, err = p.Exec(u.UserId, projectRole.PrId)
_, err = p.Exec(u.UserID, projectRole.PrID)
return err
}
// ChangeUserPassword ...
func ChangeUserPassword(u models.User, oldPassword ...string) error {
o := orm.NewOrm()
var err error
var r sql.Result
if len(oldPassword) == 0 {
//In some cases, it may no need to check old password, just as Linux change password policies.
_, err = o.Raw(`update user set password=?, salt=? where user_id=?`, utils.Encrypt(u.Password, u.Salt), u.Salt, u.UserId).Exec()
_, err = o.Raw(`update user set password=?, salt=? where user_id=?`, utils.Encrypt(u.Password, u.Salt), u.Salt, u.UserID).Exec()
} else if len(oldPassword) == 1 {
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, u.Salt), u.Salt, u.UserID, utils.Encrypt(oldPassword[0], u.Salt)).Exec()
if err != nil {
return err
}
@ -161,9 +167,10 @@ func ChangeUserPassword(u models.User, oldPassword ...string) error {
return err
}
// ResetUserPassword ...
func ResetUserPassword(u models.User) error {
o := orm.NewOrm()
r, err := o.Raw(`update user set password=?, reset_uuid=? where reset_uuid=?`, utils.Encrypt(u.Password, u.Salt), "", u.ResetUuid).Exec()
r, err := o.Raw(`update user set password=?, reset_uuid=? where reset_uuid=?`, utils.Encrypt(u.Password, u.Salt), "", u.ResetUUID).Exec()
if err != nil {
return err
}
@ -177,12 +184,14 @@ func ResetUserPassword(u models.User) error {
return err
}
func UpdateUserResetUuid(u models.User) error {
// UpdateUserResetUUID ...
func UpdateUserResetUUID(u models.User) error {
o := orm.NewOrm()
_, err := o.Raw(`update user set reset_uuid=? where email=?`, u.ResetUuid, u.Email).Exec()
_, err := o.Raw(`update user set reset_uuid=? where email=?`, u.ResetUUID, u.Email).Exec()
return err
}
// CheckUserPassword checks whether the password is correct.
func CheckUserPassword(query models.User) (*models.User, error) {
currentUser, err := GetUser(query)
@ -199,10 +208,10 @@ func CheckUserPassword(query models.User) (*models.User, error) {
queryParam := make([]interface{}, 1)
if query.UserId != 0 {
if query.UserID != 0 {
sql += ` and password = ? and user_id = ?`
queryParam = append(queryParam, utils.Encrypt(query.Password, currentUser.Salt))
queryParam = append(queryParam, query.UserId)
queryParam = append(queryParam, query.UserID)
} else {
sql += ` and username = ? and password = ?`
queryParam = append(queryParam, currentUser.Username)
@ -223,8 +232,9 @@ func CheckUserPassword(query models.User) (*models.User, error) {
}
}
func DeleteUser(userId int) error {
// DeleteUser ...
func DeleteUser(userID int) error {
o := orm.NewOrm()
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userId).Exec()
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
return err
}

26
main.go
View File

@ -12,17 +12,17 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"errors"
"fmt"
"log"
_ "github.com/vmware/harbor/auth/db"
_ "github.com/vmware/harbor/auth/ldap"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
_ "github.com/vmware/harbor/opt_auth/db"
_ "github.com/vmware/harbor/opt_auth/ldap"
_ "github.com/vmware/harbor/routers"
"os"
@ -31,19 +31,19 @@ import (
)
const (
ADMIN_USER_ID = 1
adminUserID = 1
)
func updateInitPassword(userId int, password string) error {
queryUser := models.User{UserId: userId}
func updateInitPassword(userID int, password string) error {
queryUser := models.User{UserID: userID}
user, err := dao.GetUser(queryUser)
if err != nil {
log.Println("Failed to get user, userId:", userId)
log.Println("Failed to get user, userID:", userID)
return err
}
if user == nil {
log.Printf("User id: %d does not exist.", userId)
return errors.New(fmt.Sprintf("User id: %s does not exist.", userId))
log.Printf("User id: %d does not exist.", userID)
return fmt.Errorf("User id: %d does not exist.", userID)
} else if user.Salt == "" {
salt, err := dao.GenerateRandomString()
if err != nil {
@ -54,12 +54,12 @@ func updateInitPassword(userId int, password string) error {
user.Password = password
err = dao.ChangeUserPassword(*user)
if err != nil {
log.Printf("Failed to update user encrypted password, userId: %d, err: %v", userId, err)
log.Printf("Failed to update user encrypted password, userID: %d, err: %v", userID, err)
return err
}
log.Printf("User id: %d updated its encypted password successfully.", userId)
log.Printf("User id: %d updated its encypted password successfully.", userID)
} else {
log.Printf("User id: %d already has its encrypted password.", userId)
log.Printf("User id: %d already has its encrypted password.", userID)
}
return nil
}
@ -68,6 +68,6 @@ func main() {
beego.BConfig.WebConfig.Session.SessionOn = true
dao.InitDB()
updateInitPassword(ADMIN_USER_ID, os.Getenv("HARBOR_ADMIN_PASSWORD"))
updateInitPassword(adminUserID, os.Getenv("HARBOR_ADMIN_PASSWORD"))
beego.Run()
}

View File

@ -12,20 +12,22 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package models
import (
"time"
)
// AccessLog holds information about logs which are used to record the actions that user take to the resourses.
type AccessLog struct {
LogId int
UserId int
ProjectId int64
RepoName string
Guid string
Operation string
OpTime time.Time
LogID int `orm:"column(log_id)" json:"LogId"`
UserID int `orm:"column(user_id)" json:"UserId"`
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
RepoName string `orm:"column(repo_name)"`
GUID string `orm:"column(GUID)" json:"Guid"`
Operation string `orm:"column(operation)"`
OpTime time.Time `orm:"column(op_time)"`
Username string
Keywords string

View File

@ -1,20 +0,0 @@
/*
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.
*/
package models
type AuthModel struct {
Principal string
Password string
}

View File

@ -12,4 +12,11 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
package models
// AuthModel holds information used to authenticate.
type AuthModel struct {
Principal string
Password string
}

View File

@ -1,29 +1,32 @@
/*
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.
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.
*/
package models
import (
"time"
)
// Notification holds all events.
type Notification struct {
Events []Event
}
// Event holds the details of a event.
type Event struct {
Id string
ID string `json:"Id"`
TimeStamp time.Time
Action string
Target *Target
@ -31,19 +34,22 @@ type Event struct {
Actor *Actor
}
// Target holds information about the target of a event.
type Target struct {
MediaType string
Digest string
Repository string
Url string
URL string `json:"Url"`
}
// Actor holds information about actor.
type Actor struct {
Name string
}
// Request holds information about a request.
type Request struct {
Id string
ID string `json:"Id"`
Method string
UserAgent string
}

View File

@ -1,33 +1,35 @@
/*
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.
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.
*/
package models
import (
"time"
)
// Project holds the details of a project.
type Project struct {
ProjectId int64
OwnerId int
Name string
CreationTime time.Time
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
OwnerID int `orm:"column(owner_id)" json:"OwnerId"`
Name string `orm:"column(name)"`
CreationTime time.Time `orm:"column(creation_time)"`
CreationTimeStr string
Deleted int
UserId int
Deleted int `orm:"column(deleted)"`
UserID int `json:"UserId"`
OwnerName string
Public int
Public int `orm:"column(public)"`
//This field does not have correspondent column in DB, this is just for UI to disable button
Togglable bool
}

View File

@ -1,21 +0,0 @@
/*
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.
*/
package models
type ProjectRole struct {
PrId int
ProjectId int64
RoleId int
}

View File

@ -1,25 +1,45 @@
/*
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.
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.
*/
package models
type V1Repo struct {
NumResults int
Query string
Results []RepoItem
import (
"time"
)
// Repo holds information about repositories.
type Repo struct {
Repositories []string `json:"repositories"`
}
type Repo struct {
Repositories []string `json:repositories`
// RepoItem holds manifest of an image.
type RepoItem struct {
ID string `json:"Id"`
Parent string `json:"Parent"`
Created time.Time `json:"Created"`
CreatedStr string `json:"CreatedStr"`
DurationDays string `json:"Duration Days"`
Author string `json:"Author"`
Architecture string `json:"Architecture"`
DockerVersion string `json:"Docker Version"`
Os string `json:"OS"`
//Size int `json:"Size"`
}
// Tag holds information about a tag.
type Tag struct {
Version string `json:"version"`
ImageID string `json:"image_id"`
}

View File

@ -1,32 +0,0 @@
/*
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.
*/
package models
import (
"time"
)
type RepoItem struct {
Id string `json:"Id"`
Parent string `json:"Parent"`
Created time.Time `json:"Created"`
CreatedStr string `json:"CreatedStr"`
DurationDays string `json:"Duration Days"`
Author string `json:"Author"`
Architecture string `json:"Architecture"`
Docker_version string `json:"Docker Version"`
Os string `json:"OS"`
//Size int `json:"Size"`
}

View File

@ -1,28 +1,48 @@
/*
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.
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.
*/
package models
const (
SYSADMIN = 1
//SYSADMIN system administrator
SYSADMIN = 1
//PROJECTADMIN project administrator
PROJECTADMIN = 2
DEVELOPER = 3
GUEST = 4
//DEVELOPER developer
DEVELOPER = 3
//GUEST guest
GUEST = 4
)
// Role holds the details of a role.
type Role struct {
RoleId int `json:"role_id"`
RoleCode string `json:"role_code"`
Name string `json:"role_name"`
RoleID int `json:"role_id" orm:"column(role_id)"`
RoleCode string `json:"role_code" orm:"column(role_code)"`
Name string `json:"role_name" orm:"column(name)"`
}
// ProjectRole holds information about the relationship of project and role.
type ProjectRole struct {
PrID int `orm:"column(pr_id)" json:"PrId"`
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
RoleID int `orm:"column(role_id)" json:"RoleId"`
}
// UserProjectRole holds information about relationship of user, project and role.
type UserProjectRole struct {
UprID int `orm:"column(upr_id)" json:"UprId"`
UserID int `orm:"column(user_id)" json:"UserId"`
PrID int64 `orm:"column(pr_id)" json:"PrId"`
}

View File

@ -1,20 +0,0 @@
/*
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.
*/
package models
type Tag struct {
Version string `json:version`
ImageId string `json:image_id`
}

View File

@ -1,31 +1,33 @@
/*
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.
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.
*/
package models
// User holds the details of a user.
type User struct {
UserId int
Username string
Email string
Password string
Realname string
Comment string
Deleted int
UserID int `orm:"column(user_id)" json:"UserId"`
Username string `orm:"column(username)"`
Email string `orm:"column(email)"`
Password string `orm:"column(password)"`
Realname string `orm:"column(realname)"`
Comment string `orm:"column(comment)"`
Deleted int `orm:"column(deleted)"`
Rolename string
RoleId int
RoleID int `json:"RoleId"`
RoleList []Role
HasAdminRole int
ResetUuid string
Salt string
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
Salt string `orm:"column(salt)"`
}

View File

@ -1,21 +0,0 @@
/*
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.
*/
package models
type UserProjectRole struct {
UprId int
UserId int
PrId int64
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package routers
import (
@ -61,5 +62,5 @@ func init() {
//external service that hosted on harbor process:
beego.Router("/service/notifications", &service.NotificationHandler{})
beego.Router("/service/token", &service.AuthController{}, "get:Auth")
beego.Router("/service/token", &service.TokenHandler{})
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
@ -25,12 +26,14 @@ import (
"github.com/astaxie/beego"
)
// NotificationHandler handles request on /service/notifications/, which listens to registry's events.
type NotificationHandler struct {
beego.Controller
}
const MEDIA_TYPE_MANIFEST = "application/vnd.docker.distribution.manifest.v1+json"
const mediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json"
// Post handles POST request, and records audit log or refreshes cache based on event.
func (n *NotificationHandler) Post() {
var notification models.Notification
// log.Printf("Notification Handler triggered!\n")
@ -43,7 +46,7 @@ func (n *NotificationHandler) Post() {
}
var username, action, repo, project string
for _, e := range notification.Events {
if e.Target.MediaType == MEDIA_TYPE_MANIFEST && strings.HasPrefix(e.Request.UserAgent, "docker") {
if e.Target.MediaType == mediaTypeManifest && strings.HasPrefix(e.Request.UserAgent, "docker") {
username = e.Actor.Name
action = e.Action
repo = e.Target.Repository
@ -67,6 +70,7 @@ func (n *NotificationHandler) Post() {
}
// Render returns nil as it won't render any template.
func (n *NotificationHandler) Render() error {
return nil
}

View File

@ -12,14 +12,15 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
"log"
"net/http"
"github.com/vmware/harbor/auth"
"github.com/vmware/harbor/models"
"github.com/vmware/harbor/opt_auth"
svc_utils "github.com/vmware/harbor/service/utils"
"github.com/vmware/harbor/utils"
@ -27,12 +28,15 @@ import (
"github.com/docker/distribution/registry/auth/token"
)
type AuthController struct {
// TokenHandler handles request on /service/token, which is the auth provider for registry.
type TokenHandler struct {
beego.Controller
}
//handle request
func (a *AuthController) Auth() {
// Get handles GET request, it checks the http header for user credentials
// and parse service and scope based on docker registry v2 standard,
// checkes the permission agains local DB and generates jwt token.
func (a *TokenHandler) Get() {
request := a.Ctx.Request
@ -56,7 +60,7 @@ func (a *AuthController) Auth() {
a.serveToken(username, service, access)
}
func (a *AuthController) serveToken(username, service string, access []*token.ResourceActions) {
func (a *TokenHandler) serveToken(username, service string, access []*token.ResourceActions) {
writer := a.Ctx.ResponseWriter
//create token
rawToken, err := svc_utils.MakeToken(username, service, access)
@ -72,14 +76,14 @@ func (a *AuthController) serveToken(username, service string, access []*token.Re
}
func authenticate(principal, password string) bool {
user, err := opt_auth.Login(models.AuthModel{principal, password})
user, err := auth.Login(models.AuthModel{principal, password})
if err != nil {
log.Printf("Error occurred in UserLogin: %v", err)
return false
}
if user == nil {
return false
} else {
return true
}
return true
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
@ -36,6 +37,7 @@ const (
expiration = 5 //minute
)
// GetResourceActions ...
func GetResourceActions(scope string) []*token.ResourceActions {
var res []*token.ResourceActions
if scope == "" {
@ -50,55 +52,54 @@ func GetResourceActions(scope string) []*token.ResourceActions {
return res
}
//Try to modify the action list in access based on permission
//determine if the request needs to be authenticated.
//for details see:https://github.com/docker/docker/issues/15640
// FilterAccess modify the action list in access based on permission
// determine if the request needs to be authenticated.
func FilterAccess(username string, authenticated bool, a *token.ResourceActions) {
if a.Type == "registry" && a.Name == "catalog" {
return
} else {
//clear action list to assign to new acess element after perm check.
a.Actions = []string{}
if a.Type == "repository" {
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" {
exist, err := dao.ProjectExists(projectName)
if err != nil {
log.Printf("Error occurred in CheckExistProject: %v", err)
return
}
if exist {
permission = "RW"
} else {
permission = ""
log.Printf("project %s does not exist, set empty permission for admin", projectName)
}
}
//clear action list to assign to new acess element after perm check.
a.Actions = []string{}
if a.Type == "repository" {
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" {
exist, err := dao.ProjectExists(projectName)
if err != nil {
log.Printf("Error occurred in CheckExistProject: %v", err)
return
}
if exist {
permission = "RW"
} else {
permission, err = dao.GetPermission(username, projectName)
if err != nil {
log.Printf("Error occurred in GetPermission: %v", err)
return
}
permission = ""
log.Printf("project %s does not exist, set empty permission for admin", projectName)
}
} else {
permission, err = dao.GetPermission(username, projectName)
if err != nil {
log.Printf("Error occurred in GetPermission: %v", err)
return
}
}
if strings.Contains(permission, "W") {
a.Actions = append(a.Actions, "push")
}
if strings.Contains(permission, "R") || dao.IsProjectPublic(projectName) {
a.Actions = append(a.Actions, "pull")
}
}
if strings.Contains(permission, "W") {
a.Actions = append(a.Actions, "push")
}
if strings.Contains(permission, "R") || dao.IsProjectPublic(projectName) {
a.Actions = append(a.Actions, "pull")
}
}
log.Printf("current access, type: %s, name:%s, actions:%v \n", a.Type, a.Name, a.Actions)
}
log.Printf("current access, type: %s, name:%s, actions:%v \n", a.Type, a.Name, a.Actions)
}
//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) {
access := GetResourceActions(scope)
for _, a := range access {
@ -107,6 +108,7 @@ func GenTokenForUI(username, service, scope string) (string, error) {
return MakeToken(username, service, access)
}
// MakeToken makes a valid jwt token based on parms.
func MakeToken(username, service string, access []*token.ResourceActions) (string, error) {
pk, err := libtrust.LoadKeyFile(privateKey)
if err != nil {

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
@ -25,9 +26,10 @@ import (
"github.com/astaxie/beego/cache"
)
// Cache is the global cache in system.
var Cache cache.Cache
const CATALOG string = "catalog"
const catalogKey string = "catalog"
func init() {
var err error
@ -37,8 +39,9 @@ func init() {
}
}
// RefreshCatalogCache calls registry's API to get repository list and write it to cache.
func RefreshCatalogCache() error {
result, err := RegistryApiGet(BuildRegistryUrl("_catalog"), "")
result, err := RegistryAPIGet(BuildRegistryURL("_catalog"), "")
if err != nil {
return err
}
@ -47,19 +50,20 @@ func RefreshCatalogCache() error {
if err != nil {
return err
}
Cache.Put(CATALOG, repoResp.Repositories, 600*time.Second)
Cache.Put(catalogKey, repoResp.Repositories, 600*time.Second)
return nil
}
// GetRepoFromCache get repository list from cache, it refreshes the cache if it's empty.
func GetRepoFromCache() ([]string, error) {
result := Cache.Get(CATALOG)
result := Cache.Get(catalogKey)
if result == nil {
err := RefreshCatalogCache()
if err != nil {
return nil, err
}
cached := Cache.Get(CATALOG)
cached := Cache.Get(catalogKey)
if cached != nil {
return cached.([]string), nil
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
@ -24,7 +25,8 @@ import (
"strings"
)
func BuildRegistryUrl(segments ...string) string {
// BuildRegistryURL ...
func BuildRegistryURL(segments ...string) string {
registryURL := os.Getenv("REGISTRY_URL")
if registryURL == "" {
registryURL = "http://localhost:5000"
@ -40,7 +42,9 @@ func BuildRegistryUrl(segments ...string) string {
return url
}
func RegistryApiGet(url, username string) ([]byte, error) {
// RegistryAPIGet triggers GET request to the URL which is the endpoint of registry and returns the response body.
// It will attach a valid jwt token to the request if registry requires.
func RegistryAPIGet(url, username string) ([]byte, error) {
response, err := http.Get(url)
if err != nil {
return nil, err

View File

@ -1,8 +1,8 @@
page_title_index = Harbor
page_title_sign_in = Sign In - Harbor
page_title_project = Project - Harbor
page_title_item_details = Item Details - Harbor
page_title_registration = Registration - Harbor
page_title_item_details = Details - Harbor
page_title_registration = Sign Up - Harbor
page_title_forgot_password = Forgot Password - Harbor
title_forgot_password = Forgot Password
page_title_reset_password = Reset Password - Harbor
@ -13,7 +13,7 @@ page_title_search = Search - Harbor
sign_in = Sign In
sign_up = Sign Up
log_out = Log Out
search_placeholder = Search for projects or repositories
search_placeholder = projects or repositories
change_password = Change Password
username_email = Username/Email
password = Password
@ -26,26 +26,26 @@ project_name = Project Name
creation_time = Creation Time
publicity = Publicity
add_project = Add Project
check_for_publicity = Check this checkbox and the project will be public.
check_for_publicity = Public project
button_save = Save
button_close = Close
button_cancel = Cancel
button_submit = Submit
username = Username
email = Email
system_admin = System Admin
dlg_button_ok = OK
dlg_button_cancel = Cancel
registration = Registration
registration = Sign Up
username_description = This will be your username.
email_description = You will occasionally receive account related emails. We promise not to share your email with anyone.
email_description = The Email address will be used for resetting password.
full_name = Full Name
full_name_description = First name & Last name.
password_description = Use more than seven characters with at least one lowercase letter, one capital letter and one numeral.
password_description = At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.
confirm_password = Confirm Password
note_to_the_admin = Notes to the Administrator
note_to_the_admin = Comments
old_password = Old Password
new_password = New Password
forgot_password_description = Please input the email for your account, and a link to reset password will be sent.
forgot_password_description = Please input the Email used when you signed up, a reset password Email will be sent to you.
projects = Projects
repositories = Repositories
@ -58,27 +58,26 @@ user = Users
logs = Logs
repo_name = Repository Name
add_members = Add Members
role_info = Role Info
operation = Operation
advance = Advance
advance = Advanced Search
all = All
others = Others
start_date = Start Date
end_date = End Date
timestamp = Timestamp
role = Role
reset_email_hint = Please use this link to reset your password
reset_email_hint = Please click this link to reset your password
reset_email_subject = Reset your password
language = English
language_en-US = English
language_zh-CN = 中文
copyright = Copyright
all_rights_reserved = All rights reserved.
container_registry_management = Container Registry Management
index_desc = Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment.
index_desc_0 = Key benefits of Project Harbor:
index_desc_0 = Key benefits:
index_desc_1 = 1. Security: Keep their intellectual properties within their organizations.
index_desc_2 = 2. Efficiency: A private registry server is set up within the organization's network and can reduce significantly the internet traffic to the public service.
index_desc_3 = 3. Access Control: RBAC(Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP.
index_desc_3 = 3. Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP.
index_desc_4 = 4. Audit: All access to the registry are logged and can be used for audit purpose.
index_desc_5 = 5. GUI: User friendly single-pane-of-glass management console.

View File

@ -14,92 +14,92 @@
*/
var global_messages = {
"username_is_required" : {
"en-US": "Username is required!",
"en-US": "Username is required.",
"zh-CN": "用户名为必填项。"
},
"username_has_been_taken" : {
"en-US": "Username has been taken!",
"zh-CN": "用户名已被占用"
"en-US": "Username has been taken.",
"zh-CN": "用户名已被占用"
},
"username_is_too_long" : {
"en-US": "Username is too long (maximum is 20 characters)",
"zh-CN": "用户名内容长度超出字符数限制。最长为20个字符"
"en-US": "Username is too long. (maximum 20 characters)",
"zh-CN": "用户名长度超出限制。最长为20个字符"
},
"username_contains_illegal_chars": {
"en-US": "Username contains illegal characters.",
"en-US": "Username contains illegal character(s).",
"zh-CN": "用户名包含不合法的字符。"
},
"email_is_required" : {
"en-US": "Email is required!",
"en-US": "Email is required.",
"zh-CN": "邮箱为必填项。"
},
"email_contains_illegal_chars" : {
"en-US": "Email contains illegal characters.",
"en-US": "Email contains illegal character(s).",
"zh-CN": "邮箱包含不合法的字符。"
},
"email_has_been_taken" : {
"en-US": "Email has been taken!",
"zh-CN": "邮箱已被占用"
"en-US": "Email has been taken.",
"zh-CN": "邮箱已被占用"
},
"email_content_illegal" : {
"en-US": "Email content is illegal.",
"zh-CN": "邮箱格式不合法"
"en-US": "Email format is illegal.",
"zh-CN": "邮箱格式不合法"
},
"email_does_not_exist" : {
"en-US": "Email does not exist!",
"en-US": "Email does not exist.",
"zh-CN": "邮箱不存在。"
},
"realname_is_required" : {
"en-US": "Realname is required!",
"en-US": "Full name is required.",
"zh-CN": "全名为必填项。"
},
"realname_is_too_long" : {
"en-US": "Realname is too long (maximum is 20 characters)",
"zh-CN": "全名内容长度超出字符数限制。最长为20个字符"
"en-US": "Full name is too long. (maximum 20 characters)",
"zh-CN": "全名长度超出限制。最长为20个字符"
},
"realname_contains_illegal_chars" : {
"en-US": "Realname contains illegal characters.",
"en-US": "Full name contains illegal character(s).",
"zh-CN": "全名包含不合法的字符。"
},
"password_is_required" : {
"en-US": "Password is required!",
"en-US": "Password is required.",
"zh-CN": "密码为必填项。"
},
"password_is_invalid" : {
"en-US": "Password is invalid. Use more than seven characters with at least one lowercase letter, one capital letter and one numeral.",
"zh-CN": "密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和数字。"
"en-US": "Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.",
"zh-CN": "密码无效。至少输入 7个字符且包含 1个小写字母1个大写字母和 1个数字。"
},
"password_is_too_long" : {
"en-US": "Password is too long (maximum is 20 characters)",
"zh-CN": "密码内容长度超出字符数限制。最长为20个字符"
"en-US": "Password is too long. (maximum 20 characters)",
"zh-CN": "密码长度超出限制。最长为20个字符"
},
"password_does_not_match" : {
"en-US": "Password does not match the confirmation.",
"zh-CN": "两次密码输入内容不一致。"
"en-US": "Passwords do not match.",
"zh-CN": "两次密码输入不一致。"
},
"comment_is_too_long" : {
"en-US": "Comment is too long (maximum is 20 characters)",
"zh-CN": "留言内容长度超过字符数限制。最长为20个字符"
"en-US": "Comment is too long. (maximum 20 characters)",
"zh-CN": "备注长度超出限制。最长为20个字符"
},
"comment_contains_illegal_chars" : {
"en-US": "Comment contains illegal characters.",
"zh-CN": "留言内容包含不合法的字符。"
"en-US": "Comment contains illegal character(s).",
"zh-CN": "备注包含不合法的字符。"
},
"project_name_is_required" : {
"en-US": "Project name is required!",
"en-US": "Project name is required.",
"zh-CN": "项目名称为必填项。"
},
"project_name_is_too_short" : {
"en-US": "Project name is too short (minimum is 4 characters)",
"zh-CN": "项目名称内容过于简短。最少要求4个字符"
"en-US": "Project name is too short. (minimum 4 characters)",
"zh-CN": "项目名称至少要求 4个字符。"
},
"project_name_is_too_long" : {
"en-US": "Project name is too long (maximum is 30 characters)",
"zh-CN": "项目名称内容长度超出字符数限制。最长为30个字符"
"en-US": "Project name is too long. (maximum 30 characters)",
"zh-CN": "项目名称长度超出限制。最长为30个字符"
},
"project_name_contains_illegal_chars" : {
"en-US": "Project name contains illegal characters.",
"zh-CN": "项目名称内容包含不合法的字符。"
"en-US": "Project name contains illegal character(s).",
"zh-CN": "项目名称包含不合法的字符。"
},
"project_exists" : {
"en-US": "Project exists.",
@ -118,7 +118,7 @@ var global_messages = {
"zh-CN": "请输入用户名和密码。"
},
"check_your_username_or_password" : {
"en-US": "Please check your username or password!",
"en-US": "Please check your username or password.",
"zh-CN": "请输入正确的用户名或密码。"
},
"title_login_failed" : {
@ -130,27 +130,27 @@ var global_messages = {
"zh-CN": "修改密码"
},
"change_password_successfully" : {
"en-US": "Changed password successfully.",
"zh-CN": "修改密码操作成功。"
"en-US": "Password changed successfully.",
"zh-CN": "密码已修改。"
},
"title_forgot_password" : {
"en-US": "Forgot Password",
"zh-CN": "忘记密码"
},
"email_has_been_sent" : {
"en-US": "Email has been sent.",
"en-US": "Email for resetting password has been sent.",
"zh-CN": "重置密码邮件已发送。"
},
"send_email_failed" : {
"en-US": "Send email failed.",
"zh-CN": "邮件发送失败。"
"en-US": "Failed to send Email for resetting password.",
"zh-CN": "重置密码邮件发送失败。"
},
"please_login_first" : {
"en-US": "Please login first!",
"en-US": "Please login first.",
"zh-CN": "请先登录。"
},
"old_password_is_not_correct" : {
"en-US": "Old password input is not correct.",
"en-US": "Old password is not correct.",
"zh-CN": "原密码输入不正确。"
},
"please_input_new_password" : {
@ -158,15 +158,15 @@ var global_messages = {
"zh-CN": "请输入新密码。"
},
"invalid_reset_url": {
"en-US": "Invalid reset url",
"zh-CN": "无效的重置链接"
"en-US": "Invalid URL for resetting password.",
"zh-CN": "无效密码重置链接。"
},
"reset_password_successfully" : {
"en-US": "Reset password successfully.",
"zh-CN": "密码重置成功。"
},
"internal_error": {
"en-US": "Internal error, please contact sysadmin.",
"en-US": "Internal error.",
"zh-CN": "内部错误,请联系系统管理员。"
},
"title_reset_password" : {
@ -178,11 +178,11 @@ var global_messages = {
"zh-CN": "注册"
},
"registered_successfully": {
"en-US": "Registered successfully.",
"en-US": "Signed up successfully.",
"zh-CN": "注册成功。"
},
"registered_failed" : {
"en-US": "Registered failed.",
"en-US": "Failed to sign up.",
"zh-CN": "注册失败。"
},
"projects" : {
@ -191,11 +191,11 @@ var global_messages = {
},
"repositories" : {
"en-US": "Repositories",
"zh-CN": "镜像资源"
"zh-CN": "镜像仓库"
},
"no_repo_exists" :{
"en-US": "No repositories exist.",
"zh-CN": "没有镜像资源。"
"en-US": "No repositories found, please use 'docker push' to upload images.",
"zh-CN": "未发现镜像请用docker push命令上传镜像。"
},
"tag" : {
"en-US": "Tag",
@ -210,15 +210,15 @@ var global_messages = {
"zh-CN": "镜像详细信息"
},
"add_members" : {
"en-US": "Add Members",
"en-US": "Add Member",
"zh-CN": "添加成员"
},
"edit_members" : {
"en-US": "Edit Members",
"en-US": "Edit Member",
"zh-CN": "编辑成员"
},
"add_member_failed" : {
"en-US": "Add Member Failed",
"en-US": "Adding Member Failed",
"zh-CN": "添加成员失败"
},
"please_input_username" : {
@ -230,29 +230,21 @@ var global_messages = {
"zh-CN": "请为用户分配角色。"
},
"user_id_exists" : {
"en-US": "User ID exists.",
"zh-CN": "用户ID已存在。"
"en-US": "User is already a member.",
"zh-CN": "用户已经是成员。"
},
"user_id_does_not_exist" : {
"en-US": "User ID does not exist.",
"zh-CN": "不存在此用户ID。"
"en-US": "User does not exist.",
"zh-CN": "不存在此用户。"
},
"insuffient_authority" : {
"en-US": "Insufficient authority.",
"insufficient_privileges" : {
"en-US": "Insufficient privileges.",
"zh-CN": "权限不足。"
},
"operation_failed" : {
"en-US": "Operation Failed",
"zh-CN": "操作失败"
},
"network_error" : {
"en-US": "Network Error",
"zh-CN": "网络故障"
},
"network_error_description" : {
"en-US": "Network error, please contact sysadmin.",
"zh-CN": "网络故障, 请联系系统管理员。"
},
"button_on" : {
"en-US": "On",
"zh-CN": "打开"

View File

@ -13,7 +13,7 @@ page_title_search = 搜索 - Harbor
sign_in = 登录
sign_up = 注册
log_out = 注销
search_placeholder = 搜索项目或镜像资源
search_placeholder = 项目或镜像名称
change_password = 修改密码
username_email = 用户名/邮箱
password = 密码
@ -25,10 +25,10 @@ admin_options = 管理员选项
project_name = 项目名称
creation_time = 创建时间
publicity = 公开
add_project = 项目
check_for_publicity = 选中此项会使该项目对其他人公开。
add_project = 项目
check_for_publicity = 公开项目
button_save = 保存
button_close = 关闭
button_cancel = 取消
button_submit = 提交
username = 用户名
email = 邮箱
@ -36,28 +36,28 @@ system_admin = 系统管理员
dlg_button_ok = 确定
dlg_button_cancel = 取消
registration = 注册
username_description = 在此填入登录时的用户名
email_description = 您将定期通过邮件收到相关信息,我们保证不会将此邮箱告知他人
username_description = 在此填入登录时的用户名
email_description = 此邮箱将用于重置密码
full_name = 全名
full_name_description = 姓氏和名称
password_description = 至少输入7个字符且包含1个小写字母1个大写字母和数字。
full_name_description = 请输入全名。
password_description = 至少输入 7个字符且包含 1个小写字母 1个大写字母和 1个数字。
confirm_password = 确认密码
note_to_the_admin = 留言
note_to_the_admin = 备注
old_password = 原密码
new_password = 新密码
forgot_password_description = 输入您注册时使用的邮箱,我们将发送重置邮件链接到此邮箱。
forgot_password_description = 重置邮件将发送到此邮箱。
projects = 项目
repositories = 镜像资源
repositories = 镜像仓库
search = 搜索
home = 首页
project = 项目
owner = 所有者
repo = 镜像
repo = 镜像仓库
user = 用户
logs = 日志
repo_name = 镜像名称
add_members = 添加成员
role_info = 角色信息
operation = 操作
advance = 高级检索
all = 全部
@ -73,10 +73,10 @@ language_en-US = English
language_zh-CN = 中文
copyright = 版权所有
all_rights_reserved = 保留所有权利。
container_registry_management = 容器注册管理
index_desc = Harbor是用于构建企业级、可信赖的容器注册管理系统。企业用户通过搭建Harbor创建私有容器注册服务提高生产力和安全管控。Harbor可同时应用于生产或开发环境。
index_desc_0 = 使用Harbor的关键优势在于
index_desc_1 = 1. 安全: 确保知识产权在自己组织的管控之下。
index_desc_2 = 2. 效率: 搭建于组织内部网络的私有容器注册系统可明显降低访问公共容器注册服务的带宽损耗。
index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务提高生产效率和安全度既可应用于生产环境也可以在开发环境中使用。
index_desc_0 = 主要优点:
index_desc_1 = 1. 安全: 确保知识产权在自己组织内部的管控之下。
index_desc_2 = 2. 效率: 搭建组织内部的私有容器Registry服务可显著降低访问公共Registry服务的网络需求。
index_desc_3 = 3. 访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP
index_desc_4 = 4. 审计: 所有访问注册服务的行为均被记录且可被用于日后审计。
index_desc_4 = 4. 审计: 所有访问Registry服务的操作均被记录便于日后审计。
index_desc_5 = 5. 管理界面: 具有友好易用图形管理界面。

View File

@ -218,14 +218,14 @@ jQuery(function(){
type: ajaxOpts.type,
complete: function(jqXhr, status){
if(jqXhr && jqXhr.status == 200){
$("#btnClose").trigger("click");
$("#btnCancel").trigger("click");
listUser(null);
}
},
errors: {
404: i18n.getMessage("user_id_does_not_exist"),
409: i18n.getMessage("user_id_exists"),
403: i18n.getMessage("insuffient_authority")
403: i18n.getMessage("insufficient_privileges")
}
}).exec();
});
@ -419,6 +419,24 @@ jQuery(function(){
$("#spnFilterOption input[name=chkOperation]").prop("checked", $(this).prop("checked"));
});
$("#spnFilterOption input[name=chkOperation]").on("click", function(){
if(!$(this).prop("checked")){
$("#spnFilterOption input[name=chkAll]").prop("checked", false);
}
var selectedAll = true;
$("#spnFilterOption input[name=chkOperation]").each(function(i, e){
if(!$(e).prop("checked")){
selectedAll = false;
}
});
if(selectedAll){
$("#spnFilterOption input[name=chkAll]").prop("checked", true);
}
});
function getKeyWords(){
var keywords = "";
var checkedItemList=$("#spnFilterOption input[name=chkOperation]:checked");

View File

@ -132,7 +132,7 @@ jQuery(function(){
409: i18n.getMessage("project_exists")
},
complete: function(jqXhr, status){
$("#btnClose").trigger("click");
$("#btnCancel").trigger("click");
listProject(null, currentPublic);
}
}).exec();

View File

@ -1 +0,0 @@
##dummy##

View File

@ -1,17 +1,18 @@
/*
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.
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.
*/
package utils
import (
@ -21,6 +22,7 @@ import (
"golang.org/x/crypto/pbkdf2"
)
// Encrypt encrypts the content with salt
func Encrypt(content string, salt string) string {
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
}

View File

@ -1,17 +1,18 @@
/*
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.
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.
*/
package utils
import (
@ -23,12 +24,15 @@ import (
"github.com/astaxie/beego"
)
// Mail holds information about content of Email
type Mail struct {
From string
To []string
Subject string
Message string
}
// MailConfig holds information about Email configurations
type MailConfig struct {
Identity string
Host string
@ -39,6 +43,7 @@ type MailConfig struct {
var mc MailConfig
// SendMail sends Email according to the configurations
func (m Mail) SendMail() error {
if mc.Host == "" {

View File

@ -1,145 +0,0 @@
/*
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.
*/
package utils
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/astaxie/beego"
)
const SESSION_COOKIE = "beegosessionID"
func BuildRegistryUrl(segments ...string) string {
registryURL := os.Getenv("REGISTRY_URL")
if registryURL == "" {
registryURL = "http://localhost:5000"
}
url := registryURL + "/v2"
for _, s := range segments {
if s == "v2" {
beego.Error("Unnecessary v2 in", segments)
continue
}
url += "/" + s
}
return url
}
func HttpGet(url, sessionId, username, password string) ([]byte, error) {
response, err := http.Get(url)
if err != nil {
return nil, err
}
result, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode == http.StatusOK {
return result, nil
} else if response.StatusCode == http.StatusUnauthorized {
authenticate := response.Header.Get("WWW-Authenticate")
str := strings.Split(authenticate, " ")[1]
beego.Trace("url: " + url)
beego.Trace("Authentication Header: " + str)
var realm string
var service string
var scope string
strs := strings.Split(str, ",")
for _, s := range strs {
if strings.Contains(s, "realm") {
realm = s
} else if strings.Contains(s, "service") {
service = s
} else if strings.Contains(s, "scope") {
strings.HasSuffix(url, "v2/_catalog")
scope = s
}
}
realm = strings.Split(realm, "\"")[1]
service = strings.Split(service, "\"")[1]
scope = strings.Split(scope, "\"")[1]
authUrl := realm + "?service=" + service + "&scope=" + scope
//skip certificate check if token service is https.
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
request, err := http.NewRequest("GET", authUrl, nil)
if err != nil {
return nil, err
}
if len(sessionId) > 0 {
cookie := &http.Cookie{Name: SESSION_COOKIE, Value: sessionId, Path: "/"}
request.AddCookie(cookie)
} else {
request.SetBasicAuth(username, password)
}
response, err = client.Do(request)
if err != nil {
return nil, err
}
result, err = ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return nil, err
}
if response.StatusCode == http.StatusOK {
tt := make(map[string]string)
json.Unmarshal(result, &tt)
request, err = http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
request.Header.Add("Authorization", "Bearer "+tt["token"])
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}
for k, v := range via[0].Header {
if _, ok := req.Header[k]; !ok {
req.Header[k] = v
}
}
return nil
}
response, err = client.Do(request)
if err != nil {
return nil, err
}
result, err = ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
defer response.Body.Close()
return result, nil
} else {
return nil, errors.New(string(result))
}
} else {
return nil, errors.New(string(result))
}
}

View File

@ -12,6 +12,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
@ -23,10 +24,12 @@ import (
"github.com/astaxie/beego"
)
// Repository holds information about repository
type Repository struct {
Name string
}
// ParseBasicAuth parses the basic authorization
func ParseBasicAuth(authorization []string) (username, password string) {
if authorization == nil || len(authorization) == 0 {
beego.Debug("Authorization header is not set.")
@ -38,6 +41,7 @@ func ParseBasicAuth(authorization []string) (username, password string) {
return pair[0], pair[1]
}
// GetProject parses the repository and return the name of project.
func (r *Repository) GetProject() string {
if !strings.ContainsRune(r.Name, '/') {
return ""
@ -45,18 +49,22 @@ func (r *Repository) GetProject() string {
return r.Name[0:strings.LastIndex(r.Name, "/")]
}
// ProjectSorter holds an array of projects
type ProjectSorter struct {
Projects []models.Project
}
// Len returns the length of array in ProjectSorter
func (ps *ProjectSorter) Len() int {
return len(ps.Projects)
}
// Less defines the comparison rules of project
func (ps *ProjectSorter) Less(i, j int) bool {
return ps.Projects[i].Name < ps.Projects[j].Name
}
// Swap swaps the position of i and j
func (ps *ProjectSorter) Swap(i, j int) {
ps.Projects[i], ps.Projects[j] = ps.Projects[j], ps.Projects[i]
}

View File

@ -31,6 +31,7 @@
<p>{{i18n .Lang "index_desc_2"}}</p>
<p>{{i18n .Lang "index_desc_3"}}</p>
<p>{{i18n .Lang "index_desc_4"}}</p>
<p>{{i18n .Lang "index_desc_5"}}</p>
</div>
</div>
</div> <!-- /container -->

View File

@ -82,7 +82,7 @@
<thead>
<tr>
<th>{{i18n .Lang "username"}}</th>
<th>{{i18n .Lang "role_info"}}</th>
<th>{{i18n .Lang "role"}}</th>
<th>{{i18n .Lang "operation"}}</th>
</tr>
</thead>
@ -214,7 +214,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="btnSave">{{i18n .Lang "button_save"}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal" id="btnClose">{{i18n .Lang "button_close"}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal" id="btnCancel">{{i18n .Lang "button_cancel"}}</button>
</div>
</div>
</div>

View File

@ -83,7 +83,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<a type="button" class="close" data-dismiss="modal" aria-label="Close" id="btnClose">
<a type="button" class="close" data-dismiss="modal" aria-label="Close" id="btnCancel">
<span aria-hidden="true">&times;</span>
</a>
<h4 class="modal-title" id="dlgAddProjectTitle">{{i18n .Lang "add_project"}}</h4>
@ -105,7 +105,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="btnSave">{{i18n .Lang "button_save"}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{i18n .Lang "button_close"}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{i18n .Lang "button_cancel"}}</button>
</div>
</div>
</div>