diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0561a80fd..55a08ecc1 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -656,11 +656,6 @@ paths: format: int32 required: true description: Relevant project ID. - - name: detail - in: query - type: boolean - required: false - description: Get detail info or not. - name: q in: query type: string @@ -682,7 +677,7 @@ paths: - Products responses: 200: - description: If detail is false, the response body is a string array which contains the names of repositories, or the response body contains an object array as described in schema. + description: Get repositories successfully. schema: type: array items: @@ -702,6 +697,30 @@ paths: description: Project ID does not exist. 500: description: Unexpected internal errors. + /repositories/{repo_name}: + delete: + summary: Delete a repository. + description: | + This endpoint let user delete a repository with name. + parameters: + - name: repo_name + in: path + type: string + required: true + description: The name of repository which will be deleted. + tags: + - Products + responses: + 200: + description: Delete successfully. + 400: + description: Invalid repo_name. + 401: + description: Unauthorized. + 404: + description: Repository not found. + 403: + description: Forbidden. /repositories/{repo_name}/tags/{tag}: delete: summary: Delete a tag in a repository. @@ -742,45 +761,17 @@ paths: type: string required: true description: Relevant repository name. - - name: detail - in: query - type: boolean - required: false - description: If detail is true, the manifests is returned too. tags: - Products responses: 200: - description: If detail is false, the response body is a string array, or the response body contains the manifest informations as described in schema. + description: Get tags successfully. schema: type: array items: $ref: '#/definitions/DetailedTag' 500: description: Unexpected internal errors. - delete: - summary: Delete all tags of a repository. - description: | - This endpoint let user delete all tags with repo name. - parameters: - - name: repo_name - in: path - type: string - required: true - description: The name of repository which will be deleted. - tags: - - Products - responses: - 200: - description: Delete successfully. - 400: - description: Invalid repo_name. - 401: - description: Unauthorized. - 404: - description: Repository not found. - 403: - description: Forbidden. /repositories/{repo_name}/tags/{tag}/manifest: get: summary: Get manifests of a relevant repository. @@ -851,16 +842,11 @@ paths: format: int32 required: false description: The number of the requested public repositories, default is 10 if not provided. - - name: detail - in: query - type: boolean - required: false - description: Get detail info or not. tags: - Products responses: 200: - description: If detail is true, the response is described as the schema, or the response contains a TopRepo array. + description: Get popular repositories successfully. schema: type: array items: diff --git a/src/common/api/base.go b/src/common/api/base.go index 40f4b98b4..879656adb 100644 --- a/src/common/api/base.go +++ b/src/common/api/base.go @@ -39,6 +39,36 @@ type BaseAPI struct { beego.Controller } +// HandleNotFound ... +func (b *BaseAPI) HandleNotFound(text string) { + log.Info(text) + b.RenderError(http.StatusNotFound, text) +} + +// HandleUnauthorized ... +func (b *BaseAPI) HandleUnauthorized() { + log.Info("unauthorized") + b.RenderError(http.StatusUnauthorized, "") +} + +// HandleForbidden ... +func (b *BaseAPI) HandleForbidden(username string) { + log.Info("forbidden: %s", username) + b.RenderError(http.StatusForbidden, "") +} + +// HandleBadRequest ... +func (b *BaseAPI) HandleBadRequest(text string) { + log.Info(text) + b.RenderError(http.StatusBadRequest, text) +} + +// HandleInternalServerError ... +func (b *BaseAPI) HandleInternalServerError(text string) { + log.Error(text) + b.RenderError(http.StatusInternalServerError, "") +} + // Render returns nil as it won't render template func (b *BaseAPI) Render() error { return nil @@ -83,6 +113,7 @@ func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) { } // ValidateUser checks if the request triggered by a valid user +// TODO remove func (b *BaseAPI) ValidateUser() int { userID, needsCheck, ok := b.GetUserIDForRequest() if !ok { diff --git a/src/common/dao/repository.go b/src/common/dao/repository.go index b0d5c1f24..6191bccbc 100644 --- a/src/common/dao/repository.go +++ b/src/common/dao/repository.go @@ -103,28 +103,15 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) { return repos, err } -//GetTopRepos returns the most popular repositories -func GetTopRepos(userID int, count int) ([]*models.RepoRecord, error) { - sql := - `select r.repository_id, r.name, - r.project_id, r.description, r.pull_count, - r.star_count, r.creation_time, r.update_time - from repository r - inner join project p on r.project_id = p.project_id - where ( - p.deleted = 0 and ( - p.public = 1 or ( - ? <> ? and ( - exists ( - select 1 from user u - where u.user_id = ? and u.sysadmin_flag = 1 - ) or exists ( - select 1 from project_member pm - where pm.project_id = p.project_id and pm.user_id = ? - ))))) - order by r.pull_count desc, r.name limit ?` +//GetTopRepos returns the most popular repositories whose project ID is +// in projectIDs +func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) { repositories := []*models.RepoRecord{} - _, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, userID, count).QueryRows(&repositories) + _, err := GetOrmer().QueryTable(&models.RepoRecord{}). + Filter("project_id__in", projectIDs). + OrderBy("-pull_count"). + Limit(n). + All(&repositories) return repositories, err } diff --git a/src/common/dao/repository_test.go b/src/common/dao/repository_test.go index e13cee3ef..94a769c3e 100644 --- a/src/common/dao/repository_test.go +++ b/src/common/dao/repository_test.go @@ -17,7 +17,6 @@ package dao import ( "fmt" "testing" - "time" "github.com/stretchr/testify/require" "github.com/vmware/harbor/src/common/models" @@ -170,47 +169,25 @@ func TestGetTopRepos(t *testing.T) { require.NoError(GetOrmer().Rollback()) }() - admin, err := GetUser(models.User{Username: "admin"}) - require.NoError(err) - - user := models.User{ - Username: "user", - Password: "user", - Email: "user@test.com", - } - userID, err := Register(user) - require.NoError(err) - user.UserID = int(userID) - - // - // public project with 1 repository - // non-public project with 2 repositories visible by admin - // non-public project with 1 repository visible by admin and user - // deleted public project with 1 repository - // + projectIDs := []int64{} project1 := models.Project{ - OwnerID: admin.UserID, - Name: "project1", - CreationTime: time.Now(), - OwnerName: admin.Username, - Public: 0, + OwnerID: 1, + Name: "project1", + Public: 0, } project1.ProjectID, err = AddProject(project1) require.NoError(err) + projectIDs = append(projectIDs, project1.ProjectID) project2 := models.Project{ - OwnerID: user.UserID, - Name: "project2", - CreationTime: time.Now(), - OwnerName: user.Username, - Public: 0, + OwnerID: 1, + Name: "project2", + Public: 0, } project2.ProjectID, err = AddProject(project2) require.NoError(err) - - err = AddRepository(*repository) - require.NoError(err) + projectIDs = append(projectIDs, project2.ProjectID) repository1 := &models.RepoRecord{ Name: fmt.Sprintf("%v/repository1", project1.Name), @@ -219,8 +196,6 @@ func TestGetTopRepos(t *testing.T) { err = AddRepository(*repository1) require.NoError(err) require.NoError(IncreasePullCount(repository1.Name)) - repository1, err = GetRepositoryByName(repository1.Name) - require.NoError(err) repository2 := &models.RepoRecord{ Name: fmt.Sprintf("%v/repository2", project1.Name), @@ -230,8 +205,6 @@ func TestGetTopRepos(t *testing.T) { require.NoError(err) require.NoError(IncreasePullCount(repository2.Name)) require.NoError(IncreasePullCount(repository2.Name)) - repository2, err = GetRepositoryByName(repository2.Name) - require.NoError(err) repository3 := &models.RepoRecord{ Name: fmt.Sprintf("%v/repository3", project2.Name), @@ -242,49 +215,11 @@ func TestGetTopRepos(t *testing.T) { require.NoError(IncreasePullCount(repository3.Name)) require.NoError(IncreasePullCount(repository3.Name)) require.NoError(IncreasePullCount(repository3.Name)) - repository3, err = GetRepositoryByName(repository3.Name) - require.NoError(err) - deletedPublicProject := models.Project{ - OwnerID: admin.UserID, - Name: "public-deleted", - CreationTime: time.Now(), - OwnerName: admin.Username, - Public: 1, - } - deletedPublicProject.ProjectID, err = AddProject(deletedPublicProject) - require.NoError(err) - deletedPublicRepository1 := &models.RepoRecord{ - Name: fmt.Sprintf("%v/repository1", deletedPublicProject.Name), - ProjectID: deletedPublicProject.ProjectID, - } - err = AddRepository(*deletedPublicRepository1) - require.NoError(err) - err = DeleteProject(deletedPublicProject.ProjectID) - require.NoError(err) - - var topRepos []*models.RepoRecord - - // anonymous should retrieve public non-deleted repositories - topRepos, err = GetTopRepos(NonExistUserID, 100) - require.NoError(err) - require.Len(topRepos, 1) - require.Equal(topRepos[0].Name, repository.Name) - - // admin should retrieve all repositories - topRepos, err = GetTopRepos(admin.UserID, 100) - require.NoError(err) - require.Len(topRepos, 4) - - // user should retrieve visible repositories - topRepos, err = GetTopRepos(user.UserID, 100) - require.NoError(err) - require.Len(topRepos, 2) - - // limit by count - topRepos, err = GetTopRepos(admin.UserID, 3) + topRepos, err := GetTopRepos(projectIDs, 100) require.NoError(err) require.Len(topRepos, 3) + require.Equal(topRepos[0].Name, repository3.Name) } func TestGetTotalOfRepositoriesByProject(t *testing.T) { diff --git a/src/common/models/toprepo.go b/src/common/models/toprepo.go deleted file mode 100644 index 14aaf9f9e..000000000 --- a/src/common/models/toprepo.go +++ /dev/null @@ -1,22 +0,0 @@ -// 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 models - -// TopRepo holds information about repository that accessed most -type TopRepo struct { - RepoName string `json:"name"` - AccessCount int64 `json:"count"` - // Creator string `json:"creator"` -} diff --git a/src/common/security/rbac/context_test.go b/src/common/security/rbac/context_test.go index ef297c1be..55832a50e 100644 --- a/src/common/security/rbac/context_test.go +++ b/src/common/security/rbac/context_test.go @@ -33,6 +33,18 @@ func (f *fakePM) IsPublic(projectIDOrName interface{}) bool { func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) []int { return f.roles[projectIDOrName.(string)] } +func (f *fakePM) Get(projectIDOrName interface{}) *models.Project { + return nil +} +func (f *fakePM) Exist(projectIDOrName interface{}) bool { + return false +} +func (f *fakePM) GetPublic() []models.Project { + return []models.Project{} +} +func (f *fakePM) GetByMember(username string) []models.Project { + return []models.Project{} +} func TestIsAuthenticated(t *testing.T) { // unauthenticated diff --git a/src/ui/api/base.go b/src/ui/api/base.go new file mode 100644 index 000000000..0c60425dc --- /dev/null +++ b/src/ui/api/base.go @@ -0,0 +1,54 @@ +// 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 ( + "net/http" + + "github.com/vmware/harbor/src/common/api" + "github.com/vmware/harbor/src/common/security" + "github.com/vmware/harbor/src/common/utils/log" + "github.com/vmware/harbor/src/ui/filter" + "github.com/vmware/harbor/src/ui/projectmanager" +) + +// BaseController ... +type BaseController struct { + api.BaseAPI + // SecurityCtx is the security context used to authN &authZ + SecurityCtx security.Context + // ProjectMgr is the project manager which abstracts the operations + // related to projects + ProjectMgr projectmanager.ProjectManager +} + +// Prepare inits security context and project manager from beego +// context +func (b *BaseController) Prepare() { + ok := false + ctx := b.Ctx.Input.GetData(filter.HarborSecurityContext) + b.SecurityCtx, ok = ctx.(security.Context) + if !ok { + log.Error("failed to get security context") + b.CustomAbort(http.StatusInternalServerError, "") + } + + pm := b.Ctx.Input.GetData(filter.HarborProjectManager) + b.ProjectMgr, ok = pm.(projectmanager.ProjectManager) + if !ok { + log.Error("failed to get project manager") + b.CustomAbort(http.StatusInternalServerError, "") + } +} diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 4ab7f368d..56cc0cf8d 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -30,6 +30,7 @@ import ( "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/ui/config" + "github.com/vmware/harbor/src/ui/filter" "github.com/vmware/harbor/tests/apitests/apilib" // "strconv" // "strings" @@ -86,6 +87,8 @@ func init() { beego.BConfig.WebConfig.Session.SessionOn = true beego.TestBeegoInit(apppath) + beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) + beego.Router("/api/search/", &SearchAPI{}) beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head") beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get") @@ -473,8 +476,8 @@ func (a testapi) PutProjectMember(authInfo usrInfo, projectID string, userID str //-------------------------Repositories Test---------------------------------------// //Return relevant repos of projectID -func (a testapi) GetRepos(authInfo usrInfo, projectID, - keyword, detail string) (int, interface{}, error) { +func (a testapi) GetRepos(authInfo usrInfo, projectID, keyword string) ( + int, interface{}, error) { _sling := sling.New().Get(a.basePath) path := "/api/repositories/" @@ -483,13 +486,11 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID, type QueryParams struct { ProjectID string `url:"project_id"` - Detail string `url:"detail"` Keyword string `url:"q"` } _sling = _sling.QueryStruct(&QueryParams{ ProjectID: projectID, - Detail: detail, Keyword: keyword, }) code, body, err := request(_sling, jsonAcceptHeader, authInfo) @@ -498,15 +499,7 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID, } if code == http.StatusOK { - if detail == "1" || detail == "true" { - repositories := []repoResp{} - if err = json.Unmarshal(body, &repositories); err != nil { - return 0, nil, err - } - return code, repositories, nil - } - - repositories := []string{} + repositories := []repoResp{} if err = json.Unmarshal(body, &repositories); err != nil { return 0, nil, err } @@ -517,21 +510,13 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID, } //Get tags of a relevant repository -func (a testapi) GetReposTags(authInfo usrInfo, repoName, - detail string) (int, interface{}, error) { +func (a testapi) GetReposTags(authInfo usrInfo, repoName string) (int, interface{}, error) { _sling := sling.New().Get(a.basePath) path := fmt.Sprintf("/api/repositories/%s/tags", repoName) _sling = _sling.Path(path) - type QueryParams struct { - Detail string `url:"detail"` - } - - _sling = _sling.QueryStruct(&QueryParams{ - Detail: detail, - }) httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo) if err != nil { return 0, nil, err @@ -541,15 +526,7 @@ func (a testapi) GetReposTags(authInfo usrInfo, repoName, return httpStatusCode, body, nil } - if detail == "true" || detail == "1" { - result := []detailedTagResp{} - if err := json.Unmarshal(body, &result); err != nil { - return 0, nil, err - } - return http.StatusOK, result, nil - } - - result := []string{} + result := []tagResp{} if err := json.Unmarshal(body, &result); err != nil { return 0, nil, err } @@ -569,8 +546,7 @@ func (a testapi) GetReposManifests(authInfo usrInfo, repoName string, tag string } //Get public repositories which are accessed most -func (a testapi) GetReposTop(authInfo usrInfo, count, - detail string) (int, interface{}, error) { +func (a testapi) GetReposTop(authInfo usrInfo, count string) (int, interface{}, error) { _sling := sling.New().Get(a.basePath) path := "/api/repositories/top" @@ -578,13 +554,11 @@ func (a testapi) GetReposTop(authInfo usrInfo, count, _sling = _sling.Path(path) type QueryParams struct { - Count string `url:"count"` - Detail string `url:"detail"` + Count string `url:"count"` } _sling = _sling.QueryStruct(&QueryParams{ - Count: count, - Detail: detail, + Count: count, }) code, body, err := request(_sling, jsonAcceptHeader, authInfo) if err != nil { @@ -595,15 +569,7 @@ func (a testapi) GetReposTop(authInfo usrInfo, count, return code, body, err } - if detail == "true" || detail == "1" { - result := []*repoResp{} - if err = json.Unmarshal(body, &result); err != nil { - return 0, nil, err - } - return http.StatusOK, result, nil - } - - result := []*models.TopRepo{} + result := []*repoResp{} if err = json.Unmarshal(body, &result); err != nil { return 0, nil, err } diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 933a01341..f16ef090f 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -23,23 +23,20 @@ import ( "github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema2" - "github.com/vmware/harbor/src/common/api" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/notary" "github.com/vmware/harbor/src/common/utils/registry" - "github.com/vmware/harbor/src/common/utils/registry/auth" registry_error "github.com/vmware/harbor/src/common/utils/registry/error" "github.com/vmware/harbor/src/ui/config" - svc_utils "github.com/vmware/harbor/src/ui/service/utils" ) // RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put // in the query string as the web framework can not parse the URL if it contains veriadic sectors. type RepositoryAPI struct { - api.BaseAPI + BaseController } type repoResp struct { @@ -54,7 +51,7 @@ type repoResp struct { UpdateTime time.Time `json:"update_time"` } -type detailedTagResp struct { +type tagResp struct { Tag string `json:"tag"` Manifest interface{} `json:"manifest"` } @@ -68,50 +65,40 @@ type manifestResp struct { func (ra *RepositoryAPI) Get() { projectID, err := ra.GetInt64("project_id") if err != nil || projectID <= 0 { - ra.CustomAbort(http.StatusBadRequest, "invalid project_id") + ra.HandleBadRequest(fmt.Sprintf("invalid project_id %s", ra.GetString("project_id"))) + return } - project, err := dao.GetProjectByID(projectID) - if err != nil { - log.Errorf("failed to get project %d: %v", projectID, err) - ra.CustomAbort(http.StatusInternalServerError, "") + if !ra.ProjectMgr.Exist(projectID) { + ra.HandleNotFound(fmt.Sprintf("project %d not found", projectID)) + return } - if project == nil { - ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %d not found", projectID)) - } - - if project.Public == 0 { - var userID int - - if svc_utils.VerifySecret(ra.Ctx.Request, config.JobserviceSecret()) { - userID = 1 - } else { - userID = ra.ValidateUser() - } - - if !checkProjectPermission(userID, projectID) { - ra.CustomAbort(http.StatusForbidden, "") + if !ra.SecurityCtx.HasReadPerm(projectID) { + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return } + ra.HandleForbidden(ra.SecurityCtx.GetUsername()) + return } keyword := ra.GetString("q") total, err := dao.GetTotalOfRepositoriesByProject(projectID, keyword) if err != nil { - log.Errorf("failed to get total of repositories of project %d: %v", projectID, err) - ra.CustomAbort(http.StatusInternalServerError, "") + ra.HandleInternalServerError(fmt.Sprintf("failed to get total of repositories of project %d: %v", + projectID, err)) + return } page, pageSize := ra.GetPaginationParams() - detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true" - repositories, err := getRepositories(projectID, - keyword, pageSize, pageSize*(page-1), detail) + keyword, pageSize, pageSize*(page-1)) if err != nil { - log.Errorf("failed to get repository: %v", err) - ra.CustomAbort(http.StatusInternalServerError, "") + ra.HandleInternalServerError(fmt.Sprintf("failed to get repository: %v", err)) + return } ra.SetPaginationHeader(total, page, pageSize) @@ -120,21 +107,12 @@ func (ra *RepositoryAPI) Get() { } func getRepositories(projectID int64, keyword string, - limit, offset int64, detail bool) (interface{}, error) { + limit, offset int64) ([]*repoResp, error) { repositories, err := dao.GetRepositoriesByProject(projectID, keyword, limit, offset) if err != nil { return nil, err } - //keep compatibility with old API - if !detail { - result := []string{} - for _, repository := range repositories { - result = append(result, repository.Name) - } - return result, nil - } - return populateTagsCount(repositories) } @@ -168,19 +146,19 @@ func (ra *RepositoryAPI) Delete() { repoName := ra.GetString(":splat") projectName, _ := utils.ParseRepository(repoName) - project, err := dao.GetProjectByName(projectName) - if err != nil { - log.Errorf("failed to get project %s: %v", projectName, err) - ra.CustomAbort(http.StatusInternalServerError, "") + if !ra.ProjectMgr.Exist(projectName) { + ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName)) + return } - if project == nil { - ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName)) + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return } - userID := ra.ValidateUser() - if !hasProjectAdminRole(userID, project.ProjectID) { - ra.CustomAbort(http.StatusForbidden, "") + if !ra.SecurityCtx.HasAllPerm(projectName) { + ra.HandleForbidden(ra.SecurityCtx.GetUsername()) + return } rc, err := ra.initRepositoryClient(repoName) @@ -212,18 +190,11 @@ func (ra *RepositoryAPI) Delete() { tags = append(tags, tag) } - user, _, ok := ra.Ctx.Request.BasicAuth() - if !ok { - user, err = ra.getUsername() - if err != nil { - log.Errorf("failed to get user: %v", err) - } - } - if config.WithNotary() { var digest string signedTags := make(map[string]struct{}) - targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), user, repoName) + targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), + ra.SecurityCtx.GetUsername(), repoName) if err != nil { log.Errorf("Failed to get Notary targets for repository: %s, error: %v", repoName, err) log.Warningf("Failed to check signature status of repository: %s for deletion, there maybe orphaned targets in Notary.", repoName) @@ -243,7 +214,7 @@ func (ra *RepositoryAPI) Delete() { ra.CustomAbort(http.StatusInternalServerError, err.Error()) } log.Debugf("Tag: %s, digest: %s", t, digest) - if _, ok = signedTags[digest]; ok { + if _, ok := signedTags[digest]; ok { log.Errorf("Found signed tag, repostory: %s, tag: %s, deletion will be canceled", repoName, t) ra.CustomAbort(http.StatusPreconditionFailed, fmt.Sprintf("tag %s is signed", t)) } @@ -265,13 +236,9 @@ func (ra *RepositoryAPI) Delete() { go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete) go func(tag string) { - project, err := dao.GetProjectByName(projectName) - if err != nil { - log.Errorf("failed to get project by name %s: %v", projectName, err) - return - } + project := ra.ProjectMgr.Get(projectName) if err := dao.AddAccessLog(models.AccessLog{ - Username: user, + Username: ra.SecurityCtx.GetUsername(), ProjectID: project.ProjectID, RepoName: repoName, RepoTag: tag, @@ -296,32 +263,23 @@ func (ra *RepositoryAPI) Delete() { } } -type tag struct { - Name string `json:"name"` - Tags []string `json:"tags"` -} - // GetTags returns tags of a repository func (ra *RepositoryAPI) GetTags() { repoName := ra.GetString(":splat") - detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true" projectName, _ := utils.ParseRepository(repoName) - project, err := dao.GetProjectByName(projectName) - if err != nil { - log.Errorf("failed to get project %s: %v", projectName, err) - ra.CustomAbort(http.StatusInternalServerError, "") + if !ra.ProjectMgr.Exist(projectName) { + ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName)) + return } - if project == nil { - ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName)) - } - - if project.Public == 0 { - userID := ra.ValidateUser() - if !checkProjectPermission(userID, project.ProjectID) { - ra.CustomAbort(http.StatusForbidden, "") + if !ra.SecurityCtx.HasReadPerm(projectName) { + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return } + ra.HandleForbidden(ra.SecurityCtx.GetUsername()) + return } client, err := ra.initRepositoryClient(repoName) @@ -340,13 +298,7 @@ func (ra *RepositoryAPI) GetTags() { ra.CustomAbort(regErr.StatusCode, regErr.Detail) } - if !detail { - ra.Data["json"] = tags - ra.ServeJSON() - return - } - - result := []detailedTagResp{} + result := []tagResp{} for _, tag := range tags { manifest, err := getManifest(client, tag, "v1") @@ -359,7 +311,7 @@ func (ra *RepositoryAPI) GetTags() { ra.CustomAbort(http.StatusInternalServerError, "internal error") } - result = append(result, detailedTagResp{ + result = append(result, tagResp{ Tag: tag, Manifest: manifest.Manifest, }) @@ -367,7 +319,6 @@ func (ra *RepositoryAPI) GetTags() { ra.Data["json"] = result ra.ServeJSON() - } func listTag(client *registry.Repository) ([]string, error) { @@ -409,21 +360,19 @@ func (ra *RepositoryAPI) GetManifests() { } projectName, _ := utils.ParseRepository(repoName) - project, err := dao.GetProjectByName(projectName) - if err != nil { - log.Errorf("failed to get project %s: %v", projectName, err) - ra.CustomAbort(http.StatusInternalServerError, "") + if !ra.ProjectMgr.Exist(projectName) { + ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName)) + return } - if project == nil { - ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName)) - } - - if project.Public == 0 { - userID := ra.ValidateUser() - if !checkProjectPermission(userID, project.ProjectID) { - ra.CustomAbort(http.StatusForbidden, "") + if !ra.SecurityCtx.HasReadPerm(projectName) { + if !ra.SecurityCtx.IsAuthenticated() { + ra.HandleUnauthorized() + return } + + ra.HandleForbidden(ra.SecurityCtx.GetUsername()) + return } rc, err := ra.initRepositoryClient(repoName) @@ -494,55 +443,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo return nil, err } - verify, err := config.VerifyRemoteCert() - if err != nil { - return nil, err - } - - username, password, ok := ra.Ctx.Request.BasicAuth() - if ok { - return newRepositoryClient(endpoint, !verify, username, password, - repoName, "repository", repoName, "pull", "push", "*") - } - - username, err = ra.getUsername() - if err != nil { - return nil, err - } - - return NewRepositoryClient(endpoint, !verify, username, repoName, - "repository", repoName, "pull", "push", "*") -} - -func (ra *RepositoryAPI) getUsername() (string, error) { - // get username from session - sessionUsername := ra.GetSession("username") - if sessionUsername != nil { - username, ok := sessionUsername.(string) - if ok { - return username, nil - } - } - - // if username does not exist in session, try to get userId from sessiion - // and then get username from DB according to the userId - sessionUserID := ra.GetSession("userId") - if sessionUserID != nil { - userID, ok := sessionUserID.(int) - if ok { - u := models.User{ - UserID: userID, - } - user, err := dao.GetUser(u) - if err != nil { - return "", err - } - - return user.Username, nil - } - } - - return "", nil + return NewRepositoryClient(endpoint, true, ra.SecurityCtx.GetUsername(), + repoName, "repository", repoName, "pull", "push", "*") } //GetTopRepos returns the most populor repositories @@ -552,33 +454,23 @@ func (ra *RepositoryAPI) GetTopRepos() { ra.CustomAbort(http.StatusBadRequest, "invalid count") } - userID, _, ok := ra.GetUserIDForRequest() - if !ok { - userID = dao.NonExistUserID + projectIDs := []int64{} + projects := ra.ProjectMgr.GetPublic() + if ra.SecurityCtx.IsAuthenticated() { + projects = append(projects, ra.ProjectMgr.GetByMember( + ra.SecurityCtx.GetUsername())...) } - repos, err := dao.GetTopRepos(userID, count) + for _, project := range projects { + projectIDs = append(projectIDs, project.ProjectID) + } + + repos, err := dao.GetTopRepos(projectIDs, count) if err != nil { log.Errorf("failed to get top repos: %v", err) ra.CustomAbort(http.StatusInternalServerError, "internal server error") } - detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true" - if !detail { - result := []*models.TopRepo{} - - for _, repo := range repos { - result = append(result, &models.TopRepo{ - RepoName: repo.Name, - AccessCount: repo.PullCount, - }) - } - - ra.Data["json"] = result - ra.ServeJSON() - return - } - result, err := populateTagsCount(repos) if err != nil { log.Errorf("failed to popultate tags count to repositories: %v", err) @@ -591,15 +483,10 @@ func (ra *RepositoryAPI) GetTopRepos() { //GetSignatures returns signatures of a repository func (ra *RepositoryAPI) GetSignatures() { - //use this func to init session. - ra.GetUserIDForRequest() - username, err := ra.getUsername() - if err != nil { - log.Warningf("Error when getting username: %v", err) - } repoName := ra.GetString(":splat") - targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), username, repoName) + targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), + ra.SecurityCtx.GetUsername(), repoName) if err != nil { log.Errorf("Error while fetching signature from notary: %v", err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -607,23 +494,3 @@ func (ra *RepositoryAPI) GetSignatures() { ra.Data["json"] = targets ra.ServeJSON() } - -func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string, - scopeActions ...string) (*registry.Repository, error) { - - credential := auth.NewBasicAuthCredential(username, password) - - authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, - config.InternalTokenServiceEndpoint(), scopeType, scopeName, scopeActions...) - - store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) - if err != nil { - return nil, err - } - - client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store) - if err != nil { - return nil, err - } - return client, nil -} diff --git a/src/ui/api/repository_test.go b/src/ui/api/repository_test.go index c05e40da8..bdaf72d41 100644 --- a/src/ui/api/repository_test.go +++ b/src/ui/api/repository_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/vmware/harbor/src/common/models" ) func TestGetRepos(t *testing.T) { @@ -27,12 +26,11 @@ func TestGetRepos(t *testing.T) { apiTest := newHarborAPI() projectID := "1" keyword := "hello-world" - detail := "true" fmt.Println("Testing Repos Get API") //-------------------case 1 : response code = 200------------------------// fmt.Println("case 1 : response code = 200") - code, repositories, err := apiTest.GetRepos(*admin, projectID, keyword, detail) + code, repositories, err := apiTest.GetRepos(*admin, projectID, keyword) if err != nil { t.Errorf("failed to get repositories: %v", err) } else { @@ -41,14 +39,14 @@ func TestGetRepos(t *testing.T) { assert.Equal(int(1), len(repos), "the length of repositories should be 1") assert.Equal(repos[0].Name, "library/hello-world", "unexpected repository name") } else { - t.Error("the response should return more info as detail is true") + t.Error("unexpected reponse") } } //-------------------case 2 : response code = 404------------------------// fmt.Println("case 2 : response code = 404:project not found") projectID = "111" - httpStatusCode, _, err := apiTest.GetRepos(*admin, projectID, keyword, detail) + httpStatusCode, _, err := apiTest.GetRepos(*admin, projectID, keyword) if err != nil { t.Error("Error whihle get repos by projectID", err.Error()) t.Log(err) @@ -56,27 +54,10 @@ func TestGetRepos(t *testing.T) { assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") } - //-------------------case 3 : response code = 200------------------------// - fmt.Println("case 3 : response code = 200") - projectID = "1" - detail = "false" - code, repositories, err = apiTest.GetRepos(*admin, projectID, keyword, detail) - if err != nil { - t.Errorf("failed to get repositories: %v", err) - } else { - assert.Equal(int(200), code, "response code should be 200") - if repos, ok := repositories.([]string); ok { - assert.Equal(int(1), len(repos), "the length of repositories should be 1") - assert.Equal(repos[0], "library/hello-world", "unexpected repository name") - } else { - t.Error("the response should not return detail info as detail is false") - } - } - - //-------------------case 4 : response code = 400------------------------// - fmt.Println("case 4 : response code = 400,invalid project_id") + //-------------------case 3 : response code = 400------------------------// + fmt.Println("case 3 : response code = 400,invalid project_id") projectID = "ccc" - httpStatusCode, _, err = apiTest.GetRepos(*admin, projectID, keyword, detail) + httpStatusCode, _, err = apiTest.GetRepos(*admin, projectID, keyword) if err != nil { t.Error("Error whihle get repos by projectID", err.Error()) t.Log(err) @@ -95,8 +76,7 @@ func TestGetReposTags(t *testing.T) { //-------------------case 1 : response code = 404------------------------// fmt.Println("case 1 : response code = 404,repo not found") repository := "errorRepos" - detail := "false" - code, _, err := apiTest.GetReposTags(*admin, repository, detail) + code, _, err := apiTest.GetReposTags(*admin, repository) if err != nil { t.Errorf("failed to get tags of repository %s: %v", repository, err) } else { @@ -105,33 +85,16 @@ func TestGetReposTags(t *testing.T) { //-------------------case 2 : response code = 200------------------------// fmt.Println("case 2 : response code = 200") repository = "library/hello-world" - code, tags, err := apiTest.GetReposTags(*admin, repository, detail) + code, tags, err := apiTest.GetReposTags(*admin, repository) if err != nil { t.Errorf("failed to get tags of repository %s: %v", repository, err) } else { assert.Equal(int(200), code, "httpStatusCode should be 200") - if tg, ok := tags.([]string); ok { - assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg)) - assert.Equal(tg[0], "latest", "the tag should be latest") - } else { - t.Error("the tags should be in simple style as the detail is false") - } - } - - //-------------------case 3 : response code = 200------------------------// - fmt.Println("case 3 : response code = 200") - repository = "library/hello-world" - detail = "true" - code, tags, err = apiTest.GetReposTags(*admin, repository, detail) - if err != nil { - t.Errorf("failed to get tags of repository %s: %v", repository, err) - } else { - assert.Equal(int(200), code, "httpStatusCode should be 200") - if tg, ok := tags.([]detailedTagResp); ok { + if tg, ok := tags.([]tagResp); ok { assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg)) assert.Equal(tg[0].Tag, "latest", "the tag should be latest") } else { - t.Error("the tags should be in detail style as the detail is true") + t.Error("unexpected response") } } @@ -190,38 +153,20 @@ func TestGetReposTop(t *testing.T) { apiTest := newHarborAPI() fmt.Println("Testing ReposTop Get API") - //-------------------case 1 : response code = 200------------------------// - fmt.Println("case 1 : response code = 200") - count := "1" - detail := "false" - code, repos, err := apiTest.GetReposTop(*admin, count, detail) - if err != nil { - t.Errorf("failed to get the most popular repositories: %v", err) - } else { - assert.Equal(int(200), code, "response code should be 200") - if r, ok := repos.([]*models.TopRepo); ok { - assert.Equal(int(1), len(r), "the length should be 1") - assert.Equal(r[0].RepoName, "library/docker", "the name of repository should be library/docker") - } else { - t.Error("the repositories should in simple style as the detail is false") - } - } - - //-------------------case 2 : response code = 400------------------------// - fmt.Println("case 2 : response code = 400,invalid count") - count = "cc" - code, _, err = apiTest.GetReposTop(*admin, count, detail) + //-------------------case 1 : response code = 400------------------------// + fmt.Println("case 1 : response code = 400,invalid count") + count := "cc" + code, _, err := apiTest.GetReposTop(*admin, count) if err != nil { t.Errorf("failed to get the most popular repositories: %v", err) } else { assert.Equal(int(400), code, "response code should be 400") } - //-------------------case 3 : response code = 200------------------------// - fmt.Println("case 3 : response code = 200") + //-------------------case 2 : response code = 200------------------------// + fmt.Println("case 2 : response code = 200") count = "1" - detail = "true" - code, repos, err = apiTest.GetReposTop(*admin, count, detail) + code, repos, err := apiTest.GetReposTop(*admin, count) if err != nil { t.Errorf("failed to get the most popular repositories: %v", err) } else { @@ -230,7 +175,7 @@ func TestGetReposTop(t *testing.T) { assert.Equal(int(1), len(r), "the length should be 1") assert.Equal(r[0].Name, "library/docker", "the name of repository should be library/docker") } else { - t.Error("the repositories should in detail style as the detail is true") + t.Error("unexpected response") } } diff --git a/src/ui/api/utils.go b/src/ui/api/utils.go index 62d7cbb2c..e90757cb3 100644 --- a/src/ui/api/utils.go +++ b/src/ui/api/utils.go @@ -42,6 +42,7 @@ func checkProjectPermission(userID int, projectID int64) bool { return len(roles) > 0 } +// TODO remove func hasProjectAdminRole(userID int, projectID int64) bool { roles, err := listRoles(userID, projectID) if err != nil { diff --git a/src/ui/projectmanager/db/pm.go b/src/ui/projectmanager/db/pm.go index e2cf75da0..d5f603e4d 100644 --- a/src/ui/projectmanager/db/pm.go +++ b/src/ui/projectmanager/db/pm.go @@ -24,27 +24,39 @@ import ( // ProjectManager implements pm.PM interface based on database type ProjectManager struct{} -// IsPublic returns whether the project is public or not -func (p *ProjectManager) IsPublic(projectIDOrName interface{}) bool { - var project *models.Project - var err error +// Get ... +func (p *ProjectManager) Get(projectIDOrName interface{}) *models.Project { switch projectIDOrName.(type) { case string: name := projectIDOrName.(string) - project, err = dao.GetProjectByName(name) + project, err := dao.GetProjectByName(name) if err != nil { log.Errorf("failed to get project %s: %v", name, err) + return nil } + return project case int64: id := projectIDOrName.(int64) - project, err = dao.GetProjectByID(id) + project, err := dao.GetProjectByID(id) if err != nil { log.Errorf("failed to get project %d: %v", id, err) + return nil } + return project default: log.Errorf("unsupported type of %v, must be string or int64", projectIDOrName) + return nil } +} +// Exist ... +func (p *ProjectManager) Exist(projectIDOrName interface{}) bool { + return p.Get(projectIDOrName) != nil +} + +// IsPublic returns whether the project is public or not +func (p *ProjectManager) IsPublic(projectIDOrName interface{}) bool { + project := p.Get(projectIDOrName) if project == nil { return false } @@ -67,31 +79,15 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) return roles } - var projectID int64 - switch projectIDOrName.(type) { - case string: - name := projectIDOrName.(string) - project, err := dao.GetProjectByName(name) - if err != nil { - log.Errorf("failed to get project %s: %v", name, err) - return roles - } - - if project == nil { - return roles - } - projectID = project.ProjectID - case int64: - projectID = projectIDOrName.(int64) - default: - log.Errorf("unsupported type of %v, must be string or int64", projectIDOrName) + project := p.Get(projectIDOrName) + if project == nil { return roles } - roleList, err := dao.GetUserProjectRoles(user.UserID, projectID) + roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID) if err != nil { log.Errorf("failed to get roles for user %d to project %d: %v", - user.UserID, projectID, err) + user.UserID, project.ProjectID, err) return roles } @@ -108,3 +104,32 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) return roles } + +// GetPublic returns all public projects +func (p *ProjectManager) GetPublic() []models.Project { + projects, err := dao.GetProjects("", 1) + if err != nil { + log.Errorf("failed to get all public projects: %v", err) + return []models.Project{} + } + + return projects +} + +// GetByMember returns all projects which the user is a member of +func (p *ProjectManager) GetByMember(username string) []models.Project { + user, err := dao.GetUser(models.User{ + Username: username, + }) + if err != nil { + log.Errorf("failed to get user %s: %v", username, err) + return []models.Project{} + } + projects, err := dao.GetUserRelevantProjects(user.UserID, "") + if err != nil { + log.Errorf("failed to get projects of %s: %v", username, err) + return []models.Project{} + } + + return projects +} diff --git a/src/ui/projectmanager/db/pm_test.go b/src/ui/projectmanager/db/pm_test.go index f1fc7e5f9..7bce75435 100644 --- a/src/ui/projectmanager/db/pm_test.go +++ b/src/ui/projectmanager/db/pm_test.go @@ -70,16 +70,44 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestGet(t *testing.T) { + pm := &ProjectManager{} + + // project name + project := pm.Get("library") + assert.NotNil(t, project) + assert.Equal(t, "library", project.Name) + + // project ID + project = pm.Get(int64(1)) + assert.NotNil(t, project) + assert.Equal(t, int64(1), project.ProjectID) + + // non-exist project + project = pm.Get("non-exist-project") + assert.Nil(t, project) + + // invalid type + project = pm.Get(true) + assert.Nil(t, project) +} + +func TestExist(t *testing.T) { + pm := &ProjectManager{} + + // exist project + assert.True(t, pm.Exist("library")) + + // non-exist project + assert.False(t, pm.Exist("non-exist-project")) +} + func TestIsPublic(t *testing.T) { pms := &ProjectManager{} - // project name + // public project assert.True(t, pms.IsPublic("library")) - // project ID - assert.True(t, pms.IsPublic(int64(1))) // non exist project assert.False(t, pms.IsPublic("non_exist_project")) - // invalid type - assert.False(t, pms.IsPublic(1)) } func TestGetRoles(t *testing.T) { @@ -89,18 +117,28 @@ func TestGetRoles(t *testing.T) { assert.Equal(t, []int{}, pm.GetRoles("non_exist_user", int64(1))) - // project ID - assert.Equal(t, []int{common.RoleProjectAdmin}, - pm.GetRoles("admin", int64(1))) - - // project name + // exist project assert.Equal(t, []int{common.RoleProjectAdmin}, pm.GetRoles("admin", "library")) - // non exist project + // non-exist project assert.Equal(t, []int{}, pm.GetRoles("admin", "non_exist_project")) - - // invalid type - assert.Equal(t, []int{}, pm.GetRoles("admin", 1)) +} + +func TestGetPublic(t *testing.T) { + pm := &ProjectManager{} + projects := pm.GetPublic() + + assert.NotEqual(t, 0, len(projects)) + + for _, project := range projects { + assert.Equal(t, 1, project.Public) + } +} + +func TestGetByMember(t *testing.T) { + pm := &ProjectManager{} + projects := pm.GetByMember("admin") + assert.NotEqual(t, 0, len(projects)) } diff --git a/src/ui/projectmanager/pm.go b/src/ui/projectmanager/pm.go index 3fae44fba..8f3763c8b 100644 --- a/src/ui/projectmanager/pm.go +++ b/src/ui/projectmanager/pm.go @@ -14,9 +14,19 @@ package projectmanager +import ( + "github.com/vmware/harbor/src/common/models" +) + // ProjectManager is the project mamager which abstracts the operations related // to projects type ProjectManager interface { + Get(projectIDOrName interface{}) *models.Project IsPublic(projectIDOrName interface{}) bool + Exist(projectIDOrName interface{}) bool GetRoles(username string, projectIDOrName interface{}) []int + // get all public project + GetPublic() []models.Project + // get projects which the user is a member of + GetByMember(username string) []models.Project } diff --git a/src/ui/router.go b/src/ui/router.go index 90447cb3b..69f0f9912 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -71,8 +71,9 @@ func initRouters() { beego.Router("/api/users/?:id", &api.UserAPI{}) beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword") beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry") - beego.Router("/api/repositories", &api.RepositoryAPI{}) - beego.Router("/api/repositories/*/tags/?:tag", &api.RepositoryAPI{}, "delete:Delete") + beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get") + beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete") + beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete") beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags") beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests") beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures") diff --git a/src/ui_ng/src/app/repository/repository.service.ts b/src/ui_ng/src/app/repository/repository.service.ts index 03dcfe9bd..f797d8a3d 100644 --- a/src/ui_ng/src/app/repository/repository.service.ts +++ b/src/ui_ng/src/app/repository/repository.service.ts @@ -34,14 +34,14 @@ export class RepositoryService { params.set('page_size', pageSize + ''); } return this.http - .get(`/api/repositories?project_id=${projectId}&q=${repoName}&detail=1`, {search: params}) + .get(`/api/repositories?project_id=${projectId}&q=${repoName}`, {search: params}) .map(response=>response) .catch(error=>Observable.throw(error)); } listTags(repoName: string): Observable { return this.http - .get(`/api/repositories/${repoName}/tags?detail=1`) + .get(`/api/repositories/${repoName}/tags`) .map(response=>response.json()) .catch(error=>Observable.throw(error)); } @@ -77,7 +77,7 @@ export class RepositoryService { deleteRepository(repoName: string): Observable { return this.http - .delete(`/api/repositories/${repoName}/tags`) + .delete(`/api/repositories/${repoName}`) .map(response=>response.status) .catch(error=>Observable.throw(error)); } diff --git a/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts b/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts index 0d68cdd25..ce103ef36 100644 --- a/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts +++ b/src/ui_ng/src/app/repository/top-repo/top-repository.service.ts @@ -17,7 +17,7 @@ import 'rxjs/add/operator/toPromise'; import { Repository } from '../repository'; -export const topRepoEndpoint = "/api/repositories/top?detail=1"; +export const topRepoEndpoint = "/api/repositories/top"; /** * Declare service to handle the top repositories *