harbor/src/ui/api/user.go
2017-09-19 17:16:54 +08:00

406 lines
12 KiB
Go

// Copyright (c) 2017 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 (
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
)
// UserAPI handles request to /api/users/{}
type UserAPI struct {
BaseController
currentUserID int
userID int
SelfRegistration bool
IsAdmin bool
AuthMode string
}
type passwordReq struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
// Prepare validates the URL and parms
func (ua *UserAPI) Prepare() {
ua.BaseController.Prepare()
mode, err := config.AuthMode()
if err != nil {
log.Errorf("failed to get auth mode: %v", err)
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
ua.AuthMode = mode
self, err := config.SelfRegistration()
if err != nil {
log.Errorf("failed to get self registration: %v", err)
ua.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
ua.SelfRegistration = self
if !ua.SecurityCtx.IsAuthenticated() {
if ua.Ctx.Input.IsPost() {
return
}
ua.HandleUnauthorized()
return
}
user, err := dao.GetUser(models.User{
Username: ua.SecurityCtx.GetUsername(),
})
if err != nil {
ua.HandleInternalServerError(fmt.Sprintf("failed to get user %s: %v",
ua.SecurityCtx.GetUsername(), err))
return
}
ua.currentUserID = user.UserID
id := ua.Ctx.Input.Param(":id")
if id == "current" {
ua.userID = ua.currentUserID
} else if len(id) > 0 {
var err error
ua.userID, err = strconv.Atoi(id)
if err != nil {
log.Errorf("Invalid user id, error: %v", err)
ua.CustomAbort(http.StatusBadRequest, "Invalid user Id")
}
userQuery := models.User{UserID: ua.userID}
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, "")
}
}
ua.IsAdmin = ua.SecurityCtx.IsSysAdmin()
}
// Get ...
func (ua *UserAPI) Get() {
if ua.userID == ua.currentUserID || ua.IsAdmin {
userQuery := models.User{UserID: ua.userID}
u, err := dao.GetUser(userQuery)
if err != nil {
log.Errorf("Error occurred in GetUser, error: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
ua.Data["json"] = u
ua.ServeJSON()
return
}
log.Errorf("Current user, id: %d does not have admin role, can not view other user's detail", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
// List ...
func (ua *UserAPI) List() {
if !ua.IsAdmin {
log.Errorf("Current user, id: %d does not have admin role, can not list users", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
page, size := ua.GetPaginationParams()
query := &models.UserQuery{
Username: ua.GetString("username"),
Email: ua.GetString("email"),
Pagination: &models.Pagination{
Page: page,
Size: size,
},
}
total, err := dao.GetTotalOfUsers(query)
if err != nil {
ua.HandleInternalServerError(fmt.Sprintf("failed to get total of users: %v", err))
return
}
users, err := dao.ListUsers(query)
if err != nil {
ua.HandleInternalServerError(fmt.Sprintf("failed to get users: %v", err))
return
}
ua.SetPaginationHeader(total, page, size)
ua.Data["json"] = users
ua.ServeJSON()
}
// Put ...
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 {
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.Warningf("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}
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 ...
func (ua *UserAPI) Post() {
if !(ua.AuthMode == "db_auth") {
ua.CustomAbort(http.StatusForbidden, "")
}
if !(ua.SelfRegistration || ua.IsAdmin) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
ua.CustomAbort(http.StatusForbidden, "")
}
user := models.User{}
ua.DecodeJSONReq(&user)
err := validate(user)
if err != nil {
log.Warningf("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.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
}
// Delete ...
func (ua *UserAPI) Delete() {
if !ua.IsAdmin {
log.Warningf("current user, id: %d does not have admin role, can not remove user", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
}
if ua.AuthMode == "ldap_auth" {
ua.CustomAbort(http.StatusForbidden, "user can not be deleted in LDAP authentication mode")
}
if ua.currentUserID == ua.userID {
ua.CustomAbort(http.StatusForbidden, "can not delete yourself")
}
if ua.userID == 1 {
ua.HandleForbidden(ua.SecurityCtx.GetUsername())
return
}
var err error
err = dao.DeleteUser(ua.userID)
if err != nil {
log.Errorf("Failed to delete data from database, error: %v", err)
ua.RenderError(http.StatusInternalServerError, "Failed to delete User")
return
}
}
// 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, "")
}
if !ua.IsAdmin {
if ua.userID != ua.currentUserID {
log.Error("Guests can only change their own account.")
ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.")
}
}
var req passwordReq
ua.DecodeJSONReq(&req)
if req.OldPassword == "" {
log.Error("Old password is blank")
ua.CustomAbort(http.StatusBadRequest, "Old password is blank")
}
queryUser := models.User{UserID: ua.userID, Password: req.OldPassword}
user, err := dao.CheckUserPassword(queryUser)
if err != nil {
log.Errorf("Error occurred in CheckUserPassword: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if user == nil {
log.Warning("Password input is not correct")
ua.CustomAbort(http.StatusForbidden, "old_password_is_not_correct")
}
if req.NewPassword == "" {
ua.CustomAbort(http.StatusBadRequest, "please_input_new_password")
}
updateUser := models.User{UserID: ua.userID, Password: req.NewPassword, Salt: user.Salt}
err = dao.ChangeUserPassword(updateUser, req.OldPassword)
if err != nil {
log.Errorf("Error occurred in ChangeUserPassword: %v", err)
ua.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
}
// ToggleUserAdminRole handles PUT api/users/{}/sysadmin
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, 1, 20) {
return fmt.Errorf("username with illegal length")
}
if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
return fmt.Errorf("username contains illegal characters")
}
if isIllegalLength(user.Password, 8, 20) {
return fmt.Errorf("password with illegal length")
}
return commonValidate(user)
}
//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, 1, 255) {
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
}