mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge pull request #9368 from reasonerjt/set-cli-secret-api
API for user to set the CLI secret
This commit is contained in:
commit
40d80f82ba
@ -959,13 +959,13 @@ paths:
|
||||
description: User ID does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/users/{user_id}/gen_cli_secret':
|
||||
post:
|
||||
summary: Generate new CLI secret for a user.
|
||||
'/users/{user_id}/cli_secret':
|
||||
put:
|
||||
summary: Set CLI secret for a user.
|
||||
description: |
|
||||
This endpoint let user generate a new CLI secret for himself. This API only works when auth mode is set to 'OIDC'.
|
||||
Once this API returns with successful status, the old secret will be invalid, as there will be only one CLI secret
|
||||
for a user. The new secret will be returned in the response.
|
||||
for a user.
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
@ -973,19 +973,23 @@ paths:
|
||||
format: int
|
||||
required: true
|
||||
description: User ID
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: The secret is successfully generated.
|
||||
- name: input_secret
|
||||
in: body
|
||||
description: JSON object that includes the new secret
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
secret:
|
||||
type: string
|
||||
description: The new secret
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: The secret is successfully updated
|
||||
'400':
|
||||
description: Invalid user ID. Or user is not onboarded via OIDC authentication.
|
||||
description: Invalid user ID. Or user is not onboarded via OIDC authentication. Or the secret does not meet the standard.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
|
@ -52,7 +52,7 @@ type userSearch struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type secretResp struct {
|
||||
type secretReq struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
@ -405,8 +405,8 @@ func (ua *UserAPI) ChangePassword() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.NewPassword) == 0 {
|
||||
ua.SendBadRequestError(errors.New("empty new_password"))
|
||||
if err := validateSecret(req.NewPassword); err != nil {
|
||||
ua.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -512,8 +512,8 @@ func (ua *UserAPI) ListUserPermissions() {
|
||||
return
|
||||
}
|
||||
|
||||
// GenCLISecret generates a new CLI secret and replace the old one
|
||||
func (ua *UserAPI) GenCLISecret() {
|
||||
// SetCLISecret handles request PUT /api/users/:id/cli_secret to update the CLI secret of the user
|
||||
func (ua *UserAPI) SetCLISecret() {
|
||||
if ua.AuthMode != common.OIDCAuth {
|
||||
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
||||
return
|
||||
@ -534,8 +534,17 @@ func (ua *UserAPI) GenCLISecret() {
|
||||
return
|
||||
}
|
||||
|
||||
sec := utils.GenerateRandomString()
|
||||
encSec, err := utils.ReversibleEncrypt(sec, ua.secretKey)
|
||||
s := &secretReq{}
|
||||
if err := ua.DecodeJSONReq(s); err != nil {
|
||||
ua.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
if err := validateSecret(s.Secret); err != nil {
|
||||
ua.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
encSec, err := utils.ReversibleEncrypt(s.Secret, ua.secretKey)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to encrypt secret, error: %v", err)
|
||||
ua.SendInternalServerError(errors.New("failed to encrypt secret"))
|
||||
@ -548,8 +557,6 @@ func (ua *UserAPI) GenCLISecret() {
|
||||
ua.SendInternalServerError(errors.New("failed to update secret in DB"))
|
||||
return
|
||||
}
|
||||
ua.Data["json"] = secretResp{sec}
|
||||
ua.ServeJSON()
|
||||
}
|
||||
|
||||
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
||||
@ -588,12 +595,24 @@ func validate(user models.User) error {
|
||||
if utils.IsContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||
return fmt.Errorf("username contains illegal characters")
|
||||
}
|
||||
if utils.IsIllegalLength(user.Password, 8, 20) {
|
||||
return fmt.Errorf("password with illegal length")
|
||||
|
||||
if err := validateSecret(user.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return commonValidate(user)
|
||||
}
|
||||
|
||||
func validateSecret(in string) error {
|
||||
hasLower := regexp.MustCompile(`[a-z]`)
|
||||
hasUpper := regexp.MustCompile(`[A-Z]`)
|
||||
hasNumber := regexp.MustCompile(`[0-9]`)
|
||||
if len(in) >= 8 && hasLower.MatchString(in) && hasUpper.MatchString(in) && hasNumber.MatchString(in) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("the password or secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number")
|
||||
}
|
||||
|
||||
// commonValidate validates email, realname, comment information when user register or change their profile
|
||||
func commonValidate(user models.User) error {
|
||||
|
||||
|
@ -380,8 +380,8 @@ func buildChangeUserPasswordURL(id int) string {
|
||||
|
||||
func TestUsersUpdatePassword(t *testing.T) {
|
||||
fmt.Println("Testing Update User Password")
|
||||
oldPassword := "old_password"
|
||||
newPassword := "new_password"
|
||||
oldPassword := "old_Passw0rd"
|
||||
newPassword := "new_Passw0rd"
|
||||
|
||||
user01 := models.User{
|
||||
Username: "user01_for_testing_change_password",
|
||||
@ -515,7 +515,7 @@ func TestUsersUpdatePassword(t *testing.T) {
|
||||
method: http.MethodPut,
|
||||
url: buildChangeUserPasswordURL(user01.UserID),
|
||||
bodyJSON: &passwordReq{
|
||||
NewPassword: "another_new_password",
|
||||
NewPassword: "another_new_Passw0rd",
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
@ -642,3 +642,13 @@ func TestUsersCurrentPermissions(t *testing.T) {
|
||||
assert.Nil(err)
|
||||
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
|
||||
}
|
||||
|
||||
func TestValidateSecret(t *testing.T) {
|
||||
assert.NotNil(t, validateSecret(""))
|
||||
assert.NotNil(t, validateSecret("12345678"))
|
||||
assert.NotNil(t, validateSecret("passw0rd"))
|
||||
assert.NotNil(t, validateSecret("PASSW0RD"))
|
||||
assert.NotNil(t, validateSecret("Sh0rt"))
|
||||
assert.Nil(t, validateSecret("Passw0rd"))
|
||||
assert.Nil(t, validateSecret("Thisis1Valid_password"))
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func initRouters() {
|
||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
||||
beego.Router("/api/users/:id/sysadmin", &api.UserAPI{}, "put:ToggleUserAdminRole")
|
||||
beego.Router("/api/users/:id/gen_cli_secret", &api.UserAPI{}, "post:GenCLISecret")
|
||||
beego.Router("/api/users/:id/cli_secret", &api.UserAPI{}, "put:SetCLISecret")
|
||||
beego.Router("/api/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
||||
|
Loading…
Reference in New Issue
Block a user