From ebd26c0105a8685a4c98fdecf6eefa9c898f3065 Mon Sep 17 00:00:00 2001
From: He Weiwei <hweiwei@vmware.com>
Date: Wed, 16 Jan 2019 16:08:17 +0800
Subject: [PATCH] Implement current security interfaces using ram

Signed-off-by: He Weiwei <hweiwei@vmware.com>
---
 src/common/ram/namespace.go                 | 57 +++++++++++++
 src/common/ram/namespace_test.go            | 45 ++++++++++
 src/common/ram/parser.go                    | 50 +++++++++++
 src/common/ram/parser_test.go               | 39 +++++++++
 src/common/ram/project/const.go             | 33 ++++++++
 src/common/ram/project/util.go              | 59 +++++++++++++
 src/common/ram/project/visitor.go           | 82 ++++++++++++++++++
 src/common/ram/project/visitor_role.go      | 79 +++++++++++++++++
 src/common/ram/project/visitor_role_test.go | 44 ++++++++++
 src/common/ram/project/visitor_test.go      | 93 +++++++++++++++++++++
 src/common/ram/ram.go                       | 13 +++
 src/common/ram/ram_test.go                  | 35 ++++++++
 src/common/security/admiral/context.go      | 77 +++++------------
 src/common/security/local/context.go        | 73 +++++-----------
 src/common/security/secret/context.go       |  9 ++
 15 files changed, 680 insertions(+), 108 deletions(-)
 create mode 100644 src/common/ram/namespace.go
 create mode 100644 src/common/ram/namespace_test.go
 create mode 100644 src/common/ram/parser.go
 create mode 100644 src/common/ram/parser_test.go
 create mode 100644 src/common/ram/project/const.go
 create mode 100644 src/common/ram/project/util.go
 create mode 100644 src/common/ram/project/visitor.go
 create mode 100644 src/common/ram/project/visitor_role.go
 create mode 100644 src/common/ram/project/visitor_role_test.go
 create mode 100644 src/common/ram/project/visitor_test.go

diff --git a/src/common/ram/namespace.go b/src/common/ram/namespace.go
new file mode 100644
index 0000000000..daf5e5891d
--- /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 0000000000..d3b8dff76d
--- /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 0000000000..22355b02e3
--- /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 0000000000..3869297b74
--- /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 0000000000..5d54561cf5
--- /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 0000000000..d073577771
--- /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 0000000000..8a7e5263dc
--- /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 0000000000..20b18f236d
--- /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 0000000000..b1f22d24ac
--- /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 0000000000..c1b3450ccf
--- /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 c7d8a23035..76a76b2b1c 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 cd435c369d..84a8acaa51 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 3b3b5476b4..09856d3f57 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 48af21b302..2cf469237d 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 ac4a5b2e5f..62ee716024 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")