mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-17 04:11:24 +01:00
Implement current security interfaces using ram
Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
b5788f0695
commit
ebd26c0105
57
src/common/ram/namespace.go
Normal file
57
src/common/ram/namespace.go
Normal file
@ -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}
|
||||||
|
}
|
45
src/common/ram/namespace_test.go
Normal file
45
src/common/ram/namespace_test.go
Normal file
@ -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))
|
||||||
|
}
|
50
src/common/ram/parser.go
Normal file
50
src/common/ram/parser.go
Normal file
@ -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
|
||||||
|
}
|
39
src/common/ram/parser_test.go
Normal file
39
src/common/ram/parser_test.go
Normal file
@ -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))
|
||||||
|
}
|
33
src/common/ram/project/const.go
Normal file
33
src/common/ram/project/const.go
Normal file
@ -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")
|
||||||
|
)
|
59
src/common/ram/project/util.go
Normal file
59
src/common/ram/project/util.go
Normal file
@ -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
|
||||||
|
}
|
82
src/common/ram/project/visitor.go
Normal file
82
src/common/ram/project/visitor.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
79
src/common/ram/project/visitor_role.go
Normal file
79
src/common/ram/project/visitor_role.go
Normal file
@ -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
|
||||||
|
}
|
44
src/common/ram/project/visitor_role_test.go
Normal file
44
src/common/ram/project/visitor_role_test.go
Normal file
@ -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))
|
||||||
|
}
|
93
src/common/ram/project/visitor_test.go
Normal file
93
src/common/ram/project/visitor_test.go
Normal file
@ -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))
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
package ram
|
package ram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,6 +44,18 @@ func (res Resource) Subresource(resources ...Resource) Resource {
|
|||||||
return Resource(path.Join(elements...))
|
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
|
// Action the type of action
|
||||||
type Action string
|
type Action string
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package ram
|
package ram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
package admiral
|
package admiral
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/goharbor/harbor/src/common"
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"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/security/admiral/authcontext"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"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
|
// HasReadPerm returns whether the user has read permission to the project
|
||||||
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||||
public, err := s.pm.IsPublic(projectIDOrName)
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
if err != nil {
|
return s.Can(project.ActionPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasWritePerm returns whether the user has write permission to the project
|
// HasWritePerm returns whether the user has write permission to the project
|
||||||
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||||
if !s.IsAuthenticated() {
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
return false
|
return s.Can(project.ActionPush, 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,
|
|
||||||
common.RoleDeveloper:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAllPerm returns whether the user has all permissions to the project
|
// HasAllPerm returns whether the user has all permissions to the project
|
||||||
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||||
if !s.IsAuthenticated() {
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
return false
|
return s.Can(project.ActionPushPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage))
|
||||||
}
|
}
|
||||||
|
|
||||||
// system admin
|
// Can returns whether the user can do action on resource
|
||||||
if s.IsSysAdmin() {
|
func (s *SecurityContext) Can(action ram.Action, resource ram.Resource) bool {
|
||||||
return true
|
ns, err := resource.GetNamespace()
|
||||||
}
|
if err == nil {
|
||||||
|
switch ns.Kind() {
|
||||||
roles := s.GetProjectRoles(projectIDOrName)
|
case "project":
|
||||||
for _, role := range roles {
|
projectIDOrName := ns.Identity()
|
||||||
switch role {
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
case common.RoleProjectAdmin:
|
projectNamespace := ram.NewProjectNamespace(projectIDOrName, isPublicProject)
|
||||||
return true
|
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...)
|
||||||
|
return ram.HasPermission(user, resource, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/dao"
|
"github.com/goharbor/harbor/src/common/dao"
|
||||||
"github.com/goharbor/harbor/src/common/dao/group"
|
"github.com/goharbor/harbor/src/common/dao/group"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"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/common/utils/log"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"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
|
// HasReadPerm returns whether the user has read permission to the project
|
||||||
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||||
// public project
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
public, err := s.pm.IsPublic(projectIDOrName)
|
return s.Can(project.ActionPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasWritePerm returns whether the user has write permission to the project
|
// HasWritePerm returns whether the user has write permission to the project
|
||||||
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||||
if !s.IsAuthenticated() {
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
return false
|
return s.Can(project.ActionPush, 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,
|
|
||||||
common.RoleDeveloper:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAllPerm returns whether the user has all permissions to the project
|
// HasAllPerm returns whether the user has all permissions to the project
|
||||||
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||||
if !s.IsAuthenticated() {
|
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
|
||||||
return false
|
return s.Can(project.ActionPushPull, ram.NewProjectNamespace(projectIDOrName, isPublicProject).Resource(project.ResourceImage))
|
||||||
}
|
}
|
||||||
// system admin
|
|
||||||
if s.IsSysAdmin() {
|
// Can returns whether the user can do action on resource
|
||||||
return true
|
func (s *SecurityContext) Can(action ram.Action, resource ram.Resource) bool {
|
||||||
}
|
ns, err := resource.GetNamespace()
|
||||||
roles := s.GetProjectRoles(projectIDOrName)
|
if err == nil {
|
||||||
for _, role := range roles {
|
switch ns.Kind() {
|
||||||
switch role {
|
case "project":
|
||||||
case common.RoleProjectAdmin:
|
projectIDOrName := ns.Identity()
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"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/secret"
|
||||||
"github.com/goharbor/harbor/src/common/utils/log"
|
"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
|
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 ...
|
// GetMyProjects ...
|
||||||
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||||
return nil, fmt.Errorf("GetMyProjects is unsupported")
|
return nil, fmt.Errorf("GetMyProjects is unsupported")
|
||||||
|
Loading…
Reference in New Issue
Block a user