diff --git a/src/common/ram/namespace.go b/src/common/ram/namespace.go new file mode 100644 index 000000000..daf5e5891 --- /dev/null +++ b/src/common/ram/namespace.go @@ -0,0 +1,57 @@ +// 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 ram + +import ( + "fmt" +) + +// Namespace the namespace interface +type Namespace interface { + // Kind returns the kind of namespace + Kind() string + // Resource returns new resource for subresources with the namespace + Resource(subresources ...Resource) Resource + // Identity returns identity attached with namespace + Identity() interface{} + // IsPublic returns true if namespace is public + IsPublic() bool +} + +type projectNamespace struct { + projectIDOrName interface{} + isPublic bool +} + +func (ns *projectNamespace) Kind() string { + return "project" +} + +func (ns *projectNamespace) Resource(subresources ...Resource) Resource { + return Resource(fmt.Sprintf("/project/%v", ns.projectIDOrName)).Subresource(subresources...) +} + +func (ns *projectNamespace) Identity() interface{} { + return ns.projectIDOrName +} + +func (ns *projectNamespace) IsPublic() bool { + return ns.isPublic +} + +// NewProjectNamespace returns namespace for project +func NewProjectNamespace(projectIDOrName interface{}, isPublic bool) Namespace { + return &projectNamespace{projectIDOrName: projectIDOrName, isPublic: isPublic} +} diff --git a/src/common/ram/namespace_test.go b/src/common/ram/namespace_test.go new file mode 100644 index 000000000..d3b8dff76 --- /dev/null +++ b/src/common/ram/namespace_test.go @@ -0,0 +1,45 @@ +// 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 ram + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type ProjectNamespaceTestSuite struct { + suite.Suite +} + +func (suite *ProjectNamespaceTestSuite) TestResource() { + var namespace Namespace + + namespace = &projectNamespace{projectIDOrName: int64(1)} + + suite.Equal(namespace.Resource(Resource("image")), Resource("/project/1/image")) +} + +func (suite *ProjectNamespaceTestSuite) TestIdentity() { + namespace, _ := Resource("/project/1/image").GetNamespace() + suite.Equal(namespace.Identity(), int64(1)) + + namespace, _ = Resource("/project/library/image").GetNamespace() + suite.Equal(namespace.Identity(), "library") +} + +func TestProjectNamespaceTestSuite(t *testing.T) { + suite.Run(t, new(ProjectNamespaceTestSuite)) +} diff --git a/src/common/ram/parser.go b/src/common/ram/parser.go new file mode 100644 index 000000000..22355b02e --- /dev/null +++ b/src/common/ram/parser.go @@ -0,0 +1,50 @@ +// 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 ram + +import ( + "errors" + "regexp" + "strconv" +) + +var ( + namespaceParsers = map[string]namespaceParser{ + "project": projectNamespaceParser, + } +) + +type namespaceParser func(resource Resource) (Namespace, error) + +func projectNamespaceParser(resource Resource) (Namespace, error) { + parserRe := regexp.MustCompile("^/project/([^/]*)/?") + + matches := parserRe.FindStringSubmatch(resource.String()) + + if len(matches) <= 1 { + return nil, errors.New("not support resource") + } + + var projectIDOrName interface{} + + id, err := strconv.ParseInt(matches[1], 10, 64) + if err == nil { + projectIDOrName = id + } else { + projectIDOrName = matches[1] + } + + return &projectNamespace{projectIDOrName: projectIDOrName}, nil +} diff --git a/src/common/ram/parser_test.go b/src/common/ram/parser_test.go new file mode 100644 index 000000000..3869297b7 --- /dev/null +++ b/src/common/ram/parser_test.go @@ -0,0 +1,39 @@ +// 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 ram + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type ProjectParserTestSuite struct { + suite.Suite +} + +func (suite *ProjectParserTestSuite) TestParse() { + namespace, err := projectNamespaceParser(Resource("/project/1/image")) + suite.Equal(namespace, &projectNamespace{projectIDOrName: int64(1)}) + suite.Nil(err) + + namespace, err = projectNamespaceParser(Resource("/fake/1/image")) + suite.Nil(namespace) + suite.Error(err) +} + +func TestProjectParserTestSuite(t *testing.T) { + suite.Run(t, new(ProjectParserTestSuite)) +} diff --git a/src/common/ram/project/const.go b/src/common/ram/project/const.go new file mode 100644 index 000000000..5d54561cf --- /dev/null +++ b/src/common/ram/project/const.go @@ -0,0 +1,33 @@ +// 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 project + +import ( + "github.com/goharbor/harbor/src/common/ram" +) + +// const action variables +const ( + ActionAll = ram.Action("*") + ActionPull = ram.Action("pull") + ActionPush = ram.Action("push") + ActionPushPull = ram.Action("push+pull") +) + +// const resource variables +const ( + ResourceAll = ram.Resource("*") + ResourceImage = ram.Resource("image") +) diff --git a/src/common/ram/project/util.go b/src/common/ram/project/util.go new file mode 100644 index 000000000..d07357777 --- /dev/null +++ b/src/common/ram/project/util.go @@ -0,0 +1,59 @@ +// 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 project + +import ( + "github.com/goharbor/harbor/src/common/ram" +) + +var ( + // subresource policies for public project + publicProjectPolicies = []*ram.Policy{ + {Resource: ResourceImage, Action: ActionPull}, + } + + // subresource policies for system admin visitor + systemAdminProjectPolicies = []*ram.Policy{ + {Resource: ResourceAll, Action: ActionAll}, + } +) + +func policiesForPublicProject(namespace ram.Namespace) []*ram.Policy { + policies := []*ram.Policy{} + + for _, policy := range publicProjectPolicies { + policies = append(policies, &ram.Policy{ + Resource: namespace.Resource(policy.Resource), + Action: policy.Action, + Effect: policy.Effect, + }) + } + + return policies +} + +func policiesForSystemAdmin(namespace ram.Namespace) []*ram.Policy { + policies := []*ram.Policy{} + + for _, policy := range systemAdminProjectPolicies { + policies = append(policies, &ram.Policy{ + Resource: namespace.Resource(policy.Resource), + Action: policy.Action, + Effect: policy.Effect, + }) + } + + return policies +} diff --git a/src/common/ram/project/visitor.go b/src/common/ram/project/visitor.go new file mode 100644 index 000000000..8a7e5263d --- /dev/null +++ b/src/common/ram/project/visitor.go @@ -0,0 +1,82 @@ +// 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 project + +import ( + "github.com/goharbor/harbor/src/common/ram" +) + +// visitorContext the context interface for the project visitor +type visitorContext interface { + IsAuthenticated() bool + // GetUsername returns the username of user related to the context + GetUsername() string + // IsSysAdmin returns whether the user is system admin + IsSysAdmin() bool +} + +// visitor implement the ram.User interface for project visitor +type visitor struct { + ctx visitorContext + namespace ram.Namespace + projectRoles []int +} + +// GetUserName returns username of the visitor +func (v *visitor) GetUserName() string { + // anonymous username for unauthenticated Visitor + if !v.ctx.IsAuthenticated() { + return "anonymous" + } + + return v.ctx.GetUsername() +} + +// GetPolicies returns policies of the visitor +func (v *visitor) GetPolicies() []*ram.Policy { + if v.ctx.IsSysAdmin() { + return policiesForSystemAdmin(v.namespace) + } + + if v.namespace.IsPublic() { + return policiesForPublicProject(v.namespace) + } + + return nil +} + +// GetRoles returns roles of the visitor +func (v *visitor) GetRoles() []ram.Role { + if !v.ctx.IsAuthenticated() { + return nil + } + + roles := []ram.Role{} + + for _, roleID := range v.projectRoles { + roles = append(roles, &visitorRole{roleID: roleID, namespace: v.namespace}) + } + + return roles +} + +// NewUser returns ram.User interface for the project visitor +func NewUser(ctx visitorContext, namespace ram.Namespace, projectRoles ...int) ram.User { + return &visitor{ + ctx: ctx, + namespace: namespace, + projectRoles: projectRoles, + } +} diff --git a/src/common/ram/project/visitor_role.go b/src/common/ram/project/visitor_role.go new file mode 100644 index 000000000..20b18f236 --- /dev/null +++ b/src/common/ram/project/visitor_role.go @@ -0,0 +1,79 @@ +// 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 project + +import ( + "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/common/ram" +) + +var ( + rolePoliciesMap = map[string][]*ram.Policy{ + "projectAdmin": { + {Resource: ResourceImage, Action: ActionPushPull}, // compatible with security all perm of project + {Resource: ResourceImage, Action: ActionPush}, + {Resource: ResourceImage, Action: ActionPull}, + }, + + "developer": { + {Resource: ResourceImage, Action: ActionPush}, + {Resource: ResourceImage, Action: ActionPull}, + }, + + "guest": { + {Resource: ResourceImage, Action: ActionPull}, + }, + } +) + +// visitorRole implement the ram.Role interface +type visitorRole struct { + namespace ram.Namespace + roleID int +} + +// GetRoleName returns role name for the visitor role +func (role *visitorRole) GetRoleName() string { + switch role.roleID { + case common.RoleProjectAdmin: + return "projectAdmin" + case common.RoleDeveloper: + return "developer" + case common.RoleGuest: + return "guest" + default: + return "" + } +} + +// GetPolicies returns policies for the visitor role +func (role *visitorRole) GetPolicies() []*ram.Policy { + policies := []*ram.Policy{} + + roleName := role.GetRoleName() + if roleName == "" { + return policies + } + + for _, policy := range rolePoliciesMap[roleName] { + policies = append(policies, &ram.Policy{ + Resource: role.namespace.Resource(policy.Resource), + Action: policy.Action, + Effect: policy.Effect, + }) + } + + return policies +} diff --git a/src/common/ram/project/visitor_role_test.go b/src/common/ram/project/visitor_role_test.go new file mode 100644 index 000000000..b1f22d24a --- /dev/null +++ b/src/common/ram/project/visitor_role_test.go @@ -0,0 +1,44 @@ +// 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 project + +import ( + "testing" + + "github.com/goharbor/harbor/src/common" + "github.com/stretchr/testify/suite" +) + +type VisitorRoleTestSuite struct { + suite.Suite +} + +func (suite *VisitorRoleTestSuite) TestGetRoleName() { + projectAdmin := visitorRole{roleID: common.RoleProjectAdmin} + suite.Equal(projectAdmin.GetRoleName(), "projectAdmin") + + developer := visitorRole{roleID: common.RoleDeveloper} + suite.Equal(developer.GetRoleName(), "developer") + + guest := visitorRole{roleID: common.RoleGuest} + suite.Equal(guest.GetRoleName(), "guest") + + unknow := visitorRole{roleID: 404} + suite.Equal(unknow.GetRoleName(), "") +} + +func TestVisitorRoleTestSuite(t *testing.T) { + suite.Run(t, new(VisitorRoleTestSuite)) +} diff --git a/src/common/ram/project/visitor_test.go b/src/common/ram/project/visitor_test.go new file mode 100644 index 000000000..c1b3450cc --- /dev/null +++ b/src/common/ram/project/visitor_test.go @@ -0,0 +1,93 @@ +// 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 project + +import ( + "testing" + + "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/common/ram" + "github.com/stretchr/testify/suite" +) + +type fakeVisitorContext struct { + username string + isSysAdmin bool +} + +func (ctx *fakeVisitorContext) IsAuthenticated() bool { + return ctx.username != "" +} + +func (ctx *fakeVisitorContext) GetUsername() string { + return ctx.username +} + +func (ctx *fakeVisitorContext) IsSysAdmin() bool { + return ctx.IsAuthenticated() && ctx.isSysAdmin +} + +var ( + anonymousCtx = &fakeVisitorContext{} + authenticatedCtx = &fakeVisitorContext{username: "user"} + sysAdminCtx = &fakeVisitorContext{username: "admin", isSysAdmin: true} +) + +type VisitorTestSuite struct { + suite.Suite +} + +func (suite *VisitorTestSuite) TestGetPolicies() { + namespace := ram.NewProjectNamespace("library", false) + publicNamespace := ram.NewProjectNamespace("library", true) + + anonymous := NewUser(anonymousCtx, namespace) + suite.Nil(anonymous.GetPolicies()) + + anonymousForPublicProject := NewUser(anonymousCtx, publicNamespace) + suite.Equal(anonymousForPublicProject.GetPolicies(), policiesForPublicProject(publicNamespace)) + + authenticated := NewUser(authenticatedCtx, namespace) + suite.Nil(authenticated.GetPolicies()) + + authenticatedForPublicProject := NewUser(authenticatedCtx, publicNamespace) + suite.Equal(authenticatedForPublicProject.GetPolicies(), policiesForPublicProject(publicNamespace)) + + systemAdmin := NewUser(sysAdminCtx, namespace) + suite.Equal(systemAdmin.GetPolicies(), policiesForSystemAdmin(namespace)) + + systemAdminForPublicProject := NewUser(sysAdminCtx, publicNamespace) + suite.Equal(systemAdminForPublicProject.GetPolicies(), policiesForSystemAdmin(publicNamespace)) +} + +func (suite *VisitorTestSuite) TestGetRoles() { + namespace := ram.NewProjectNamespace("library", false) + + anonymous := NewUser(anonymousCtx, namespace) + suite.Nil(anonymous.GetRoles()) + + authenticated := NewUser(authenticatedCtx, namespace) + suite.Empty(authenticated.GetRoles()) + + authenticated = NewUser(authenticatedCtx, namespace, common.RoleProjectAdmin) + suite.Len(authenticated.GetRoles(), 1) + + authenticated = NewUser(authenticatedCtx, namespace, common.RoleProjectAdmin, common.RoleDeveloper) + suite.Len(authenticated.GetRoles(), 2) +} + +func TestVisitorTestSuite(t *testing.T) { + suite.Run(t, new(VisitorTestSuite)) +} diff --git a/src/common/ram/ram.go b/src/common/ram/ram.go index c7d8a2303..76a76b2b1 100644 --- a/src/common/ram/ram.go +++ b/src/common/ram/ram.go @@ -15,6 +15,7 @@ package ram import ( + "fmt" "path" ) @@ -43,6 +44,18 @@ func (res Resource) Subresource(resources ...Resource) Resource { return Resource(path.Join(elements...)) } +// GetNamespace returns namespace from resource +func (res Resource) GetNamespace() (Namespace, error) { + for _, parser := range namespaceParsers { + namespace, err := parser(res) + if err == nil { + return namespace, nil + } + } + + return nil, fmt.Errorf("no namespace found for %s", res) +} + // Action the type of action type Action string diff --git a/src/common/ram/ram_test.go b/src/common/ram/ram_test.go index cd435c369..84a8acaa5 100644 --- a/src/common/ram/ram_test.go +++ b/src/common/ram/ram_test.go @@ -15,6 +15,7 @@ package ram import ( + "reflect" "testing" ) @@ -355,3 +356,37 @@ func TestResource_Subresource(t *testing.T) { }) } } + +func TestResource_GetNamespace(t *testing.T) { + tests := []struct { + name string + res Resource + want Namespace + wantErr bool + }{ + { + name: "project namespace", + res: Resource("/project/1"), + want: &projectNamespace{int64(1), false}, + wantErr: false, + }, + { + name: "unknow namespace", + res: Resource("/unknow/1"), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.res.GetNamespace() + if (err != nil) != tt.wantErr { + t.Errorf("Resource.GetNamespace() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Resource.GetNamespace() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go index 3b3b5476b..09856d3f5 100644 --- a/src/common/security/admiral/context.go +++ b/src/common/security/admiral/context.go @@ -15,10 +15,10 @@ package admiral import ( - "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/models" + "github.com/goharbor/harbor/src/common/ram" + "github.com/goharbor/harbor/src/common/ram/project" "github.com/goharbor/harbor/src/common/security/admiral/authcontext" - "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/core/promgr" ) @@ -71,70 +71,33 @@ func (s *SecurityContext) IsSolutionUser() bool { // HasReadPerm returns whether the user has read permission to the project func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { - public, err := s.pm.IsPublic(projectIDOrName) - if err != nil { - log.Errorf("failed to check the public of project %v: %v", - projectIDOrName, err) - return false - } - if public { - return true - } - - // private project - if !s.IsAuthenticated() { - return false - } - - // system admin - if s.IsSysAdmin() { - return true - } - - roles := s.GetProjectRoles(projectIDOrName) - - return len(roles) > 0 + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) } // HasWritePerm returns whether the user has write permission to the project func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { - if !s.IsAuthenticated() { - return false - } - - // system admin - if s.IsSysAdmin() { - return true - } - - roles := s.GetProjectRoles(projectIDOrName) - for _, role := range roles { - switch role { - case common.RoleProjectAdmin, - common.RoleDeveloper: - return true - } - } - - return false + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPush, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) } // HasAllPerm returns whether the user has all permissions to the project func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { - if !s.IsAuthenticated() { - return false - } + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPushPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) +} - // system admin - if s.IsSysAdmin() { - return true - } - - roles := s.GetProjectRoles(projectIDOrName) - for _, role := range roles { - switch role { - case common.RoleProjectAdmin: - return true +// Can returns whether the user can do action on resource +func (s *SecurityContext) Can(action ram.Action, resource ram.Resource) bool { + ns, err := resource.GetNamespace() + if err == nil { + switch ns.Kind() { + case "project": + projectIDOrName := ns.Identity() + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + projectNamespace := ram.NewProjectNamespace(projectIDOrName, isPublicProject) + user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...) + return ram.HasPermission(user, resource, action) } } diff --git a/src/common/security/local/context.go b/src/common/security/local/context.go index 48af21b30..2cf469237 100644 --- a/src/common/security/local/context.go +++ b/src/common/security/local/context.go @@ -19,6 +19,8 @@ import ( "github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao/group" "github.com/goharbor/harbor/src/common/models" + "github.com/goharbor/harbor/src/common/ram" + "github.com/goharbor/harbor/src/common/ram/project" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/core/promgr" ) @@ -67,67 +69,36 @@ func (s *SecurityContext) IsSolutionUser() bool { // HasReadPerm returns whether the user has read permission to the project func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { - // public project - public, err := s.pm.IsPublic(projectIDOrName) - if err != nil { - log.Errorf("failed to check the public of project %v: %v", - projectIDOrName, err) - return false - } - if public { - return true - } - - // private project - if !s.IsAuthenticated() { - return false - } - - // system admin - if s.IsSysAdmin() { - return true - } - - roles := s.GetProjectRoles(projectIDOrName) - return len(roles) > 0 + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) } // HasWritePerm returns whether the user has write permission to the project func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { - if !s.IsAuthenticated() { - return false - } - // system admin - if s.IsSysAdmin() { - return true - } - roles := s.GetProjectRoles(projectIDOrName) - for _, role := range roles { - switch role { - case common.RoleProjectAdmin, - common.RoleDeveloper: - return true - } - } - return false + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPush, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) } // HasAllPerm returns whether the user has all permissions to the project func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { - if !s.IsAuthenticated() { - return false - } - // system admin - if s.IsSysAdmin() { - return true - } - roles := s.GetProjectRoles(projectIDOrName) - for _, role := range roles { - switch role { - case common.RoleProjectAdmin: - return true + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + return s.Can(project.ActionPushPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage)) +} + +// Can returns whether the user can do action on resource +func (s *SecurityContext) Can(action ram.Action, resource ram.Resource) bool { + ns, err := resource.GetNamespace() + if err == nil { + switch ns.Kind() { + case "project": + projectIDOrName := ns.Identity() + isPublicProject, _ := s.pm.IsPublic(projectIDOrName) + projectNamespace := ram.NewProjectNamespace(projectIDOrName, isPublicProject) + user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...) + return ram.HasPermission(user, resource, action) } } + return false } diff --git a/src/common/security/secret/context.go b/src/common/security/secret/context.go index ac4a5b2e5..62ee71602 100644 --- a/src/common/security/secret/context.go +++ b/src/common/security/secret/context.go @@ -19,6 +19,7 @@ import ( "github.com/goharbor/harbor/src/common" "github.com/goharbor/harbor/src/common/models" + "github.com/goharbor/harbor/src/common/ram" "github.com/goharbor/harbor/src/common/secret" "github.com/goharbor/harbor/src/common/utils/log" ) @@ -97,6 +98,14 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return s.store.GetUsername(s.secret) == secret.JobserviceUser || s.store.GetUsername(s.secret) == secret.CoreUser } +// Can returns whether the user can do action on resource +func (s *SecurityContext) Can(action ram.Action, resource ram.Resource) bool { + if s.store == nil { + return false + } + return s.store.GetUsername(s.secret) == secret.JobserviceUser || s.store.GetUsername(s.secret) == secret.CoreUser +} + // GetMyProjects ... func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) { return nil, fmt.Errorf("GetMyProjects is unsupported")