diff --git a/api/v2.0/legacy_swagger.yaml b/api/v2.0/legacy_swagger.yaml index a9abcd40e..6a5908b18 100644 --- a/api/v2.0/legacy_swagger.yaml +++ b/api/v2.0/legacy_swagger.yaml @@ -19,151 +19,6 @@ securityDefinitions: security: - basicAuth: [] paths: - '/projects/{project_id}/metadatas': - get: - summary: Get project metadata. - description: | - This endpoint returns metadata of the project specified by project ID. - parameters: - - name: project_id - in: path - description: The ID of project. - required: true - type: integer - format: int64 - tags: - - Products - responses: - '200': - description: Get metadata successfully. - schema: - $ref: '#/definitions/ProjectMetadata' - '401': - description: User need to login first. - '500': - description: Internal server errors. - post: - summary: Add metadata for the project. - description: | - This endpoint is aimed to add metadata of a project. - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: Selected project ID. - - name: metadata - in: body - required: true - schema: - $ref: '#/definitions/ProjectMetadata' - description: The metadata of project. - tags: - - Products - responses: - '200': - description: Add metadata successfully. - '400': - description: Invalid request. - '401': - description: User need to log in first. - '403': - description: User does not have permission to the project. - '404': - description: Project ID does not exist. - '415': - $ref: '#/responses/UnsupportedMediaType' - '500': - description: Internal server errors. - '/projects/{project_id}/metadatas/{meta_name}': - get: - summary: Get project metadata - description: | - This endpoint returns specified metadata of a project. - parameters: - - name: project_id - in: path - description: Project ID for filtering results. - required: true - type: integer - format: int64 - - name: meta_name - in: path - description: The name of metadat. - required: true - type: string - tags: - - Products - responses: - '200': - description: Get metadata successfully. - schema: - $ref: '#/definitions/ProjectMetadata' - '401': - description: User need to log in first. - '500': - description: Internal server errors. - put: - summary: Update metadata of a project. - description: | - This endpoint is aimed to update the metadata of a project. - parameters: - - name: project_id - in: path - type: integer - format: int64 - required: true - description: The ID of project. - - name: meta_name - in: path - description: The name of metadat. - required: true - type: string - tags: - - Products - responses: - '200': - description: Updated metadata successfully. - '400': - description: Invalid request. - '401': - description: User need to log in first. - '403': - description: User does not have permission to the project. - '404': - description: Project or metadata does not exist. - '500': - description: Internal server errors. - delete: - summary: Delete metadata of a project - description: | - This endpoint is aimed to delete metadata of a project. - parameters: - - name: project_id - in: path - description: The ID of project. - required: true - type: integer - format: int64 - - name: meta_name - in: path - description: The name of metadat. - required: true - type: string - tags: - - Products - responses: - '200': - description: Metadata is deleted successfully. - '400': - description: Invalid requst. - '403': - description: User need to log in first. - '404': - description: Project or metadata does not exist. - '500': - description: Internal server errors. /email/ping: post: summary: Test connection and authentication with email server. @@ -376,28 +231,6 @@ responses: InternalServerError: description: 'Internal Server Error' definitions: - ProjectMetadata: - type: object - properties: - public: - type: string - description: 'The public status of the project. The valid values are "true", "false".' - enable_content_trust: - type: string - description: 'Whether content trust is enabled or not. If it is enabled, user can''t pull unsigned images from this project. The valid values are "true", "false".' - prevent_vul: - type: string - description: 'Whether prevent the vulnerable images from running. The valid values are "true", "false".' - severity: - type: string - description: 'If the vulnerability is high than severity defined here, the images can''t be pulled. The valid values are "none", "low", "medium", "high", "critical".' - auto_scan: - type: string - description: 'Whether scan images automatically when pushing. The valid values are "true", "false".' - reuse_sys_cve_allowlist: - type: string - description: 'Whether this project reuse the system level CVE allowlist as the allowlist of its own. The valid values are "true", "false". - If it is set to "true" the actual allowlist associate with this project, if any, will be ignored.' Role: type: object properties: diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index 53b206e21..6f349a09d 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -641,6 +641,164 @@ paths: $ref: '#/responses/403' '500': $ref: '#/responses/500' + '/projects/{project_name_or_id}/metadatas/': + get: + summary: Get the metadata of the specific project + description: Get the metadata of the specific project + operationId: listProjectMetadatas + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + tags: + - projectMetadata + responses: + '200': + description: Success + schema: + type: object + additionalProperties: + type: string + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' + post: + summary: Add metadata for the specific project + operationId: addProjectMetadatas + description: Add metadata for the specific project + tags: + - projectMetadata + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - name: metadata + in: body + schema: + type: object + additionalProperties: + type: string + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '409': + $ref: '#/responses/409' + '500': + $ref: '#/responses/500' + '/projects/{project_name_or_id}/metadatas/{meta_name}': + get: + summary: Get the specific metadata of the specific project + description: Get the specific metadata of the specific project + operationId: getProjectMetadata + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - name: meta_name + in: path + description: The name of metadata. + required: true + type: string + tags: + - projectMetadata + responses: + '200': + description: Success + schema: + type: object + additionalProperties: + type: string + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '500': + $ref: '#/responses/500' + put: + summary: Update the specific metadata for the specific project + description: Update the specific metadata for the specific project + operationId: updateProjectMetadata + tags: + - projectMetadata + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - name: meta_name + in: path + description: The name of metadata. + required: true + type: string + - name: metadata + in: body + schema: + type: object + additionalProperties: + type: string + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '409': + $ref: '#/responses/409' + '500': + $ref: '#/responses/500' + delete: + summary: Delete the specific metadata for the specific project + description: Delete the specific metadata for the specific project + operationId: deleteProjectMetadata + tags: + - projectMetadata + parameters: + - $ref: '#/parameters/requestId' + - $ref: '#/parameters/isResourceName' + - $ref: '#/parameters/projectNameOrId' + - name: meta_name + in: path + description: The name of metadata. + required: true + type: string + responses: + '200': + $ref: '#/responses/200' + '400': + $ref: '#/responses/400' + '401': + $ref: '#/responses/401' + '403': + $ref: '#/responses/403' + '404': + $ref: '#/responses/404' + '409': + $ref: '#/responses/409' + '500': + $ref: '#/responses/500' /repositories: get: summary: List all authorized repositories diff --git a/src/controller/project/metadata/controller.go b/src/controller/project/metadata/controller.go new file mode 100644 index 000000000..6aa9ebc8b --- /dev/null +++ b/src/controller/project/metadata/controller.go @@ -0,0 +1,65 @@ +// Copyright 2018 Project Harbor Authors +// +// 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 metadata + +import ( + "context" + "github.com/goharbor/harbor/src/pkg/project/metadata" +) + +// Ctl is the global controller instance +var Ctl = NewController() + +// Controller defines the operations that a project metadata controller should implement +type Controller interface { + // Add metadatas for project specified by projectID + Add(ctx context.Context, projectID int64, meta map[string]string) error + + // Delete metadatas whose keys are specified in parameter meta, if it is absent, delete all + Delete(ctx context.Context, projectID int64, meta ...string) error + + // Update metadatas + Update(ctx context.Context, projectID int64, meta map[string]string) error + + // Get metadatas whose keys are specified in parameter meta, if it is absent, get all + Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error) +} + +// NewController creates an instance of the default controller +func NewController() Controller { + return &controller{ + mgr: metadata.Mgr, + } +} + +type controller struct { + mgr metadata.Manager +} + +func (c *controller) Add(ctx context.Context, projectID int64, meta map[string]string) error { + return c.mgr.Add(ctx, projectID, meta) +} + +func (c *controller) Delete(ctx context.Context, projectID int64, meta ...string) error { + return c.mgr.Delete(ctx, projectID, meta...) +} + +func (c *controller) Update(ctx context.Context, projectID int64, meta map[string]string) error { + return c.mgr.Update(ctx, projectID, meta) +} + +func (c *controller) Get(ctx context.Context, projectID int64, meta ...string) (map[string]string, error) { + return c.mgr.Get(ctx, projectID, meta...) +} diff --git a/src/core/api/harborapi_test.go b/src/core/api/harborapi_test.go index 790349e90..074a3bb19 100644 --- a/src/core/api/harborapi_test.go +++ b/src/core/api/harborapi_test.go @@ -93,9 +93,6 @@ func init() { beego.BConfig.WebConfig.Session.SessionOn = true beego.TestBeegoInit(apppath) - beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &MetadataAPI{}, "get:Get") - beego.Router("/api/projects/:id([0-9]+)/metadatas/", &MetadataAPI{}, "post:Post") - beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete") beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping") // Charts are controlled under projects diff --git a/src/core/api/metadata.go b/src/core/api/metadata.go deleted file mode 100644 index c5a4951df..000000000 --- a/src/core/api/metadata.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2018 Project Harbor Authors -// -// 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" - "reflect" - "strconv" - "strings" - - "github.com/goharbor/harbor/src/common/models" - "github.com/goharbor/harbor/src/common/rbac" - "github.com/goharbor/harbor/src/lib/errors" - "github.com/goharbor/harbor/src/pkg/project/metadata" - "github.com/goharbor/harbor/src/pkg/scan/vuln" -) - -// MetadataAPI ... -type MetadataAPI struct { - BaseController - metaMgr metadata.Manager - project *models.Project - name string -} - -// Prepare ... -func (m *MetadataAPI) Prepare() { - m.BaseController.Prepare() - - m.metaMgr = metadata.Mgr - - id, err := m.GetInt64FromPath(":id") - if err != nil || id <= 0 { - text := "invalid project ID: " - if err != nil { - text += err.Error() - } else { - text += fmt.Sprintf("%d", id) - } - m.SendBadRequestError(errors.New(text)) - return - } - - project, err := m.ProjectCtl.Get(m.Context(), id) - if err != nil { - if errors.IsNotFoundErr(err) { - m.handleProjectNotFound(id) - } else { - m.ParseAndHandleError(fmt.Sprintf("failed to get project %d", id), err) - } - return - } - - m.project = project - - name := m.GetStringFromPath(":name") - if len(name) > 0 { - m.name = name - metas, err := m.metaMgr.Get(m.Context(), project.ProjectID, name) - if err != nil { - m.SendInternalServerError(fmt.Errorf("failed to get metadata of project %d: %v", project.ProjectID, err)) - return - } - if len(metas) == 0 { - m.SendNotFoundError(fmt.Errorf("metadata %s of project %d not found", name, project.ProjectID)) - return - } - } -} - -func (m *MetadataAPI) requireAccess(action rbac.Action) bool { - return m.RequireProjectAccess(m.project.ProjectID, action, rbac.ResourceMetadata) -} - -// Get ... -func (m *MetadataAPI) Get() { - if !m.requireAccess(rbac.ActionRead) { - return - } - - var metas map[string]string - var err error - if len(m.name) > 0 { - metas, err = m.metaMgr.Get(m.Context(), m.project.ProjectID, m.name) - } else { - metas, err = m.metaMgr.Get(m.Context(), m.project.ProjectID) - } - - if err != nil { - m.SendInternalServerError(fmt.Errorf("failed to get metadata %s of project %d: %v", m.name, m.project.ProjectID, err)) - return - } - m.Data["json"] = metas - m.ServeJSON() -} - -// Post ... -func (m *MetadataAPI) Post() { - if !m.requireAccess(rbac.ActionCreate) { - return - } - - var metas map[string]string - if err := m.DecodeJSONReq(&metas); err != nil { - m.SendBadRequestError(err) - return - } - - ms, err := validateProjectMetadata(metas) - if err != nil { - m.SendBadRequestError(err) - return - } - - if len(ms) != 1 { - m.SendBadRequestError(errors.New("invalid request: has no valid key/value pairs or has more than one valid key/value pairs")) - return - } - - keys := reflect.ValueOf(ms).MapKeys() - mts, err := m.metaMgr.Get(m.Context(), m.project.ProjectID, keys[0].String()) - if err != nil { - m.SendInternalServerError(fmt.Errorf("failed to get metadata for project %d: %v", m.project.ProjectID, err)) - return - } - - if len(mts) != 0 { - m.SendConflictError(errors.New("conflict metadata")) - return - } - - if err := m.metaMgr.Add(m.Context(), m.project.ProjectID, ms); err != nil { - m.SendInternalServerError(fmt.Errorf("failed to create metadata for project %d: %v", m.project.ProjectID, err)) - return - } - - m.Ctx.ResponseWriter.WriteHeader(http.StatusCreated) -} - -// Put ... -func (m *MetadataAPI) Put() { - if !m.requireAccess(rbac.ActionUpdate) { - return - } - - var metas map[string]string - if err := m.DecodeJSONReq(&metas); err != nil { - m.SendBadRequestError(err) - return - } - - meta, exist := metas[m.name] - if !exist { - m.SendBadRequestError(fmt.Errorf("must contains key %s", m.name)) - return - } - - ms, err := validateProjectMetadata(map[string]string{ - m.name: meta, - }) - if err != nil { - m.SendBadRequestError(err) - return - } - - if err := m.metaMgr.Update(m.Context(), m.project.ProjectID, map[string]string{ - m.name: ms[m.name], - }); err != nil { - m.SendInternalServerError(fmt.Errorf("failed to update metadata %s of project %d: %v", m.name, m.project.ProjectID, err)) - return - } -} - -// Delete ... -func (m *MetadataAPI) Delete() { - if !m.requireAccess(rbac.ActionDelete) { - return - } - - if err := m.metaMgr.Delete(m.Context(), m.project.ProjectID, m.name); err != nil { - m.SendInternalServerError(fmt.Errorf("failed to delete metadata %s of project %d: %v", m.name, m.project.ProjectID, err)) - return - } -} - -// validate metas and return a new map which contains the valid key/value pairs only -func validateProjectMetadata(metas map[string]string) (map[string]string, error) { - if len(metas) == 0 { - return nil, nil - } - - boolMetas := []string{ - models.ProMetaPublic, - models.ProMetaEnableContentTrust, - models.ProMetaPreventVul, - models.ProMetaAutoScan} - - for _, boolMeta := range boolMetas { - value, exist := metas[boolMeta] - if exist { - b, err := strconv.ParseBool(value) - if err != nil { - return nil, fmt.Errorf("failed to parse %s to bool: %v", value, err) - } - metas[boolMeta] = strconv.FormatBool(b) - } - } - - value, exist := metas[models.ProMetaSeverity] - if exist { - severity := vuln.ParseSeverityVersion3(strings.ToLower(value)) - if severity == vuln.Unknown { - return nil, fmt.Errorf("invalid severity %s", value) - } - - metas[models.ProMetaSeverity] = strings.ToLower(severity.String()) - } - - return metas, nil -} diff --git a/src/core/api/metadata_test.go b/src/core/api/metadata_test.go deleted file mode 100644 index 2fd6d823a..000000000 --- a/src/core/api/metadata_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018 Project Harbor Authors -// -// 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" - "testing" - - "github.com/goharbor/harbor/src/common/models" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestValidateProjectMetadata(t *testing.T) { - var metas map[string]string - - // nil metas - ms, err := validateProjectMetadata(metas) - require.Nil(t, err) - require.Nil(t, ms) - - // valid key, invalid value(bool) - metas = map[string]string{ - models.ProMetaPublic: "invalid_value", - } - ms, err = validateProjectMetadata(metas) - require.NotNil(t, err) - - // valid key/value(bool) - metas = map[string]string{ - models.ProMetaPublic: "1", - } - ms, err = validateProjectMetadata(metas) - require.Nil(t, err) - assert.Equal(t, "true", ms[models.ProMetaPublic]) - - // valid key, invalid value(string) - metas = map[string]string{ - models.ProMetaSeverity: "invalid_value", - } - ms, err = validateProjectMetadata(metas) - require.NotNil(t, err) - - // valid key, valid value(string) - metas = map[string]string{ - models.ProMetaSeverity: "High", - } - ms, err = validateProjectMetadata(metas) - require.Nil(t, err) - assert.Equal(t, "high", ms[models.ProMetaSeverity]) -} - -func TestMetaAPI(t *testing.T) { - client := newHarborAPI() - - // non-exist project, it should return 401 if user is not logged in. - code, _, err := client.PostMeta(*unknownUsr, int64(1000), nil) - require.Nil(t, err) - assert.Equal(t, http.StatusUnauthorized, code) - - // non-login - code, _, err = client.PostMeta(*unknownUsr, int64(1), nil) - require.Nil(t, err) - assert.Equal(t, http.StatusUnauthorized, code) - - // test post - code, _, err = client.PostMeta(*admin, int64(1), map[string]string{ - models.ProMetaAutoScan: "true", - }) - require.Nil(t, err) - assert.Equal(t, http.StatusCreated, code) - - // test get - code, metas, err := client.GetMeta(*admin, int64(1), models.ProMetaAutoScan) - require.Nil(t, err) - assert.Equal(t, http.StatusOK, code) - assert.Equal(t, "true", metas[models.ProMetaAutoScan]) - - // test put - code, _, err = client.PutMeta(*admin, int64(1), models.ProMetaAutoScan, - map[string]string{ - models.ProMetaAutoScan: "false", - }) - require.Nil(t, err) - assert.Equal(t, http.StatusOK, code) - - code, metas, err = client.GetMeta(*admin, int64(1), models.ProMetaAutoScan) - require.Nil(t, err) - assert.Equal(t, http.StatusOK, code) - assert.Equal(t, "false", metas[models.ProMetaAutoScan]) - - // test delete - code, _, err = client.DeleteMeta(*admin, int64(1), models.ProMetaAutoScan) - require.Nil(t, err) - assert.Equal(t, http.StatusOK, code) - - code, metas, err = client.GetMeta(*admin, int64(1), models.ProMetaAutoScan) - require.Nil(t, err) - assert.Equal(t, http.StatusNotFound, code) -} diff --git a/src/pkg/project/metadata/dao/dao.go b/src/pkg/project/metadata/dao/dao.go index 4fb5910fc..45c0f6da0 100644 --- a/src/pkg/project/metadata/dao/dao.go +++ b/src/pkg/project/metadata/dao/dao.go @@ -61,7 +61,14 @@ func (d *dao) Create(ctx context.Context, projectID int64, name, value string) ( UpdateTime: now, } - return o.Insert(md) + id, err := o.Insert(md) + if err != nil { + if e := orm.AsConflictError(err, "metadata %s already exists for project %d", name, projectID); e != nil { + err = e + } + return 0, err + } + return id, nil } // Delete delete metadata interfaces filtered the query diff --git a/src/server/v2.0/handler/handler.go b/src/server/v2.0/handler/handler.go index a80333b94..6e347a071 100644 --- a/src/server/v2.0/handler/handler.go +++ b/src/server/v2.0/handler/handler.go @@ -63,6 +63,7 @@ func New() http.Handler { UserAPI: newUsersAPI(), HealthAPI: newHealthAPI(), StatisticAPI: newStatisticAPI(), + ProjectMetadataAPI: newProjectMetadaAPI(), }) if err != nil { log.Fatal(err) diff --git a/src/server/v2.0/handler/project_metadata.go b/src/server/v2.0/handler/project_metadata.go new file mode 100644 index 000000000..be7a0ff45 --- /dev/null +++ b/src/server/v2.0/handler/project_metadata.go @@ -0,0 +1,160 @@ +// Copyright Project Harbor Authors +// +// 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 handler + +import ( + "context" + "github.com/go-openapi/runtime/middleware" + "github.com/goharbor/harbor/src/common/models" + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/controller/project" + "github.com/goharbor/harbor/src/controller/project/metadata" + "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/pkg/scan/vuln" + operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/project_metadata" + "strconv" + "strings" +) + +func newProjectMetadaAPI() *projectMetadataAPI { + return &projectMetadataAPI{ + ctl: metadata.Ctl, + proCtl: project.Ctl, + } +} + +type projectMetadataAPI struct { + BaseAPI + ctl metadata.Controller + proCtl project.Controller +} + +func (p *projectMetadataAPI) AddProjectMetadatas(ctx context.Context, params operation.AddProjectMetadatasParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceMetadata); err != nil { + return p.SendError(ctx, err) + } + metadata := params.Metadata + metadata, err := p.validate(metadata) + if err != nil { + return p.SendError(ctx, err) + } + project, err := p.proCtl.Get(ctx, projectNameOrID) + if err != nil { + return p.SendError(ctx, err) + } + if err = p.ctl.Add(ctx, project.ProjectID, metadata); err != nil { + return p.SendError(ctx, err) + } + return operation.NewAddProjectMetadatasOK() +} + +func (p *projectMetadataAPI) ListProjectMetadatas(ctx context.Context, params operation.ListProjectMetadatasParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceMetadata); err != nil { + return p.SendError(ctx, err) + } + project, err := p.proCtl.Get(ctx, projectNameOrID) + if err != nil { + return p.SendError(ctx, err) + } + metadata, err := p.ctl.Get(ctx, project.ProjectID) + if err != nil { + return p.SendError(ctx, err) + } + return operation.NewListProjectMetadatasOK().WithPayload(metadata) +} + +func (p *projectMetadataAPI) DeleteProjectMetadata(ctx context.Context, params operation.DeleteProjectMetadataParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete, rbac.ResourceMetadata); err != nil { + return p.SendError(ctx, err) + } + project, err := p.proCtl.Get(ctx, projectNameOrID) + if err != nil { + return p.SendError(ctx, err) + } + if err = p.ctl.Delete(ctx, project.ProjectID, params.MetaName); err != nil { + return p.SendError(ctx, err) + } + return operation.NewDeleteProjectMetadataOK() +} + +func (p *projectMetadataAPI) GetProjectMetadata(ctx context.Context, params operation.GetProjectMetadataParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceMetadata); err != nil { + return p.SendError(ctx, err) + } + project, err := p.proCtl.Get(ctx, projectNameOrID) + if err != nil { + return p.SendError(ctx, err) + } + metadata, err := p.ctl.Get(ctx, project.ProjectID, params.MetaName) + if err != nil { + return p.SendError(ctx, err) + } + return operation.NewGetProjectMetadataOK().WithPayload(metadata) +} + +func (p *projectMetadataAPI) UpdateProjectMetadata(ctx context.Context, params operation.UpdateProjectMetadataParams) middleware.Responder { + projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName) + if err := p.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate, rbac.ResourceMetadata); err != nil { + return p.SendError(ctx, err) + } + metadata := map[string]string{ + params.MetaName: params.Metadata[params.MetaName], + } + metadata, err := p.validate(metadata) + if err != nil { + return p.SendError(ctx, err) + } + project, err := p.proCtl.Get(ctx, projectNameOrID) + if err != nil { + return p.SendError(ctx, err) + } + if err = p.ctl.Update(ctx, project.ProjectID, metadata); err != nil { + return p.SendError(ctx, err) + } + return operation.NewUpdateProjectMetadataOK() +} + +func (p *projectMetadataAPI) validate(metas map[string]string) (map[string]string, error) { + if len(metas) != 1 { + return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("only allow one key/value pair") + } + + key, value := "", "" + for key, value = range metas { + } + + switch key { + case models.ProMetaPublic, models.ProMetaEnableContentTrust, + models.ProMetaPreventVul, models.ProMetaAutoScan: + v, err := strconv.ParseBool(value) + if err != nil { + return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value) + } + metas[key] = strconv.FormatBool(v) + case models.ProMetaSeverity: + severity := vuln.ParseSeverityVersion3(strings.ToLower(value)) + if severity == vuln.Unknown { + return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid value: %s", value) + } + metas[models.ProMetaSeverity] = strings.ToLower(severity.String()) + default: + return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("invalid key: %s", key) + } + return metas, nil +} diff --git a/src/server/v2.0/route/legacy.go b/src/server/v2.0/route/legacy.go index b9f540467..094e949d5 100755 --- a/src/server/v2.0/route/legacy.go +++ b/src/server/v2.0/route/legacy.go @@ -24,8 +24,6 @@ import ( func registerLegacyRoutes() { version := APIVersion beego.Router("/api/"+version+"/email/ping", &api.EmailAPI{}, "post:Ping") - beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get") - beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post") // APIs for chart repository if config.WithChartMuseum() {