mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01:00
Merge remote-tracking branch 'upstream/job-service' into new-ui-with-sync-image
This commit is contained in:
commit
fc25048fdb
86
api/log.go
Normal file
86
api/log.go
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
//LogAPI handles request api/logs
|
||||
type LogAPI struct {
|
||||
BaseAPI
|
||||
userID int
|
||||
}
|
||||
|
||||
//Prepare validates the URL and the user
|
||||
func (l *LogAPI) Prepare() {
|
||||
l.userID = l.ValidateUser()
|
||||
}
|
||||
|
||||
//Get returns the recent logs according to parameters
|
||||
func (l *LogAPI) Get() {
|
||||
var err error
|
||||
startTime := l.GetString("start_time")
|
||||
if len(startTime) != 0 {
|
||||
i, err := strconv.ParseInt(startTime, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Parse startTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "startTime is not a valid integer")
|
||||
}
|
||||
startTime = time.Unix(i, 0).String()
|
||||
}
|
||||
|
||||
endTime := l.GetString("end_time")
|
||||
if len(endTime) != 0 {
|
||||
j, err := strconv.ParseInt(endTime, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Parse endTime to int error, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "endTime is not a valid integer")
|
||||
}
|
||||
endTime = time.Unix(j, 0).String()
|
||||
}
|
||||
|
||||
var linesNum int
|
||||
lines := l.GetString("lines")
|
||||
if len(lines) != 0 {
|
||||
linesNum, err = strconv.Atoi(lines)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--lines, err: %v", err)
|
||||
l.CustomAbort(http.StatusBadRequest, "bad request of lines")
|
||||
}
|
||||
if linesNum <= 0 {
|
||||
log.Warning("lines must be a positive integer")
|
||||
l.CustomAbort(http.StatusBadRequest, "lines is 0 or negative")
|
||||
}
|
||||
} else if len(startTime) == 0 && len(endTime) == 0 {
|
||||
linesNum = 10
|
||||
}
|
||||
|
||||
var logList []models.AccessLog
|
||||
logList, err = dao.GetRecentLogs(l.userID, linesNum, startTime, endTime)
|
||||
if err != nil {
|
||||
log.Errorf("Get recent logs error, err: %v", err)
|
||||
l.CustomAbort(http.StatusInternalServerError, "Internal error")
|
||||
}
|
||||
l.Data["json"] = logList
|
||||
l.ServeJSON()
|
||||
}
|
@ -18,6 +18,7 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"unicode"
|
||||
|
||||
"github.com/vmware/harbor/dao"
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -40,6 +41,7 @@ type projectReq struct {
|
||||
}
|
||||
|
||||
const projectNameMaxLen int = 30
|
||||
const projectNameMinLen int = 4
|
||||
|
||||
// Prepare validates the URL and the user
|
||||
func (p *ProjectAPI) Prepare() {
|
||||
@ -197,10 +199,9 @@ func (p *ProjectAPI) List() {
|
||||
p.ServeJSON()
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (p *ProjectAPI) Put() {
|
||||
// ToggleProjectPublic ...
|
||||
func (p *ProjectAPI) ToggleProjectPublic() {
|
||||
p.userID = p.ValidateUser()
|
||||
|
||||
var req projectReq
|
||||
var public int
|
||||
|
||||
@ -287,8 +288,16 @@ func validateProjectReq(req projectReq) error {
|
||||
if len(pn) == 0 {
|
||||
return fmt.Errorf("Project name can not be empty")
|
||||
}
|
||||
if len(pn) > projectNameMaxLen {
|
||||
return fmt.Errorf("Project name is too long")
|
||||
if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {
|
||||
return fmt.Errorf("project name is illegal in length. (greater than 4 or less than 30)")
|
||||
}
|
||||
if isContainIllegalChar(req.ProjectName, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
|
||||
return fmt.Errorf("project name contains illegal characters")
|
||||
}
|
||||
for _, v := range pn {
|
||||
if !unicode.IsLower(v) {
|
||||
return fmt.Errorf("project name must be in lower case")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -294,3 +294,30 @@ func (ra *RepositoryAPI) getUsername() (string, error) {
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
//GetTopRepos handles request GET /api/repositories/top
|
||||
func (ra *RepositoryAPI) GetTopRepos() {
|
||||
var err error
|
||||
var countNum int
|
||||
count := ra.GetString("count")
|
||||
if len(count) == 0 {
|
||||
countNum = 10
|
||||
} else {
|
||||
countNum, err = strconv.Atoi(count)
|
||||
if err != nil {
|
||||
log.Errorf("Get parameters error--count, err: %v", err)
|
||||
ra.CustomAbort(http.StatusBadRequest, "bad request of count")
|
||||
}
|
||||
if countNum <= 0 {
|
||||
log.Warning("count must be a positive integer")
|
||||
ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative")
|
||||
}
|
||||
}
|
||||
repos, err := dao.GetTopRepos(countNum)
|
||||
if err != nil {
|
||||
log.Errorf("error occured in get top 10 repos: %v", err)
|
||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||
}
|
||||
ra.Data["json"] = repos
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
158
api/user.go
158
api/user.go
@ -16,8 +16,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -133,14 +135,52 @@ func (ua *UserAPI) Get() {
|
||||
}
|
||||
|
||||
// Put ...
|
||||
func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body
|
||||
func (ua *UserAPI) Put() {
|
||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
||||
|
||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
||||
ua.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
if !ua.IsAdmin {
|
||||
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
|
||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
||||
if ua.userID != ua.currentUserID {
|
||||
log.Warning("Guests can only change their own account.")
|
||||
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
|
||||
}
|
||||
}
|
||||
user := models.User{UserID: ua.userID}
|
||||
ua.DecodeJSONReq(&user)
|
||||
err := commonValidate(user)
|
||||
if err != nil {
|
||||
log.Warning("Bad request in change user profile: %v", err)
|
||||
ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error())
|
||||
return
|
||||
}
|
||||
userQuery := models.User{UserID: ua.userID}
|
||||
dao.ToggleUserAdminRole(userQuery)
|
||||
u, err := dao.GetUser(userQuery)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in GetUser, error: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if u == nil {
|
||||
log.Errorf("User with Id: %d does not exist", ua.userID)
|
||||
ua.CustomAbort(http.StatusNotFound, "")
|
||||
}
|
||||
if u.Email != user.Email {
|
||||
emailExist, err := dao.UserExists(user, "email")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in change user profile: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if emailExist {
|
||||
log.Warning("email has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "email has already been used!")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := dao.ChangeUserProfile(user); err != nil {
|
||||
log.Errorf("Failed to update user profile, error: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Post ...
|
||||
@ -157,12 +197,36 @@ func (ua *UserAPI) Post() {
|
||||
|
||||
user := models.User{}
|
||||
ua.DecodeJSONReq(&user)
|
||||
|
||||
err := validate(user)
|
||||
if err != nil {
|
||||
log.Warning("Bad request in Register: %v", err)
|
||||
ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
|
||||
return
|
||||
}
|
||||
userExist, err := dao.UserExists(user, "username")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Register: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if userExist {
|
||||
log.Warning("username has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "username has already been used!")
|
||||
return
|
||||
}
|
||||
emailExist, err := dao.UserExists(user, "email")
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in change user profile: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
if emailExist {
|
||||
log.Warning("email has already been used!")
|
||||
ua.RenderError(http.StatusConflict, "email has already been used!")
|
||||
return
|
||||
}
|
||||
userID, err := dao.Register(user)
|
||||
if err != nil {
|
||||
log.Errorf("Error occurred in Register: %v", err)
|
||||
ua.RenderError(http.StatusInternalServerError, "Internal error.")
|
||||
return
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
|
||||
ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
|
||||
@ -186,9 +250,8 @@ func (ua *UserAPI) Delete() {
|
||||
|
||||
// ChangePassword handles PUT to /api/users/{}/password
|
||||
func (ua *UserAPI) ChangePassword() {
|
||||
|
||||
ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID)
|
||||
|
||||
|
||||
if !(ua.AuthMode == "db_auth" || ldapAdminUser) {
|
||||
ua.CustomAbort(http.StatusForbidden, "")
|
||||
}
|
||||
@ -228,3 +291,80 @@ func (ua *UserAPI) ChangePassword() {
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleUserAdminRole handles PUT api/users/{}/toggleadmin
|
||||
func (ua *UserAPI) ToggleUserAdminRole() {
|
||||
if !ua.IsAdmin {
|
||||
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
|
||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
||||
return
|
||||
}
|
||||
userQuery := models.User{UserID: ua.userID}
|
||||
ua.DecodeJSONReq(&userQuery)
|
||||
if err := dao.ToggleUserAdminRole(userQuery.UserID, userQuery.HasAdminRole); err != nil {
|
||||
log.Errorf("Error occurred in ToggleUserAdminRole: %v", err)
|
||||
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
||||
}
|
||||
}
|
||||
|
||||
// validate only validate when user register
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 0, 20) {
|
||||
return fmt.Errorf("Username with illegal length.")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Username contains illegal characters.")
|
||||
}
|
||||
if isIllegalLength(user.Password, 0, 20) {
|
||||
return fmt.Errorf("Password with illegal length.")
|
||||
}
|
||||
if err := commonValidate(user); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//commonValidate validates email, realname, comment information when user register or change their profile
|
||||
func commonValidate(user models.User) error {
|
||||
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := 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,}))$`, user.Email); !m {
|
||||
return fmt.Errorf("Email with illegal format.")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Email can't be empty")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
return fmt.Errorf("Realname with illegal length.")
|
||||
}
|
||||
|
||||
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("Realname contains illegal characters.")
|
||||
}
|
||||
if isIllegalLength(user.Comment, -1, 30) {
|
||||
return fmt.Errorf("Comment with illegal length.")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func isIllegalLength(s string, min int, max int) bool {
|
||||
if min == -1 {
|
||||
return (len(s) > max)
|
||||
}
|
||||
if max == -1 {
|
||||
return (len(s) <= min)
|
||||
}
|
||||
return (len(s) < min || len(s) > max)
|
||||
}
|
||||
|
||||
func isContainIllegalChar(s string, illegalChar []string) bool {
|
||||
for _, c := range illegalChar {
|
||||
if strings.Index(s, c) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -111,6 +111,9 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) {
|
||||
u.Realname = m.Principal
|
||||
u.Password = "12345678AbC"
|
||||
u.Comment = "registered from LDAP."
|
||||
if u.Email == "" {
|
||||
u.Email = u.Username + "@placeholder.com"
|
||||
}
|
||||
userID, err := dao.Register(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -115,3 +115,49 @@ func AccessLog(username, projectName, repoName, repoTag, action string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//GetRecentLogs returns recent logs according to parameters
|
||||
func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.AccessLog, error) {
|
||||
var recentLogList []models.AccessLog
|
||||
queryParam := make([]interface{}, 1)
|
||||
|
||||
sql := "select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username from access_log left join user on access_log.user_id=user.user_id where project_id in (select distinct project_id from project_member where user_id = ?)"
|
||||
queryParam = append(queryParam, userID)
|
||||
if startTime != "" {
|
||||
sql += " and op_time >= ?"
|
||||
queryParam = append(queryParam, startTime)
|
||||
}
|
||||
|
||||
if endTime != "" {
|
||||
sql += " and op_time <= ?"
|
||||
queryParam = append(queryParam, endTime)
|
||||
}
|
||||
|
||||
sql += " order by op_time desc"
|
||||
if linesNum != 0 {
|
||||
sql += " limit ?"
|
||||
queryParam = append(queryParam, linesNum)
|
||||
}
|
||||
o := GetOrmer()
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&recentLogList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return recentLogList, nil
|
||||
}
|
||||
|
||||
//GetTopRepos return top accessed public repos
|
||||
func GetTopRepos(countNum int) ([]models.TopRepo, error) {
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where project.public = 1 and access_log.operation = 'pull' group by repo_name order by access_count desc limit ? "
|
||||
queryParam := make([]interface{}, 1)
|
||||
queryParam = append(queryParam, countNum)
|
||||
var lists []models.TopRepo
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&lists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lists, nil
|
||||
}
|
||||
|
23
dao/base.go
23
dao/base.go
@ -18,39 +18,18 @@ package dao
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql" //register mysql driver
|
||||
"github.com/vmware/harbor/utils/log"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return (len(s) > max)
|
||||
}
|
||||
if max == -1 {
|
||||
return (len(s) <= min)
|
||||
}
|
||||
return (len(s) < min || len(s) > max)
|
||||
}
|
||||
|
||||
func isContainIllegalChar(s string, illegalChar []string) bool {
|
||||
for _, c := range illegalChar {
|
||||
if strings.Index(s, c) >= 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateRandomString generates a random string
|
||||
func GenerateRandomString() (string, error) {
|
||||
o := orm.NewOrm()
|
||||
|
@ -689,7 +689,7 @@ func TestDeleteProjectMember(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToggleAdminRole(t *testing.T) {
|
||||
err := ToggleUserAdminRole(*currentUser)
|
||||
err := ToggleUserAdminRole(currentUser.UserID, 1)
|
||||
if err != nil {
|
||||
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
|
||||
}
|
||||
@ -700,7 +700,7 @@ func TestToggleAdminRole(t *testing.T) {
|
||||
if !isAdmin {
|
||||
t.Errorf("User is not admin after toggled, user id: %d", currentUser.UserID)
|
||||
}
|
||||
err = ToggleUserAdminRole(*currentUser)
|
||||
err = ToggleUserAdminRole(currentUser.UserID, 0)
|
||||
if err != nil {
|
||||
t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser)
|
||||
}
|
||||
@ -713,6 +713,39 @@ func TestToggleAdminRole(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeUserProfile(t *testing.T) {
|
||||
user := models.User{UserID: currentUser.UserID, Email: username + "@163.com", Realname: "test", Comment: "Unit Test"}
|
||||
err := ChangeUserProfile(user)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in ChangeUserProfile: %v", err)
|
||||
}
|
||||
loginedUser, err := GetUser(models.User{UserID: currentUser.UserID})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetUser: %v", err)
|
||||
}
|
||||
if loginedUser != nil {
|
||||
if loginedUser.Email != username+"@163.com" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", username+"@163.com", loginedUser.Email)
|
||||
}
|
||||
if loginedUser.Realname != "test" {
|
||||
t.Errorf("user realname does not update, expected: %s, acutal: %s", "test", loginedUser.Realname)
|
||||
}
|
||||
if loginedUser.Comment != "Unit Test" {
|
||||
t.Errorf("user email does not update, expected: %s, acutal: %s", "Unit Test", loginedUser.Comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecentLogs(t *testing.T) {
|
||||
logs, err := GetRecentLogs(currentUser.UserID, 10, "2016-05-13 00:00:00", time.Now().String())
|
||||
if err != nil {
|
||||
t.Errorf("error occured in getting recent logs, error: %v", err)
|
||||
}
|
||||
if len(logs) <= 0 {
|
||||
t.Errorf("get logs error, expected: %d, actual: %d", 1, len(logs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
err := DeleteUser(currentUser.UserID)
|
||||
if err != nil {
|
||||
|
@ -18,7 +18,6 @@ package dao
|
||||
import (
|
||||
"github.com/vmware/harbor/models"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -30,15 +29,7 @@ import (
|
||||
// AddProject adds a project to the database along with project roles information and access log records.
|
||||
func AddProject(project models.Project) (int64, error) {
|
||||
|
||||
if isIllegalLength(project.Name, 4, 30) {
|
||||
return 0, errors.New("project name is illegal in length. (greater than 4 or less than 30)")
|
||||
}
|
||||
if isContainIllegalChar(project.Name, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) {
|
||||
return 0, errors.New("project name contains illegal characters")
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@ -17,7 +17,6 @@ package dao
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/models"
|
||||
@ -26,14 +25,7 @@ import (
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
o := GetOrmer()
|
||||
|
||||
p, err := o.Raw("insert into user (username, password, realname, email, comment, salt, sysadmin_flag, creation_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").Prepare()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -59,46 +51,6 @@ func Register(user models.User) (int64, error) {
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func validate(user models.User) error {
|
||||
|
||||
if isIllegalLength(user.Username, 0, 20) {
|
||||
return errors.New("Username with illegal length.")
|
||||
}
|
||||
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return errors.New("Username contains illegal characters.")
|
||||
}
|
||||
|
||||
if exist, _ := UserExists(models.User{Username: user.Username}, "username"); exist {
|
||||
return errors.New("Username already exists.")
|
||||
}
|
||||
|
||||
if len(user.Email) > 0 {
|
||||
if m, _ := 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,}))$`, user.Email); !m {
|
||||
return errors.New("Email with illegal format.")
|
||||
}
|
||||
if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist {
|
||||
return errors.New("Email already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 0, 20) {
|
||||
return errors.New("Realname with illegal length.")
|
||||
}
|
||||
|
||||
if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) {
|
||||
return errors.New("Realname contains illegal characters.")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Password, 0, 20) {
|
||||
return errors.New("Password with illegal length.")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Comment, -1, 30) {
|
||||
return errors.New("Comment with illegal length.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserExists returns whether a user exists according username or Email.
|
||||
func UserExists(user models.User, target string) (bool, error) {
|
||||
|
||||
|
21
dao/user.go
21
dao/user.go
@ -109,12 +109,13 @@ func ListUsers(query models.User) ([]models.User, error) {
|
||||
}
|
||||
|
||||
// ToggleUserAdminRole gives a user admin role.
|
||||
func ToggleUserAdminRole(u models.User) error {
|
||||
func ToggleUserAdminRole(userID, hasAdmin int) error {
|
||||
o := GetOrmer()
|
||||
|
||||
sql := `update user set sysadmin_flag =not sysadmin_flag where user_id = ?`
|
||||
|
||||
r, err := o.Raw(sql, u.UserID).Exec()
|
||||
queryParams := make([]interface{}, 1)
|
||||
sql := `update user set sysadmin_flag = ? where user_id = ?`
|
||||
queryParams = append(queryParams, hasAdmin)
|
||||
queryParams = append(queryParams, userID)
|
||||
r, err := o.Raw(sql, queryParams).Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -229,3 +230,13 @@ func DeleteUser(userID int) error {
|
||||
_, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeUserProfile ...
|
||||
func ChangeUserProfile(user models.User) error {
|
||||
o := GetOrmer()
|
||||
if _, err := o.Update(&user, "Email", "Realname", "Comment"); err != nil {
|
||||
log.Errorf("update user failed, error: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -353,7 +353,24 @@ paths:
|
||||
404:
|
||||
description: Project ID does not exist.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
description: Unexpected internal errors.
|
||||
/statistics:
|
||||
get:
|
||||
summary: Get projects number and repositories number relevant to the user
|
||||
description: |
|
||||
This endpoint is aimed to statistic all of the projects number and repositories number relevant to the logined user, also the public projects number and repositories number. If the user is admin, he can also get total projects number and total repositories number.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get the projects number and repositories number relevant to the user successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/StatisticMap'
|
||||
401:
|
||||
description: User need to log in first.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
|
||||
/users:
|
||||
get:
|
||||
summary: Get registered users of Harbor.
|
||||
@ -407,10 +424,9 @@ paths:
|
||||
description: Unexpected internal errors.
|
||||
/users/{user_id}:
|
||||
put:
|
||||
summary: Update a registered user to change to be an administrator of Harbor.
|
||||
summary: Update a registered user to change his profile.
|
||||
description: |
|
||||
This endpoint let a registered user change to be an administrator
|
||||
of Harbor.
|
||||
This endpoint let a registered user change his profile.
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
@ -418,6 +434,12 @@ paths:
|
||||
format: int32
|
||||
required: true
|
||||
description: Registered user ID
|
||||
- name: profile
|
||||
in: body
|
||||
description: Only email, realname and comment can be modified.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
@ -490,7 +512,35 @@ paths:
|
||||
403:
|
||||
description: Guests can only change their own account.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
description: Unexpected internal errors.
|
||||
/users/{user_id}/sysadmin:
|
||||
put:
|
||||
summary: Update a registered user to change to be an administrator of Harbor.
|
||||
description: |
|
||||
This endpoint let a registered user change to be an administrator
|
||||
of Harbor.
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int32
|
||||
required: true
|
||||
description: Registered user ID
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Updated user's admin role successfully.
|
||||
400:
|
||||
description: Invalid user ID.
|
||||
401:
|
||||
description: User need to log in first.
|
||||
403:
|
||||
description: User does not have permission of admin role.
|
||||
404:
|
||||
description: User ID does not exist.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories:
|
||||
get:
|
||||
summary: Get repositories accompany with relevant project and repo name.
|
||||
@ -597,6 +647,70 @@ paths:
|
||||
description: Retrieved manifests from a relevant repository successfully.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/repositories/top:
|
||||
get:
|
||||
summary: Get public repositories which are accessed most.
|
||||
description: |
|
||||
This endpoint aims to let users see the most popular public repositories
|
||||
parameters:
|
||||
- name: count
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The number of the requested public repositories, default is 10 if not provided.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Retrieved top repositories successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/TopRepo'
|
||||
400:
|
||||
description: Bad request because of invalid count.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
/logs:
|
||||
get:
|
||||
summary: Get recent logs of the projects which the user is a member of
|
||||
description: |
|
||||
This endpoint let user see the recent operation logs of the projects which he is member of
|
||||
parameters:
|
||||
- name: lines
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The number of logs to be shown, default is 10 if lines, start_time, end_time are not provided.
|
||||
- name: start_time
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
description: The start time of logs to be shown in unix timestap
|
||||
- name: end_time
|
||||
in: query
|
||||
type: integer
|
||||
format: int64
|
||||
required: false
|
||||
description: The end time of logs to be shown in unix timestap
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
200:
|
||||
description: Get the required logs successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/AccessLog'
|
||||
400:
|
||||
description: Bad request because of invalid parameter of lines or start_time or end_time.
|
||||
401:
|
||||
description: User need to login first.
|
||||
500:
|
||||
description: Unexpected internal errors.
|
||||
definitions:
|
||||
Search:
|
||||
type: object
|
||||
@ -744,3 +858,45 @@ definitions:
|
||||
user_name:
|
||||
type: string
|
||||
description: Username relevant to a project role member.
|
||||
TopRepo:
|
||||
type: object
|
||||
properties:
|
||||
repo_name:
|
||||
type: string
|
||||
description: The name of the repo
|
||||
access_count:
|
||||
type: integer
|
||||
format: int
|
||||
description: The access count of the repo
|
||||
StatisticMap:
|
||||
type: object
|
||||
properties:
|
||||
my_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the projects which the user is a member of.
|
||||
my_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the repositories belonging to the projects which the user is a member of.
|
||||
public_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the public projects.
|
||||
public_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the public repositories belonging to the public projects which the user is a member of.
|
||||
total_project_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the total projects, only be seen when the is admin.
|
||||
total_repo_count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of the total repositories, only be seen when the user is admin.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
// AccessLog holds information about logs which are used to record the actions that user take to the resourses.
|
||||
type AccessLog struct {
|
||||
LogID int `orm:"column(log_id)" json:"LogId"`
|
||||
LogID int `orm:"pk;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)"`
|
||||
|
@ -7,5 +7,9 @@ import (
|
||||
func init() {
|
||||
orm.RegisterModel(new(RepTarget),
|
||||
new(RepPolicy),
|
||||
new(RepJob))
|
||||
new(RepJob),
|
||||
new(User),
|
||||
new(Project),
|
||||
new(Role),
|
||||
new(AccessLog))
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
// Project holds the details of a project.
|
||||
type Project struct {
|
||||
ProjectID int64 `orm:"column(project_id)" json:"ProjectId"`
|
||||
ProjectID int64 `orm:"pk;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)"`
|
||||
|
@ -26,7 +26,7 @@ const (
|
||||
|
||||
// Role holds the details of a role.
|
||||
type Role struct {
|
||||
RoleID int `orm:"column(role_id)" json:"role_id"`
|
||||
RoleID int `orm:"pk;column(role_id)" json:"role_id"`
|
||||
RoleCode string `orm:"column(role_code)" json:"role_code"`
|
||||
Name string `orm:"column(name)" json:"role_name"`
|
||||
|
||||
|
22
models/toprepo.go
Normal file
22
models/toprepo.go
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
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
|
||||
|
||||
// TopRepo holds information about repository that accessed most
|
||||
type TopRepo struct {
|
||||
RepoName string `json:"name"`
|
||||
AccessCount int64 `json:"count"`
|
||||
}
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
// User holds the details of a user.
|
||||
type User struct {
|
||||
UserID int `orm:"column(user_id)" json:"UserId"`
|
||||
UserID int `orm:"pk;column(user_id)" json:"UserId"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
Email string `orm:"column(email)" json:"email"`
|
||||
Password string `orm:"column(password)" json:"password"`
|
||||
@ -29,11 +29,11 @@ type User struct {
|
||||
Comment string `orm:"column(comment)" json:"comment"`
|
||||
Deleted int `orm:"column(deleted)"`
|
||||
Rolename string
|
||||
RoleID int `json:"RoleId"`
|
||||
RoleList []Role
|
||||
HasAdminRole int `orm:"column(sysadmin_flag)"`
|
||||
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
|
||||
Salt string `orm:"column(salt)"`
|
||||
RoleID int `json:"RoleId"`
|
||||
RoleList []*Role `orm:"rel(m2m)"`
|
||||
HasAdminRole int `orm:"column(sysadmin_flag)"`
|
||||
ResetUUID string `orm:"column(reset_uuid)" json:"ResetUuid"`
|
||||
Salt string `orm:"column(salt)"`
|
||||
|
||||
CreationTime time.Time `orm:"creation_time" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"update_time" json:"update_time"`
|
||||
|
@ -55,6 +55,7 @@ func initRouters() {
|
||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &api.ProjectMemberAPI{})
|
||||
beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List")
|
||||
beego.Router("/api/projects/?:id", &api.ProjectAPI{})
|
||||
beego.Router("/api/projects/:id/publicity", &api.ProjectAPI{}, "put:ToggleProjectPublic")
|
||||
beego.Router("/api/statistics", &api.StatisticAPI{})
|
||||
beego.Router("/api/projects/:id([0-9]+)/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")
|
||||
beego.Router("/api/users/?:id", &api.UserAPI{})
|
||||
@ -68,6 +69,9 @@ func initRouters() {
|
||||
beego.Router("/api/policies/replication/:id([0-9]+)/enablement", &api.RepPolicyAPI{}, "put:UpdateEnablement")
|
||||
beego.Router("/api/targets/?:id([0-9]+)", &api.TargetAPI{})
|
||||
beego.Router("/api/targets/ping", &api.TargetAPI{}, "post:Ping")
|
||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||
beego.Router("api/logs", &api.LogAPI{})
|
||||
|
||||
//external service that hosted on harbor process:
|
||||
beego.Router("/service/notifications", &service.NotificationHandler{})
|
||||
|
Loading…
Reference in New Issue
Block a user