diff --git a/src/common/const.go b/src/common/const.go index ec9ac2885..806e67169 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -24,6 +24,11 @@ const ( LDAPScopeOnelevel = "2" LDAPScopeSubtree = "3" + RoleSystemAdmin = 1 + RoleProjectAdmin = 2 + RoleDeveloper = 3 + RoleGuest = 4 + ExtEndpoint = "ext_endpoint" AUTHMode = "auth_mode" DatabaseType = "database_type" diff --git a/src/common/security/rbac/context.go b/src/common/security/rbac/context.go index 02d6c6ffc..f72e6452d 100644 --- a/src/common/security/rbac/context.go +++ b/src/common/security/rbac/context.go @@ -13,3 +13,120 @@ // limitations under the License. package rbac + +import ( + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/ui/pms" +) + +// SecurityContext implements security.Context interface based on database +type SecurityContext struct { + user *models.User + pms pms.PMS +} + +// NewSecurityContext ... +func NewSecurityContext(user *models.User, pms pms.PMS) *SecurityContext { + return &SecurityContext{ + user: user, + pms: pms, + } +} + +// IsAuthenticated returns true if the user has been authenticated +func (s *SecurityContext) IsAuthenticated() bool { + return s.user != nil +} + +// GetUsername returns the username of the authenticated user +// It returns null if the user has not been authenticated +func (s *SecurityContext) GetUsername() string { + if !s.IsAuthenticated() { + return "" + } + return s.user.Username +} + +// IsSysAdmin returns whether the authenticated user is system admin +// It returns false if the user has not been authenticated +func (s *SecurityContext) IsSysAdmin() bool { + if !s.IsAuthenticated() { + return false + } + return s.user.HasAdminRole == 1 +} + +// HasReadPerm returns whether the user has read permission to the project +func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { + // public project + if s.pms.IsPublic(projectIDOrName) { + return true + } + + // private project + if !s.IsAuthenticated() { + return false + } + + // system admin + if s.IsSysAdmin() { + return true + } + + roles := s.pms.GetRoles(s.GetUsername(), projectIDOrName) + for _, role := range roles { + switch role { + case common.RoleProjectAdmin, + common.RoleDeveloper, + common.RoleGuest: + return true + } + } + + return false +} + +// 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.pms.GetRoles(s.GetUsername(), 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 +func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { + if !s.IsAuthenticated() { + return false + } + // system admin + if s.IsSysAdmin() { + return true + } + + roles := s.pms.GetRoles(s.GetUsername(), projectIDOrName) + for _, role := range roles { + switch role { + case common.RoleProjectAdmin: + return true + } + } + + return false +} diff --git a/src/common/security/rbac/context_test.go b/src/common/security/rbac/context_test.go new file mode 100644 index 000000000..52aabd39e --- /dev/null +++ b/src/common/security/rbac/context_test.go @@ -0,0 +1,185 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rbac + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" +) + +type fakePMS struct { + public string + roles map[string][]int +} + +func (f *fakePMS) IsPublic(projectIDOrName interface{}) bool { + return f.public == projectIDOrName.(string) +} +func (f *fakePMS) GetRoles(username string, projectIDOrName interface{}) []int { + return f.roles[projectIDOrName.(string)] +} + +func TestIsAuthenticated(t *testing.T) { + // unauthenticated + ctx := NewSecurityContext(nil, nil) + assert.False(t, ctx.IsAuthenticated()) + + // authenticated + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, nil) + assert.True(t, ctx.IsAuthenticated()) +} + +func TestGetUsername(t *testing.T) { + // unauthenticated + ctx := NewSecurityContext(nil, nil) + assert.Equal(t, "", ctx.GetUsername()) + + // authenticated + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, nil) + assert.Equal(t, "test", ctx.GetUsername()) +} + +func TestIsSysAdmin(t *testing.T) { + // unauthenticated + ctx := NewSecurityContext(nil, nil) + assert.False(t, ctx.IsSysAdmin()) + + // authenticated, non admin + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, nil) + assert.False(t, ctx.IsSysAdmin()) + + // authenticated, admin + ctx = NewSecurityContext(&models.User{ + Username: "test", + HasAdminRole: 1, + }, nil) + assert.True(t, ctx.IsSysAdmin()) +} + +func TestHasReadPerm(t *testing.T) { + pms := &fakePMS{ + public: "public_project", + roles: map[string][]int{ + "has_read_perm_project": []int{common.RoleGuest}, + }, + } + + // public project, unauthenticated + ctx := NewSecurityContext(nil, pms) + assert.True(t, ctx.HasReadPerm("public_project")) + + // private project, unauthenticated + ctx = NewSecurityContext(nil, pms) + assert.False(t, ctx.HasReadPerm("has_read_perm_project")) + + // private project, authenticated, has no perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.False(t, ctx.HasReadPerm("has_no_perm_project")) + + // private project, authenticated, has read perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.True(t, ctx.HasReadPerm("has_read_perm_project")) + + // private project, authenticated, system admin + ctx = NewSecurityContext(&models.User{ + Username: "test", + HasAdminRole: 1, + }, pms) + assert.True(t, ctx.HasReadPerm("has_no_perm_project")) +} + +func TestHasWritePerm(t *testing.T) { + pms := &fakePMS{ + roles: map[string][]int{ + "has_read_perm_project": []int{common.RoleGuest}, + "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, + }, + } + + // unauthenticated + ctx := NewSecurityContext(nil, pms) + assert.False(t, ctx.HasWritePerm("has_write_perm_project")) + + // authenticated, has read perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.False(t, ctx.HasWritePerm("has_read_perm_project")) // authenticated, has read perm + + // authenticated, has write perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.True(t, ctx.HasWritePerm("has_write_perm_project")) + + // authenticated, system admin + ctx = NewSecurityContext(&models.User{ + Username: "test", + HasAdminRole: 1, + }, pms) + assert.True(t, ctx.HasReadPerm("has_no_perm_project")) +} + +func TestHasAllPerm(t *testing.T) { + pms := &fakePMS{ + roles: map[string][]int{ + "has_read_perm_project": []int{common.RoleGuest}, + "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, + "has_all_perm_project": []int{common.RoleGuest, common.RoleDeveloper, common.RoleProjectAdmin}, + }, + } + + // unauthenticated + ctx := NewSecurityContext(nil, pms) + assert.False(t, ctx.HasAllPerm("has_all_perm_project")) + + // authenticated, has read perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.False(t, ctx.HasAllPerm("has_read_perm_project")) + + // authenticated, has write perm + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.False(t, ctx.HasAllPerm("has_write_perm_project")) + + // authenticated, has all perms + ctx = NewSecurityContext(&models.User{ + Username: "test", + }, pms) + assert.True(t, ctx.HasAllPerm("has_all_perm_project")) + + // authenticated, system admin + ctx = NewSecurityContext(&models.User{ + Username: "test", + HasAdminRole: 1, + }, pms) + assert.True(t, ctx.HasReadPerm("has_no_perm_project")) +} diff --git a/src/ui/pms/service.go b/src/ui/pms/service.go index 6e916da11..8ab949af7 100644 --- a/src/ui/pms/service.go +++ b/src/ui/pms/service.go @@ -18,7 +18,5 @@ package pms // the operations related to projects type PMS interface { IsPublic(projectIDOrName interface{}) bool - HasReadPerm(username string, projectIDOrName interface{}, token ...string) bool - HasWritePerm(username string, projectIDOrName interface{}, token ...string) bool - HasAllPerm(username string, projectIDOrName interface{}, token ...string) bool + GetRoles(username string, projectIDOrName interface{}) []int }