Merge pull request #7463 from reasonerjt/regen-cli-secret

Provide API to generate CLI secret
This commit is contained in:
Daniel Jiang 2019-04-22 14:54:20 +08:00 committed by GitHub
commit 5cd3c039a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 136 deletions

View File

@ -940,6 +940,44 @@ 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':
post:
summary: Generate new 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.
parameters:
- name: user_id
in: path
type: integer
format: int
required: true
description: User ID
tags:
- Products
responses:
'200':
description: The secret is successfully generated.
schema:
type: object
properties:
secret:
type: string
description: The new secret
'400':
description: Invalid user ID. Or user is not onboarded via OIDC authentication.
'401':
description: User need to log in first.
'403':
description: Non-admin user can only generate the cli secret of himself.
'404':
description: User ID does not exist.
'412':
description: The auth mode of the system is not "oidc_auth", or the user is not onboarded via OIDC AuthN.
'500':
description: Unexpected internal errors.
/repositories: /repositories:
get: get:
summary: Get repositories accompany with relevant project and repo name. summary: Get repositories accompany with relevant project and repo name.
@ -1884,14 +1922,14 @@ paths:
description: | description: |
Delete the replication policy specified by ID. Delete the replication policy specified by ID.
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Replication policy ID description: Replication policy ID
tags: tags:
- Products - Products
responses: responses:
'200': '200':
$ref: '#/responses/OK' $ref: '#/responses/OK'
@ -2297,18 +2335,18 @@ paths:
description: | description: |
This endpoint let user list namespaces of registry according to query. This endpoint let user list namespaces of registry according to query.
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer type: integer
required: true required: true
description: The registry ID. description: The registry ID.
- name: name - name: name
in: query in: query
type: string type: string
required: false required: false
description: The name of namespace. description: The name of namespace.
tags: tags:
- Products - Products
responses: responses:
'200': '200':
description: Success description: Success
@ -2661,7 +2699,7 @@ paths:
'200': '200':
description: Get gc results successfully. description: Get gc results successfully.
schema: schema:
$ref: '#/definitions/GCResult' $ref: '#/definitions/GCResult'
'401': '401':
description: User need to log in first. description: User need to log in first.
'403': '403':
@ -2706,7 +2744,7 @@ paths:
'200': '200':
description: Get gc's schedule. description: Get gc's schedule.
schema: schema:
$ref: '#/definitions/AdminJobSchedule' $ref: '#/definitions/AdminJobSchedule'
'401': '401':
description: User need to log in first. description: User need to log in first.
'403': '403':
@ -2768,12 +2806,12 @@ paths:
summary: Get scan_all's schedule. summary: Get scan_all's schedule.
description: This endpoint is for getting a schedule for the scan all job, which scans all of images in Harbor. description: This endpoint is for getting a schedule for the scan all job, which scans all of images in Harbor.
tags: tags:
- Products - Products
responses: responses:
'200': '200':
description: Get a schedule for the scan all job, which scans all of images in Harbor. description: Get a schedule for the scan all job, which scans all of images in Harbor.
schema: schema:
$ref: '#/definitions/AdminJobSchedule' $ref: '#/definitions/AdminJobSchedule'
'401': '401':
description: User need to log in first. description: User need to log in first.
'403': '403':
@ -2785,14 +2823,14 @@ paths:
description: | description: |
This endpoint is for updating the schedule of scan all job, which scans all of images in Harbor. This endpoint is for updating the schedule of scan all job, which scans all of images in Harbor.
parameters: parameters:
- name: schedule - name: schedule
in: body in: body
required: true required: true
schema: schema:
$ref: '#/definitions/AdminJobSchedule' $ref: '#/definitions/AdminJobSchedule'
description: Updates the schedule of scan all job, which scans all of images in Harbor. description: Updates the schedule of scan all job, which scans all of images in Harbor.
tags: tags:
- Products - Products
responses: responses:
'200': '200':
description: Updated scan_all's schedule successfully. description: Updated scan_all's schedule successfully.
@ -2809,14 +2847,14 @@ paths:
description: | description: |
This endpoint is for creating a schedule or a manual trigger for the scan all job, which scans all of images in Harbor. This endpoint is for creating a schedule or a manual trigger for the scan all job, which scans all of images in Harbor.
parameters: parameters:
- name: schedule - name: schedule
in: body in: body
required: true required: true
schema: schema:
$ref: '#/definitions/AdminJobSchedule' $ref: '#/definitions/AdminJobSchedule'
description: Create a schedule or a manual trigger for the scan all job. description: Create a schedule or a manual trigger for the scan all job.
tags: tags:
- Products - Products
responses: responses:
'200': '200':
description: Updated scan_all's schedule successfully. description: Updated scan_all's schedule successfully.
@ -3289,15 +3327,15 @@ paths:
summary: Get all robot accounts of specified project summary: Get all robot accounts of specified project
description: Get all robot accounts of specified project description: Get all robot accounts of specified project
parameters: parameters:
- name: project_id - name: project_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Relevant project ID. description: Relevant project ID.
tags: tags:
- Products - Products
- Robot Account - Robot Account
responses: responses:
'200': '200':
description: Get project robot accounts successfully. description: Get project robot accounts successfully.
@ -3319,21 +3357,21 @@ paths:
summary: Create a robot account for project summary: Create a robot account for project
description: Create a robot account for project description: Create a robot account for project
tags: tags:
- Products - Products
- Robot Account - Robot Account
parameters: parameters:
- name: project_id - name: project_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Relevant project ID. description: Relevant project ID.
- name: robot - name: robot
in: body in: body
description: Request body of creating a robot account. description: Request body of creating a robot account.
required: true required: true
schema: schema:
$ref: '#/definitions/RobotAccountCreate' $ref: '#/definitions/RobotAccountCreate'
responses: responses:
'201': '201':
description: Project member created successfully. description: Project member created successfully.
@ -3354,21 +3392,21 @@ paths:
summary: Return the infor of the specified robot account. summary: Return the infor of the specified robot account.
description: Return the infor of the specified robot account. description: Return the infor of the specified robot account.
tags: tags:
- Products - Products
- Robot Account - Robot Account
parameters: parameters:
- name: project_id - name: project_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Relevant project ID. description: Relevant project ID.
- name: robot_id - name: robot_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: The ID of robot account. description: The ID of robot account.
responses: responses:
'200': '200':
description: Robot account information. description: Robot account information.
@ -3386,27 +3424,27 @@ paths:
summary: Update status of robot account. summary: Update status of robot account.
description: Used to disable/enable a specified robot account. description: Used to disable/enable a specified robot account.
tags: tags:
- Products - Products
- Robot Account - Robot Account
parameters: parameters:
- name: project_id - name: project_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Relevant project ID. description: Relevant project ID.
- name: robot_id - name: robot_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: The ID of robot account. description: The ID of robot account.
- name: robot - name: robot
in: body in: body
description: Request body of enable/disable a robot account. description: Request body of enable/disable a robot account.
required: true required: true
schema: schema:
$ref: '#/definitions/RobotAccountUpdate' $ref: '#/definitions/RobotAccountUpdate'
responses: responses:
'200': '200':
description: Robot account has been modified success. description: Robot account has been modified success.
@ -3416,21 +3454,21 @@ paths:
summary: Delete the specified robot account summary: Delete the specified robot account
description: Delete the specified robot account description: Delete the specified robot account
tags: tags:
- Products - Products
- Robot Account - Robot Account
parameters: parameters:
- name: project_id - name: project_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: Relevant project ID. description: Relevant project ID.
- name: robot_id - name: robot_id
in: path in: path
type: integer type: integer
format: int64 format: int64
required: true required: true
description: The ID of robot account. description: The ID of robot account.
responses: responses:
'200': '200':
description: The specified robot account is successfully deleted. description: The specified robot account is successfully deleted.

View File

@ -38,7 +38,7 @@ CREATE TABLE oidc_user (
UNIQUE (subiss) UNIQUE (subiss)
); );
CREATE TRIGGER odic_user_update_time_at_modtime BEFORE UPDATE ON oidc_user FOR EACH ROW EXECUTE PROCEDURE update_update_time_at_column(); CREATE TRIGGER oidc_user_update_time_at_modtime BEFORE UPDATE ON oidc_user FOR EACH ROW EXECUTE PROCEDURE update_update_time_at_column();
/*add master role*/ /*add master role*/
INSERT INTO role (role_code, name) VALUES ('DRWS', 'master'); INSERT INTO role (role_code, name) VALUES ('DRWS', 'master');

View File

@ -92,10 +92,17 @@ func GetOIDCUserByUserID(userID int) (*models.OIDCUser, error) {
return &oidcUsers[0], nil return &oidcUsers[0], nil
} }
// UpdateOIDCUser ... // UpdateOIDCUser updates the OIDCUser based on the input parm, only the column "secret" and "token" can be updated
func UpdateOIDCUser(oidcUser *models.OIDCUser) error { func UpdateOIDCUser(oidcUser *models.OIDCUser) error {
oidcUser.UpdateTime = time.Now() cols := []string{"secret", "token"}
_, err := GetOrmer().Update(oidcUser) _, err := GetOrmer().Update(oidcUser, cols...)
return err
}
// UpdateOIDCUserSecret updates the secret of the OIDC User. The secret in the input parm should be encrypted before
// calling this func
func UpdateOIDCUserSecret(oidcUser *models.OIDCUser) error {
_, err := GetOrmer().Update(oidcUser, "secret")
return err return err
} }

View File

@ -80,15 +80,13 @@ func TestOIDCUserMetaDaoMethods(t *testing.T) {
ID: ou111.ID, ID: ou111.ID,
UserID: ou111.UserID, UserID: ou111.UserID,
SubIss: "newSub", SubIss: "newSub",
Secret: "newSecret",
} }
require.Nil(t, UpdateOIDCUser(meta3)) require.Nil(t, UpdateOIDCUser(meta3))
oidcUser1Update, err := GetOIDCUserByID(ou111.ID) oidcUser1Update, err := GetOIDCUserByID(ou111.ID)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, "newSub", oidcUser1Update.SubIss) assert.Equal(t, "QWE123123RT1", oidcUser1Update.SubIss)
assert.Equal(t, "newSecret", oidcUser1Update.Secret)
user, err := GetUserBySubIss("new", "Sub")
require.Nil(t, err)
assert.Equal(t, "user111", user.Username)
// clear data // clear data
defer func() { defer func() {

View File

@ -38,6 +38,7 @@ type UserAPI struct {
SelfRegistration bool SelfRegistration bool
IsAdmin bool IsAdmin bool
AuthMode string AuthMode string
secretKey string
} }
type passwordReq struct { type passwordReq struct {
@ -50,6 +51,10 @@ type userSearch struct {
Username string `json:"username"` Username string `json:"username"`
} }
type secretResp struct {
Secret string `json:"secret"`
}
// Prepare validates the URL and parms // Prepare validates the URL and parms
func (ua *UserAPI) Prepare() { func (ua *UserAPI) Prepare() {
ua.BaseController.Prepare() ua.BaseController.Prepare()
@ -61,6 +66,15 @@ func (ua *UserAPI) Prepare() {
} }
ua.AuthMode = mode ua.AuthMode = mode
if mode == common.OIDCAuth {
key, err := config.SecretKey()
if err != nil {
log.Errorf("failed to get secret key: %v", err)
ua.SendInternalServerError(fmt.Errorf("failed to get secret key: %v", err))
return
}
ua.secretKey = key
}
self, err := config.SelfRegistration() self, err := config.SelfRegistration()
if err != nil { if err != nil {
@ -475,17 +489,53 @@ func (ua *UserAPI) ListUserPermissions() {
return return
} }
func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) { // GenCLISecret generates a new CLI secret and replace the old one
key, err := config.SecretKey() func (ua *UserAPI) GenCLISecret() {
if err != nil { if ua.AuthMode != common.OIDCAuth {
return nil, err ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
return
} }
if ua.userID != ua.currentUserID && !ua.IsAdmin {
ua.SendForbiddenError(errors.New(""))
return
}
oidcData, err := dao.GetOIDCUserByUserID(ua.userID)
if err != nil {
log.Errorf("Failed to get OIDC User meta for user, id: %d, error: %v", ua.userID, err)
ua.SendInternalServerError(errors.New("failed to get OIDC meta data for user"))
return
}
if oidcData == nil {
log.Errorf("User is not onboarded via OIDC AuthN, user id: %d", ua.userID)
ua.SendPreconditionFailedError(errors.New("user is not onboarded via OIDC AuthN"))
return
}
sec := utils.GenerateRandomString()
encSec, err := utils.ReversibleEncrypt(sec, ua.secretKey)
if err != nil {
log.Errorf("Failed to encrypt secret, error: %v", err)
ua.SendInternalServerError(errors.New("failed to encrypt secret"))
return
}
oidcData.Secret = encSec
err = dao.UpdateOIDCUserSecret(oidcData)
if err != nil {
log.Errorf("Failed to update secret in DB, error: %v", err)
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) {
o, err := dao.GetOIDCUserByUserID(ua.userID) o, err := dao.GetOIDCUserByUserID(ua.userID)
if err != nil || o == nil { if err != nil || o == nil {
return nil, err return nil, err
} }
if len(o.Secret) > 0 { if len(o.Secret) > 0 {
p, err := utils.ReversibleDecrypt(o.Secret, key) p, err := utils.ReversibleDecrypt(o.Secret, ua.secretKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -53,6 +53,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/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")