mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 00:57:44 +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.
|
description: User ID does not exist.
|
||||||
'500':
|
'500':
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
'/users/{user_id}/gen_cli_secret':
|
'/users/{user_id}/cli_secret':
|
||||||
post:
|
put:
|
||||||
summary: Generate new CLI secret for a user.
|
summary: Set CLI secret for a user.
|
||||||
description: |
|
description: |
|
||||||
This endpoint let user generate a new CLI secret for himself. This API only works when auth mode is set to 'OIDC'.
|
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
|
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:
|
parameters:
|
||||||
- name: user_id
|
- name: user_id
|
||||||
in: path
|
in: path
|
||||||
@ -973,19 +973,23 @@ paths:
|
|||||||
format: int
|
format: int
|
||||||
required: true
|
required: true
|
||||||
description: User ID
|
description: User ID
|
||||||
tags:
|
- name: input_secret
|
||||||
- Products
|
in: body
|
||||||
responses:
|
description: JSON object that includes the new secret
|
||||||
'200':
|
required: true
|
||||||
description: The secret is successfully generated.
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
description: The new secret
|
description: The new secret
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: The secret is successfully updated
|
||||||
'400':
|
'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':
|
'401':
|
||||||
description: User need to log in first.
|
description: User need to log in first.
|
||||||
'403':
|
'403':
|
||||||
|
@ -52,7 +52,7 @@ type userSearch struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type secretResp struct {
|
type secretReq struct {
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,8 +405,8 @@ func (ua *UserAPI) ChangePassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.NewPassword) == 0 {
|
if err := validateSecret(req.NewPassword); err != nil {
|
||||||
ua.SendBadRequestError(errors.New("empty new_password"))
|
ua.SendBadRequestError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,8 +512,8 @@ func (ua *UserAPI) ListUserPermissions() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenCLISecret generates a new CLI secret and replace the old one
|
// SetCLISecret handles request PUT /api/users/:id/cli_secret to update the CLI secret of the user
|
||||||
func (ua *UserAPI) GenCLISecret() {
|
func (ua *UserAPI) SetCLISecret() {
|
||||||
if ua.AuthMode != common.OIDCAuth {
|
if ua.AuthMode != common.OIDCAuth {
|
||||||
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
||||||
return
|
return
|
||||||
@ -534,8 +534,17 @@ func (ua *UserAPI) GenCLISecret() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sec := utils.GenerateRandomString()
|
s := &secretReq{}
|
||||||
encSec, err := utils.ReversibleEncrypt(sec, ua.secretKey)
|
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 {
|
if err != nil {
|
||||||
log.Errorf("Failed to encrypt secret, error: %v", err)
|
log.Errorf("Failed to encrypt secret, error: %v", err)
|
||||||
ua.SendInternalServerError(errors.New("failed to encrypt secret"))
|
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"))
|
ua.SendInternalServerError(errors.New("failed to update secret in DB"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ua.Data["json"] = secretResp{sec}
|
|
||||||
ua.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
||||||
@ -588,12 +595,24 @@ func validate(user models.User) error {
|
|||||||
if utils.IsContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
if utils.IsContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) {
|
||||||
return fmt.Errorf("username contains illegal characters")
|
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)
|
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
|
// commonValidate validates email, realname, comment information when user register or change their profile
|
||||||
func commonValidate(user models.User) error {
|
func commonValidate(user models.User) error {
|
||||||
|
|
||||||
|
@ -380,8 +380,8 @@ func buildChangeUserPasswordURL(id int) string {
|
|||||||
|
|
||||||
func TestUsersUpdatePassword(t *testing.T) {
|
func TestUsersUpdatePassword(t *testing.T) {
|
||||||
fmt.Println("Testing Update User Password")
|
fmt.Println("Testing Update User Password")
|
||||||
oldPassword := "old_password"
|
oldPassword := "old_Passw0rd"
|
||||||
newPassword := "new_password"
|
newPassword := "new_Passw0rd"
|
||||||
|
|
||||||
user01 := models.User{
|
user01 := models.User{
|
||||||
Username: "user01_for_testing_change_password",
|
Username: "user01_for_testing_change_password",
|
||||||
@ -515,7 +515,7 @@ func TestUsersUpdatePassword(t *testing.T) {
|
|||||||
method: http.MethodPut,
|
method: http.MethodPut,
|
||||||
url: buildChangeUserPasswordURL(user01.UserID),
|
url: buildChangeUserPasswordURL(user01.UserID),
|
||||||
bodyJSON: &passwordReq{
|
bodyJSON: &passwordReq{
|
||||||
NewPassword: "another_new_password",
|
NewPassword: "another_new_Passw0rd",
|
||||||
},
|
},
|
||||||
credential: admin,
|
credential: admin,
|
||||||
},
|
},
|
||||||
@ -642,3 +642,13 @@ func TestUsersCurrentPermissions(t *testing.T) {
|
|||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
|
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([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||||
beego.Router("/api/users/:id/permissions", &api.UserAPI{}, "get:ListUserPermissions")
|
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/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/usergroups/?:ugid([0-9]+)", &api.UserGroupAPI{})
|
||||||
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &api.LdapAPI{}, "post:Ping")
|
||||||
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
beego.Router("/api/ldap/users/search", &api.LdapAPI{}, "get:Search")
|
||||||
|
Loading…
Reference in New Issue
Block a user