refactor auth of token service

This commit is contained in:
Wenkai Yin 2017-05-11 16:45:24 +08:00
parent 161cbea40b
commit 7e805b2dd4
11 changed files with 335 additions and 197 deletions

View File

@ -136,6 +136,7 @@ func (b *BaseAPI) ValidateUser() int {
// GetUserIDForRequest tries to get user ID from basic auth header and session. // GetUserIDForRequest tries to get user ID from basic auth header and session.
// It returns the user ID, whether need further verification(when the id is from session) and if the action is successful // It returns the user ID, whether need further verification(when the id is from session) and if the action is successful
// TODO remove
func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) { func (b *BaseAPI) GetUserIDForRequest() (int, bool, bool) {
username, password, ok := b.Ctx.Request.BasicAuth() username, password, ok := b.Ctx.Request.BasicAuth()
if ok { if ok {

View File

@ -59,6 +59,11 @@ func (s *SecurityContext) IsSysAdmin() 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 {
// not exist
if !s.pm.Exist(projectIDOrName) {
return false
}
// public project // public project
if s.pm.IsPublic(projectIDOrName) { if s.pm.IsPublic(projectIDOrName) {
return true return true
@ -93,6 +98,11 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
return false return false
} }
// project does not exist
if !s.pm.Exist(projectIDOrName) {
return false
}
// system admin // system admin
if s.IsSysAdmin() { if s.IsSysAdmin() {
return true return true
@ -115,6 +125,12 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
if !s.IsAuthenticated() { if !s.IsAuthenticated() {
return false return false
} }
// project does not exist
if !s.pm.Exist(projectIDOrName) {
return false
}
// system admin // system admin
if s.IsSysAdmin() { if s.IsSysAdmin() {
return true return true

View File

@ -22,26 +22,69 @@ import (
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
) )
var (
public = &models.Project{
Name: "public_project",
Public: 1,
}
private = &models.Project{
Name: "private_project",
Public: 0,
}
read = &models.Project{
Name: "has_read_perm_project",
}
write = &models.Project{
Name: "has_write_perm_project",
}
all = &models.Project{
Name: "has_all_perm_project",
}
)
type fakePM struct { type fakePM struct {
public string projects []*models.Project
roles map[string][]int roles map[string][]int
} }
func (f *fakePM) IsPublic(projectIDOrName interface{}) bool { func (f *fakePM) IsPublic(projectIDOrName interface{}) bool {
return f.public == projectIDOrName.(string) for _, project := range f.projects {
if project.Name == projectIDOrName.(string) {
return project.Public == 1
}
}
return false
} }
func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) []int { func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) []int {
return f.roles[projectIDOrName.(string)] return f.roles[projectIDOrName.(string)]
} }
func (f *fakePM) Get(projectIDOrName interface{}) *models.Project { func (f *fakePM) Get(projectIDOrName interface{}) *models.Project {
for _, project := range f.projects {
if project.Name == projectIDOrName.(string) {
return project
}
}
return nil return nil
} }
func (f *fakePM) Exist(projectIDOrName interface{}) bool { func (f *fakePM) Exist(projectIDOrName interface{}) bool {
for _, project := range f.projects {
if project.Name == projectIDOrName.(string) {
return true
}
}
return false return false
} }
// nil implement
func (f *fakePM) GetPublic() []models.Project { func (f *fakePM) GetPublic() []models.Project {
return []models.Project{} return []models.Project{}
} }
// nil implement
func (f *fakePM) GetByMember(username string) []models.Project { func (f *fakePM) GetByMember(username string) []models.Project {
return []models.Project{} return []models.Project{}
} }
@ -91,25 +134,29 @@ func TestIsSysAdmin(t *testing.T) {
func TestHasReadPerm(t *testing.T) { func TestHasReadPerm(t *testing.T) {
pm := &fakePM{ pm := &fakePM{
public: "public_project", projects: []*models.Project{public, private, read},
roles: map[string][]int{ roles: map[string][]int{
"has_read_perm_project": []int{common.RoleGuest}, "has_read_perm_project": []int{common.RoleGuest},
}, },
} }
// public project, unauthenticated // non-exist project
ctx := NewSecurityContext(nil, pm) ctx := NewSecurityContext(nil, pm)
assert.False(t, ctx.HasReadPerm("non_exist_project"))
// public project
ctx = NewSecurityContext(nil, pm)
assert.True(t, ctx.HasReadPerm("public_project")) assert.True(t, ctx.HasReadPerm("public_project"))
// private project, unauthenticated // private project, unauthenticated
ctx = NewSecurityContext(nil, pm) ctx = NewSecurityContext(nil, pm)
assert.False(t, ctx.HasReadPerm("has_read_perm_project")) assert.False(t, ctx.HasReadPerm("private_project"))
// private project, authenticated, has no perm // private project, authenticated, has no perm
ctx = NewSecurityContext(&models.User{ ctx = NewSecurityContext(&models.User{
Username: "test", Username: "test",
}, pm) }, pm)
assert.False(t, ctx.HasReadPerm("has_no_perm_project")) assert.False(t, ctx.HasReadPerm("private_project"))
// private project, authenticated, has read perm // private project, authenticated, has read perm
ctx = NewSecurityContext(&models.User{ ctx = NewSecurityContext(&models.User{
@ -122,11 +169,19 @@ func TestHasReadPerm(t *testing.T) {
Username: "test", Username: "test",
HasAdminRole: 1, HasAdminRole: 1,
}, pm) }, pm)
assert.True(t, ctx.HasReadPerm("has_no_perm_project")) assert.True(t, ctx.HasReadPerm("private_project"))
// non-exist project, authenticated, system admin
ctx = NewSecurityContext(&models.User{
Username: "test",
HasAdminRole: 1,
}, pm)
assert.False(t, ctx.HasReadPerm("non_exist_project"))
} }
func TestHasWritePerm(t *testing.T) { func TestHasWritePerm(t *testing.T) {
pm := &fakePM{ pm := &fakePM{
projects: []*models.Project{read, write, private},
roles: map[string][]int{ roles: map[string][]int{
"has_read_perm_project": []int{common.RoleGuest}, "has_read_perm_project": []int{common.RoleGuest},
"has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper},
@ -137,6 +192,12 @@ func TestHasWritePerm(t *testing.T) {
ctx := NewSecurityContext(nil, pm) ctx := NewSecurityContext(nil, pm)
assert.False(t, ctx.HasWritePerm("has_write_perm_project")) assert.False(t, ctx.HasWritePerm("has_write_perm_project"))
// authenticated, non-exist project
ctx = NewSecurityContext(&models.User{
Username: "test",
}, pm)
assert.False(t, ctx.HasWritePerm("non_exist_project"))
// authenticated, has read perm // authenticated, has read perm
ctx = NewSecurityContext(&models.User{ ctx = NewSecurityContext(&models.User{
Username: "test", Username: "test",
@ -154,11 +215,19 @@ func TestHasWritePerm(t *testing.T) {
Username: "test", Username: "test",
HasAdminRole: 1, HasAdminRole: 1,
}, pm) }, pm)
assert.True(t, ctx.HasReadPerm("has_no_perm_project")) assert.True(t, ctx.HasReadPerm("private_project"))
// authenticated, system admin, non-exist project
ctx = NewSecurityContext(&models.User{
Username: "test",
HasAdminRole: 1,
}, pm)
assert.False(t, ctx.HasReadPerm("non_exist_project"))
} }
func TestHasAllPerm(t *testing.T) { func TestHasAllPerm(t *testing.T) {
pm := &fakePM{ pm := &fakePM{
projects: []*models.Project{read, write, all, private},
roles: map[string][]int{ roles: map[string][]int{
"has_read_perm_project": []int{common.RoleGuest}, "has_read_perm_project": []int{common.RoleGuest},
"has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper},
@ -170,6 +239,12 @@ func TestHasAllPerm(t *testing.T) {
ctx := NewSecurityContext(nil, pm) ctx := NewSecurityContext(nil, pm)
assert.False(t, ctx.HasAllPerm("has_all_perm_project")) assert.False(t, ctx.HasAllPerm("has_all_perm_project"))
// authenticated, non-exist project
ctx = NewSecurityContext(&models.User{
Username: "test",
}, pm)
assert.False(t, ctx.HasAllPerm("non_exist_project"))
// authenticated, has read perm // authenticated, has read perm
ctx = NewSecurityContext(&models.User{ ctx = NewSecurityContext(&models.User{
Username: "test", Username: "test",
@ -193,5 +268,12 @@ func TestHasAllPerm(t *testing.T) {
Username: "test", Username: "test",
HasAdminRole: 1, HasAdminRole: 1,
}, pm) }, pm)
assert.True(t, ctx.HasReadPerm("has_no_perm_project")) assert.True(t, ctx.HasAllPerm("private_project"))
// authenticated, system admin, non-exist project
ctx = NewSecurityContext(&models.User{
Username: "test",
HasAdminRole: 1,
}, pm)
assert.False(t, ctx.HasAllPerm("non_exist_project"))
} }

View File

@ -34,21 +34,20 @@ type BaseController struct {
ProjectMgr projectmanager.ProjectManager ProjectMgr projectmanager.ProjectManager
} }
// Prepare inits security context and project manager from beego // Prepare inits security context and project manager from request
// context // context
func (b *BaseController) Prepare() { func (b *BaseController) Prepare() {
ok := false ctx, err := filter.GetSecurityContext(b.Ctx.Request)
ctx := b.Ctx.Input.GetData(filter.HarborSecurityContext) if err != nil {
b.SecurityCtx, ok = ctx.(security.Context)
if !ok {
log.Error("failed to get security context") log.Error("failed to get security context")
b.CustomAbort(http.StatusInternalServerError, "") b.CustomAbort(http.StatusInternalServerError, "")
} }
b.SecurityCtx = ctx
pm := b.Ctx.Input.GetData(filter.HarborProjectManager) pm, err := filter.GetProjectManager(b.Ctx.Request)
b.ProjectMgr, ok = pm.(projectmanager.ProjectManager) if err != nil {
if !ok {
log.Error("failed to get project manager") log.Error("failed to get project manager")
b.CustomAbort(http.StatusInternalServerError, "") b.CustomAbort(http.StatusInternalServerError, "")
} }
b.ProjectMgr = pm
} }

View File

@ -356,7 +356,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
client, err := NewRepositoryClient(endpoint, true, client, err := NewRepositoryClient(endpoint, true,
"admin", repoInR, "repository", repoInR) "admin", repoInR, "repository", repoInR, "pull")
if err != nil { if err != nil {
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
@ -381,7 +381,7 @@ func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
client, err := NewRepositoryClient(endpoint, true, client, err := NewRepositoryClient(endpoint, true,
"admin", repoInR, "repository", repoInR) "admin", repoInR, "repository", repoInR, "pull")
if err != nil { if err != nil {
return needsAdd, needsDel, err return needsAdd, needsDel, err
} }
@ -428,6 +428,7 @@ func projectExists(repository string) (bool, error) {
return dao.ProjectExists(project) return dao.ProjectExists(project)
} }
// TODO need a registry client which accept a raw token as param
func initRegistryClient() (r *registry.Registry, err error) { func initRegistryClient() (r *registry.Registry, err error) {
endpoint, err := config.RegistryURL() endpoint, err := config.RegistryURL()
if err != nil { if err != nil {
@ -500,6 +501,7 @@ func repositoryExist(name string, client *registry.Repository) (bool, error) {
} }
// NewRegistryClient ... // NewRegistryClient ...
// TODO need a registry client which accept a raw token as param
func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string, func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string,
scopeActions ...string) (*registry.Registry, error) { scopeActions ...string) (*registry.Registry, error) {
authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...) authorizer := auth.NewRegistryUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...)
@ -517,6 +519,7 @@ func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scop
} }
// NewRepositoryClient ... // NewRepositoryClient ...
// TODO need a registry client which accept a raw token as param
func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string, func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string,
scopeActions ...string) (*registry.Repository, error) { scopeActions ...string) (*registry.Repository, error) {

View File

@ -15,11 +15,15 @@
package filter package filter
import ( import (
"context"
"fmt"
"net/http"
"strings" "strings"
beegoctx "github.com/astaxie/beego/context" beegoctx "github.com/astaxie/beego/context"
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/security/rbac" "github.com/vmware/harbor/src/common/security/rbac"
"github.com/vmware/harbor/src/common/security/secret" "github.com/vmware/harbor/src/common/security/secret"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
@ -28,15 +32,17 @@ import (
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager"
) )
type key string
const ( const (
// HarborSecurityContext is the name of security context passed to handlers // HarborSecurityContext is the name of security context passed to handlers
HarborSecurityContext = "harbor_security_context" HarborSecurityContext key = "harbor_security_context"
// HarborProjectManager is the name of project manager passed to handlers // HarborProjectManager is the name of project manager passed to handlers
HarborProjectManager = "harbor_project_manager" HarborProjectManager key = "harbor_project_manager"
) )
// SecurityFilter authenticates the request and passes a security context with it // SecurityFilter authenticates the request and passes a security context
// which can be used to do some authorization // and a project manager with it which can be used to do some authN & authZ
func SecurityFilter(ctx *beegoctx.Context) { func SecurityFilter(ctx *beegoctx.Context) {
if ctx == nil { if ctx == nil {
return return
@ -60,13 +66,16 @@ func fillContext(ctx *beegoctx.Context) {
// secret // secret
scrt := ctx.GetCookie("secret") scrt := ctx.GetCookie("secret")
if len(scrt) != 0 { if len(scrt) != 0 {
ctx.Input.SetData(HarborProjectManager, ct := context.WithValue(ctx.Request.Context(),
HarborProjectManager,
getProjectManager(ctx)) getProjectManager(ctx))
log.Info("creating a secret security context...") log.Info("creating a secret security context...")
ctx.Input.SetData(HarborSecurityContext, ct = context.WithValue(ct, HarborSecurityContext,
secret.NewSecurityContext(scrt, config.SecretStore)) secret.NewSecurityContext(scrt, config.SecretStore))
ctx.Request = ctx.Request.WithContext(ct)
return return
} }
@ -113,11 +122,12 @@ func fillContext(ctx *beegoctx.Context) {
} }
pm := getProjectManager(ctx) pm := getProjectManager(ctx)
ctx.Input.SetData(HarborProjectManager, pm) ct := context.WithValue(ctx.Request.Context(), HarborProjectManager, pm)
log.Info("creating a rbac security context...") log.Info("creating a rbac security context...")
ctx.Input.SetData(HarborSecurityContext, ct = context.WithValue(ct, HarborSecurityContext,
rbac.NewSecurityContext(user, pm)) rbac.NewSecurityContext(user, pm))
ctx.Request = ctx.Request.WithContext(ct)
return return
} }
@ -133,3 +143,41 @@ func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager {
log.Info("filling a project manager based on pms...") log.Info("filling a project manager based on pms...")
return nil return nil
} }
// GetSecurityContext tries to get security context from request and returns it
func GetSecurityContext(req *http.Request) (security.Context, error) {
if req == nil {
return nil, fmt.Errorf("request is nil")
}
ctx := req.Context().Value(HarborSecurityContext)
if ctx == nil {
return nil, fmt.Errorf("the security context got from request is nil")
}
c, ok := ctx.(security.Context)
if !ok {
return nil, fmt.Errorf("the variable got from request is not security context type")
}
return c, nil
}
// GetProjectManager tries to get project manager from request and returns it
func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error) {
if req == nil {
return nil, fmt.Errorf("request is nil")
}
pm := req.Context().Value(HarborProjectManager)
if pm == nil {
return nil, fmt.Errorf("the project manager got from request is nil")
}
p, ok := pm.(projectmanager.ProjectManager)
if !ok {
return nil, fmt.Errorf("the variable got from request is not project manager type")
}
return p, nil
}

View File

@ -15,6 +15,7 @@
package filter package filter
import ( import (
"context"
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
@ -26,7 +27,7 @@ import (
"time" "time"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/astaxie/beego/context" beegoctx "github.com/astaxie/beego/context"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
@ -36,6 +37,8 @@ import (
_ "github.com/vmware/harbor/src/ui/auth/db" _ "github.com/vmware/harbor/src/ui/auth/db"
_ "github.com/vmware/harbor/src/ui/auth/ldap" _ "github.com/vmware/harbor/src/ui/auth/ldap"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/projectmanager"
"github.com/vmware/harbor/src/ui/projectmanager/db"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -209,9 +212,9 @@ func TestFillContext(t *testing.T) {
assert.NotNil(t, projectManager(ctx)) assert.NotNil(t, projectManager(ctx))
} }
func newContext(req *http.Request) (*context.Context, error) { func newContext(req *http.Request) (*beegoctx.Context, error) {
var err error var err error
ctx := context.NewContext() ctx := beegoctx.NewContext()
ctx.Reset(httptest.NewRecorder(), req) ctx.Reset(httptest.NewRecorder(), req)
if req != nil { if req != nil {
ctx.Input.CruSession, err = beego.GlobalSessions.SessionStart(ctx.ResponseWriter, req) ctx.Input.CruSession, err = beego.GlobalSessions.SessionStart(ctx.ResponseWriter, req)
@ -235,10 +238,77 @@ func addSessionIDToCookie(req *http.Request, sessionID string) {
req.AddCookie(cookie) req.AddCookie(cookie)
} }
func securityContext(ctx *context.Context) interface{} { func securityContext(ctx *beegoctx.Context) interface{} {
return ctx.Input.Data()[HarborSecurityContext] c, err := GetSecurityContext(ctx.Request)
if err != nil {
return nil
}
return c
} }
func projectManager(ctx *context.Context) interface{} { func projectManager(ctx *beegoctx.Context) interface{} {
return ctx.Input.Data()[HarborProjectManager] if ctx.Request == nil {
return nil
}
return ctx.Request.Context().Value(HarborProjectManager)
}
func TestGetSecurityContext(t *testing.T) {
// nil request
ctx, err := GetSecurityContext(nil)
assert.NotNil(t, err)
// the request contains no security context
req, err := http.NewRequest("", "", nil)
assert.Nil(t, err)
ctx, err = GetSecurityContext(req)
assert.NotNil(t, err)
// the request contains a variable which is not the correct type
req, err = http.NewRequest("", "", nil)
assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(),
HarborSecurityContext, "test"))
ctx, err = GetSecurityContext(req)
assert.NotNil(t, err)
// the request contains a correct variable
req, err = http.NewRequest("", "", nil)
assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(),
HarborSecurityContext, rbac.NewSecurityContext(nil, nil)))
ctx, err = GetSecurityContext(req)
assert.Nil(t, err)
_, ok := ctx.(security.Context)
assert.True(t, ok)
}
func TestGetProjectManager(t *testing.T) {
// nil request
pm, err := GetProjectManager(nil)
assert.NotNil(t, err)
// the request contains no project manager
req, err := http.NewRequest("", "", nil)
assert.Nil(t, err)
pm, err = GetProjectManager(req)
assert.NotNil(t, err)
// the request contains a variable which is not the correct type
req, err = http.NewRequest("", "", nil)
assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(),
HarborProjectManager, "test"))
pm, err = GetProjectManager(req)
assert.NotNil(t, err)
// the request contains a correct variable
req, err = http.NewRequest("", "", nil)
assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(),
HarborProjectManager, &db.ProjectManager{}))
pm, err = GetProjectManager(req)
assert.Nil(t, err)
_, ok := pm.(projectmanager.ProjectManager)
assert.True(t, ok)
} }

View File

@ -23,7 +23,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config" "github.com/vmware/harbor/src/ui/config"
@ -74,7 +74,8 @@ func GetResourceActions(scopes []string) []*token.ResourceActions {
} }
//filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions. //filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions.
func filterAccess(access []*token.ResourceActions, u userInfo, filters map[string]accessFilter) error { func filterAccess(access []*token.ResourceActions, ctx security.Context,
filters map[string]accessFilter) error {
var err error var err error
for _, a := range access { for _, a := range access {
f, ok := filters[a.Type] f, ok := filters[a.Type]
@ -83,8 +84,8 @@ func filterAccess(access []*token.ResourceActions, u userInfo, filters map[strin
log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name) log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name)
continue continue
} }
err = f.filter(u, a) err = f.filter(ctx, a)
log.Debugf("user: %s, access: %v", u.name, a) log.Debugf("user: %s, access: %v", ctx.GetUsername(), a)
if err != nil { if err != nil {
return err return err
} }
@ -92,31 +93,23 @@ func filterAccess(access []*token.ResourceActions, u userInfo, filters map[strin
return nil return nil
} }
// TODO merge RegistryTokenForUI NotaryTokenForUI genTokenForUI
// to one function
//RegistryTokenForUI calls genTokenForUI to get raw token for registry //RegistryTokenForUI calls genTokenForUI to get raw token for registry
func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) { func RegistryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) {
return genTokenForUI(username, service, scopes, registryFilterMap) return genTokenForUI(username, service, scopes)
} }
//NotaryTokenForUI calls genTokenForUI to get raw token for notary //NotaryTokenForUI calls genTokenForUI to get raw token for notary
func NotaryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) { func NotaryTokenForUI(username string, service string, scopes []string) (string, int, *time.Time, error) {
return genTokenForUI(username, service, scopes, notaryFilterMap) return genTokenForUI(username, service, scopes)
} }
// genTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy. // genTokenForUI is for the UI process to call, so it won't establish a https connection from UI to proxy.
func genTokenForUI(username string, service string, scopes []string, filters map[string]accessFilter) (string, int, *time.Time, error) { func genTokenForUI(username string, service string,
isAdmin, err := dao.IsAdminRole(username) scopes []string) (string, int, *time.Time, error) {
if err != nil {
return "", 0, nil, err
}
u := userInfo{
name: username,
allPerm: isAdmin,
}
access := GetResourceActions(scopes) access := GetResourceActions(scopes)
err = filterAccess(access, u, filters)
if err != nil {
return "", 0, nil, err
}
return MakeRawToken(username, service, access) return MakeRawToken(username, service, access)
} }

View File

@ -16,12 +16,14 @@ package token
import ( import (
"fmt" "fmt"
"github.com/docker/distribution/registry/auth/token"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
"net/http" "net/http"
"strings" "strings"
"github.com/docker/distribution/registry/auth/token"
"github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/config"
"github.com/vmware/harbor/src/ui/filter"
) )
var creatorMap map[string]Creator var creatorMap map[string]Creator
@ -54,19 +56,12 @@ func InitCreators() {
}, },
} }
creatorMap[notary] = &generalCreator{ creatorMap[notary] = &generalCreator{
validators: []ReqValidator{
&basicAuthValidator{},
},
service: notary, service: notary,
filterMap: notaryFilterMap, filterMap: notaryFilterMap,
} }
} }
creatorMap[registry] = &generalCreator{ creatorMap[registry] = &generalCreator{
validators: []ReqValidator{
&secretValidator{config.JobserviceSecret()},
&basicAuthValidator{},
},
service: registry, service: registry,
filterMap: registryFilterMap, filterMap: registryFilterMap,
} }
@ -127,18 +122,19 @@ func parseImg(s string) (*image, error) {
// An accessFilter will filter access based on userinfo // An accessFilter will filter access based on userinfo
type accessFilter interface { type accessFilter interface {
filter(user userInfo, a *token.ResourceActions) error filter(ctx security.Context, a *token.ResourceActions) error
} }
type registryFilter struct { type registryFilter struct {
} }
func (reg registryFilter) filter(user userInfo, a *token.ResourceActions) error { func (reg registryFilter) filter(ctx security.Context,
a *token.ResourceActions) error {
//Do not filter if the request is to access registry catalog //Do not filter if the request is to access registry catalog
if a.Name != "catalog" { if a.Name != "catalog" {
return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name) return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name)
} }
if !user.allPerm { if !ctx.IsSysAdmin() {
//Set the actions to empty is the user is not admin //Set the actions to empty is the user is not admin
a.Actions = []string{} a.Actions = []string{}
} }
@ -150,7 +146,7 @@ type repositoryFilter struct {
parser imageParser parser imageParser
} }
func (rep repositoryFilter) filter(user userInfo, a *token.ResourceActions) error { func (rep repositoryFilter) filter(ctx security.Context, a *token.ResourceActions) error {
//clear action list to assign to new acess element after perm check. //clear action list to assign to new acess element after perm check.
img, err := rep.parser.parse(a.Name) img, err := rep.parser.parse(a.Name)
if err != nil { if err != nil {
@ -158,37 +154,21 @@ func (rep repositoryFilter) filter(user userInfo, a *token.ResourceActions) erro
} }
project := img.namespace project := img.namespace
permission := "" permission := ""
if user.allPerm { if ctx.HasAllPerm(project) {
exist, err := dao.ProjectExists(project) permission = "RWM"
if err != nil { } else if ctx.HasWritePerm(project) {
log.Errorf("Error occurred in CheckExistProject: %v", err) permission = "RW"
//just leave empty permission } else if ctx.HasReadPerm(project) {
return nil permission = "R"
}
if exist {
permission = "RWM"
} else {
log.Infof("project %s does not exist, set empty permission for admin\n", project)
}
} else {
permission, err = dao.GetPermission(user.name, project)
if err != nil {
log.Errorf("Error occurred in GetPermission: %v", err)
//just leave empty permission
return nil
}
if dao.IsProjectPublic(project) {
permission += "R"
}
} }
a.Actions = permToActions(permission) a.Actions = permToActions(permission)
return nil return nil
} }
type generalCreator struct { type generalCreator struct {
validators []ReqValidator service string
service string filterMap map[string]accessFilter
filterMap map[string]accessFilter
} }
type unauthorizedError struct{} type unauthorizedError struct{}
@ -198,7 +178,6 @@ func (e *unauthorizedError) Error() string {
} }
func (g generalCreator) Create(r *http.Request) (*tokenJSON, error) { func (g generalCreator) Create(r *http.Request) (*tokenJSON, error) {
var user *userInfo
var err error var err error
var scopes []string var scopes []string
scopeParm := r.URL.Query()["scope"] scopeParm := r.URL.Query()["scope"]
@ -206,25 +185,22 @@ func (g generalCreator) Create(r *http.Request) (*tokenJSON, error) {
scopes = strings.Split(r.URL.Query()["scope"][0], " ") scopes = strings.Split(r.URL.Query()["scope"][0], " ")
} }
log.Debugf("scopes: %v", scopes) log.Debugf("scopes: %v", scopes)
for _, v := range g.validators {
user, err = v.validate(r) ctx, err := filter.GetSecurityContext(r)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to get security context from request")
}
if user != nil {
break
}
} }
if user == nil {
// for docker login
if !ctx.IsAuthenticated() {
if len(scopes) == 0 { if len(scopes) == 0 {
return nil, &unauthorizedError{} return nil, &unauthorizedError{}
} }
user = &userInfo{}
} }
access := GetResourceActions(scopes) access := GetResourceActions(scopes)
err = filterAccess(access, *user, g.filterMap) err = filterAccess(access, ctx, g.filterMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return makeToken(user.name, g.service, access) return makeToken(ctx.GetUsername(), g.service, access)
} }

View File

@ -22,13 +22,14 @@ import (
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/ui/config"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"runtime" "runtime"
"testing" "testing"
"github.com/vmware/harbor/src/common/utils/test"
"github.com/vmware/harbor/src/ui/config"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -199,6 +200,31 @@ func TestEndpointParser(t *testing.T) {
} }
} }
type fakeSecurityContext struct {
isAdmin bool
}
func (f *fakeSecurityContext) IsAuthenticated() bool {
return true
}
func (f *fakeSecurityContext) GetUsername() string {
return "jack"
}
func (f *fakeSecurityContext) IsSysAdmin() bool {
return f.isAdmin
}
func (f *fakeSecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
return false
}
func (f *fakeSecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
return false
}
func (f *fakeSecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
return false
}
func TestFilterAccess(t *testing.T) { func TestFilterAccess(t *testing.T) {
//TODO put initial data in DB to verify repository filter. //TODO put initial data in DB to verify repository filter.
var err error var err error
@ -206,8 +232,7 @@ func TestFilterAccess(t *testing.T) {
a1 := GetResourceActions(s) a1 := GetResourceActions(s)
a2 := GetResourceActions(s) a2 := GetResourceActions(s)
a3 := GetResourceActions(s) a3 := GetResourceActions(s)
u1 := userInfo{"jack", true}
u2 := userInfo{"jack", false}
ra1 := token.ResourceActions{ ra1 := token.ResourceActions{
Type: "registry", Type: "registry",
Name: "catalog", Name: "catalog",
@ -218,13 +243,21 @@ func TestFilterAccess(t *testing.T) {
Name: "catalog", Name: "catalog",
Actions: []string{}, Actions: []string{},
} }
err = filterAccess(a1, u1, registryFilterMap) err = filterAccess(a1, &fakeSecurityContext{
isAdmin: true,
}, registryFilterMap)
assert.Nil(t, err, "Unexpected error: %v", err) assert.Nil(t, err, "Unexpected error: %v", err)
assert.Equal(t, ra1, *a1[0], "Mismatch after registry filter Map") assert.Equal(t, ra1, *a1[0], "Mismatch after registry filter Map")
err = filterAccess(a2, u1, notaryFilterMap)
err = filterAccess(a2, &fakeSecurityContext{
isAdmin: true,
}, notaryFilterMap)
assert.Nil(t, err, "Unexpected error: %v", err) assert.Nil(t, err, "Unexpected error: %v", err)
assert.Equal(t, ra2, *a2[0], "Mismatch after notary filter Map") assert.Equal(t, ra2, *a2[0], "Mismatch after notary filter Map")
err = filterAccess(a3, u2, registryFilterMap)
err = filterAccess(a3, &fakeSecurityContext{
isAdmin: false,
}, registryFilterMap)
assert.Nil(t, err, "Unexpected error: %v", err) assert.Nil(t, err, "Unexpected error: %v", err)
assert.Equal(t, ra2, *a3[0], "Mismatch after registry filter Map") assert.Equal(t, ra2, *a3[0], "Mismatch after registry filter Map")
} }

View File

@ -1,83 +0,0 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package token
import (
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/auth"
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
"net/http"
)
//For filtering permission by token creators.
type userInfo struct {
name string
allPerm bool
}
//ReqValidator validates request based on different rules and returns userInfo
type ReqValidator interface {
validate(req *http.Request) (*userInfo, error)
}
type secretValidator struct {
secret string
}
var jobServiceUserInfo userInfo
func init() {
jobServiceUserInfo = userInfo{
name: "job-service-user",
allPerm: true,
}
}
func (sv secretValidator) validate(r *http.Request) (*userInfo, error) {
if svc_utils.VerifySecret(r, sv.secret) {
return &jobServiceUserInfo, nil
}
return nil, nil
}
type basicAuthValidator struct {
}
func (ba basicAuthValidator) validate(r *http.Request) (*userInfo, error) {
uid, password, _ := r.BasicAuth()
user, err := auth.Login(models.AuthModel{
Principal: uid,
Password: password,
})
if err != nil {
log.Errorf("Error occurred in UserLogin: %v", err)
return nil, err
}
if user == nil {
log.Warningf("Invalid credentials for uid: %s", uid)
return nil, nil
}
isAdmin, err := dao.IsAdminRole(user.UserID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole: %v", err)
}
info := &userInfo{
name: user.Username,
allPerm: isAdmin,
}
return info, nil
}