add security context based on admiral

This commit is contained in:
Wenkai Yin 2017-06-18 13:51:42 +08:00
parent 6e89f11ffc
commit 8191f4a476
13 changed files with 602 additions and 122 deletions

View File

@ -107,6 +107,10 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
// in projectIDs // in projectIDs
func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) { func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) {
repositories := []*models.RepoRecord{} repositories := []*models.RepoRecord{}
if len(projectIDs) == 0 {
return repositories, nil
}
_, err := GetOrmer().QueryTable(&models.RepoRecord{}). _, err := GetOrmer().QueryTable(&models.RepoRecord{}).
Filter("project_id__in", projectIDs). Filter("project_id__in", projectIDs).
OrderBy("-pull_count"). OrderBy("-pull_count").

View File

@ -0,0 +1,174 @@
// 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 admiral
import (
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/security/authcontext"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/projectmanager"
)
// SecurityContext implements security.Context interface based on
// auth context and project manager
type SecurityContext struct {
ctx *authcontext.AuthContext
pm projectmanager.ProjectManager
}
// NewSecurityContext ...
func NewSecurityContext(ctx *authcontext.AuthContext, pm projectmanager.ProjectManager) *SecurityContext {
return &SecurityContext{
ctx: ctx,
pm: pm,
}
}
// IsAuthenticated returns true if the user has been authenticated
func (s *SecurityContext) IsAuthenticated() bool {
if s.ctx == nil {
return false
}
return len(s.ctx.GetUsername()) > 0
}
// 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.ctx.GetUsername()
}
// 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.ctx.IsSysAdmin()
}
// 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
}
if name, ok := projectIDOrName.(string); ok {
return s.ctx.HasReadPerm(name)
}
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
if err != nil {
log.Errorf("failed to get roles of user %s to project %v: %v",
s.GetUsername(), projectIDOrName, err)
return false
}
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
}
if name, ok := projectIDOrName.(string); ok {
return s.ctx.HasWritePerm(name)
}
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
if err != nil {
log.Errorf("failed to get roles of user %s to project %v: %v",
s.GetUsername(), projectIDOrName, err)
return false
}
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
}
if name, ok := projectIDOrName.(string); ok {
return s.ctx.HasAllPerm(name)
}
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
if err != nil {
log.Errorf("failed to get roles of user %s to project %v: %v",
s.GetUsername(), projectIDOrName, err)
return false
}
for _, role := range roles {
switch role {
case common.RoleProjectAdmin:
return true
}
}
return false
}

View File

@ -0,0 +1,17 @@
// 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 admiral
// TODO add test cases

View File

@ -0,0 +1,129 @@
// 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 authcontext
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/vmware/harbor/src/ui/config"
)
const (
// AuthTokenHeader is the key of auth token header
AuthTokenHeader = "x-xenon-auth-token"
sysAdminRole = "CLOUD_ADMIN"
projectAdminRole = "DEVOPS_ADMIN"
developerRole = "DEVELOPER"
guestRole = "GUEST"
)
var client = &http.Client{
Transport: &http.Transport{},
}
// AuthContext ...
type AuthContext struct {
PrincipalID string `json:"principalId"`
Name string `json:"name"`
Roles []string `json:"projects"`
Projects map[string][]string `json:"roles"`
}
// GetUsername ...
func (a *AuthContext) GetUsername() string {
return a.PrincipalID
}
// IsSysAdmin ...
func (a *AuthContext) IsSysAdmin() bool {
isSysAdmin := false
for _, role := range a.Roles {
// TODO update the value of role when admiral API is ready
if role == sysAdminRole {
isSysAdmin = true
break
}
}
return isSysAdmin
}
// HasReadPerm ...
func (a *AuthContext) HasReadPerm(project string) bool {
_, exist := a.Projects[project]
return exist
}
// HasWritePerm ...
func (a *AuthContext) HasWritePerm(project string) bool {
roles, _ := a.Projects[project]
for _, role := range roles {
if role == projectAdminRole || role == developerRole {
return true
}
}
return false
}
// HasAllPerm ...
func (a *AuthContext) HasAllPerm(project string) bool {
roles, _ := a.Projects[project]
for _, role := range roles {
if role == projectAdminRole {
return true
}
}
return false
}
// GetByToken ...
func GetByToken(token string) (*AuthContext, error) {
endpoint := config.AdmiralEndpoint()
path := strings.TrimRight(endpoint, "/") + "/sso/auth-context"
req, err := http.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, err
}
req.Header.Add(AuthTokenHeader, token)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get auth context by token: %d %s",
resp.StatusCode, string(data))
}
ctx := &AuthContext{
Projects: make(map[string][]string),
}
if err = json.Unmarshal(data, ctx); err != nil {
return nil, err
}
return ctx, nil
}

View File

@ -0,0 +1,17 @@
// 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 authcontext
// TODO add test cases

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package rbac package local
import ( import (
"github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package rbac package local
import ( import (
"fmt" "fmt"

View File

@ -39,14 +39,14 @@ type BaseController struct {
func (b *BaseController) Prepare() { func (b *BaseController) Prepare() {
ctx, err := filter.GetSecurityContext(b.Ctx.Request) ctx, err := filter.GetSecurityContext(b.Ctx.Request)
if err != nil { if err != nil {
log.Error("failed to get security context") log.Errorf("failed to get security context: %v", err)
b.CustomAbort(http.StatusInternalServerError, "") b.CustomAbort(http.StatusInternalServerError, "")
} }
b.SecurityCtx = ctx b.SecurityCtx = ctx
pm, err := filter.GetProjectManager(b.Ctx.Request) pm, err := filter.GetProjectManager(b.Ctx.Request)
if err != nil { if err != nil {
log.Error("failed to get project manager") log.Errorf("failed to get project manager: %v", err)
b.CustomAbort(http.StatusInternalServerError, "") b.CustomAbort(http.StatusInternalServerError, "")
} }
b.ProjectMgr = pm b.ProjectMgr = pm

View File

@ -87,6 +87,7 @@ func init() {
beego.BConfig.WebConfig.Session.SessionOn = true beego.BConfig.WebConfig.Session.SessionOn = true
beego.TestBeegoInit(apppath) beego.TestBeegoInit(apppath)
filter.Init()
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
beego.Router("/api/search/", &SearchAPI{}) beego.Router("/api/search/", &SearchAPI{})

View File

@ -28,6 +28,7 @@ import (
"github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager"
"github.com/vmware/harbor/src/ui/projectmanager/db" "github.com/vmware/harbor/src/ui/projectmanager/db"
"github.com/vmware/harbor/src/ui/projectmanager/pms"
) )
const ( const (
@ -96,10 +97,16 @@ func initSecretStore() {
func initProjectManager() { func initProjectManager() {
if !WithAdmiral() { if !WithAdmiral() {
// standalone
log.Info("initializing the project manager based on database...") log.Info("initializing the project manager based on database...")
GlobalProjectMgr = &db.ProjectManager{} GlobalProjectMgr = &db.ProjectManager{}
return
} }
// TODO create project manager based on pms
// integration with admiral
// TODO create project manager based on pms using service account
log.Info("initializing the project manager based on PMS...")
GlobalProjectMgr = pms.NewProjectManager(AdmiralEndpoint(), "")
} }
// Load configurations // Load configurations

View File

@ -22,8 +22,11 @@ import (
beegoctx "github.com/astaxie/beego/context" beegoctx "github.com/astaxie/beego/context"
"github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/models"
secstore "github.com/vmware/harbor/src/common/secret"
"github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/security/rbac" "github.com/vmware/harbor/src/common/security/admiral"
"github.com/vmware/harbor/src/common/security/authcontext"
"github.com/vmware/harbor/src/common/security/local"
"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"
"github.com/vmware/harbor/src/ui/auth" "github.com/vmware/harbor/src/ui/auth"
@ -35,12 +38,33 @@ import (
type key string type key string
const ( const (
// HarborSecurityContext is the name of security context passed to handlers securCtxKey key = "harbor_security_context"
HarborSecurityContext key = "harbor_security_context" pmKey key = "harbor_project_manager"
// HarborProjectManager is the name of project manager passed to handlers
HarborProjectManager key = "harbor_project_manager"
) )
var (
reqCtxModifiers []ReqCtxModifier
)
// Init ReqCtxMofiers list
func Init() {
// integration with admiral
if config.WithAdmiral() {
reqCtxModifiers = []ReqCtxModifier{
&secretReqCtxModifier{},
&tokenReqCtxModifier{},
&unauthorizedReqCtxModifier{}}
return
}
// standalone
reqCtxModifiers = []ReqCtxModifier{
&secretReqCtxModifier{},
&basicAuthReqCtxModifier{},
&sessionReqCtxModifier{},
&unauthorizedReqCtxModifier{}}
}
// SecurityFilter authenticates the request and passes a security context // SecurityFilter authenticates the request and passes a security context
// and a project manager with it which can be used to do some authN & authZ // 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) {
@ -54,93 +78,175 @@ func SecurityFilter(ctx *beegoctx.Context) {
} }
if !strings.HasPrefix(req.URL.RequestURI(), "/api/") && if !strings.HasPrefix(req.URL.RequestURI(), "/api/") &&
!strings.HasPrefix(req.URL.RequestURI(), "/service/") { !strings.HasPrefix(req.URL.RequestURI(), "/service/token") {
return return
} }
// fill ctx with security context and project manager // add security context and project manager to request context
fillContext(ctx) for _, modifier := range reqCtxModifiers {
if modifier.Modify(ctx) {
break
}
}
} }
func fillContext(ctx *beegoctx.Context) { // ReqCtxModifier modifies the context of request
// secret type ReqCtxModifier interface {
Modify(*beegoctx.Context) bool
}
type secretReqCtxModifier struct {
store *secstore.Store
}
func (s *secretReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
scrt := ctx.GetCookie("secret") scrt := ctx.GetCookie("secret")
if len(scrt) != 0 { if len(scrt) == 0 {
ct := context.WithValue(ctx.Request.Context(), return false
HarborProjectManager,
getProjectManager(ctx))
log.Info("creating a secret security context...")
ct = context.WithValue(ct, HarborSecurityContext,
secret.NewSecurityContext(scrt, config.SecretStore))
ctx.Request = ctx.Request.WithContext(ct)
return
} }
var user *models.User log.Debug("got secret from request")
var err error
// basic auth var pm projectmanager.ProjectManager
if config.WithAdmiral() {
// TODO project manager with harbor service accout
} else {
log.Debug("using local database project manager")
pm = config.GlobalProjectMgr
}
log.Debug("creating a secret security context...")
securCtx := secret.NewSecurityContext(scrt, s.store)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
}
type basicAuthReqCtxModifier struct{}
func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
username, password, ok := ctx.Request.BasicAuth() username, password, ok := ctx.Request.BasicAuth()
if ok { if !ok {
// TODO the return data contains other params when integrated return false
// with vic }
user, err = auth.Login(models.AuthModel{
user, err := auth.Login(models.AuthModel{
Principal: username, Principal: username,
Password: password, Password: password,
}) })
if err != nil { if err != nil {
log.Errorf("failed to authenticate %s: %v", username, err) log.Errorf("failed to authenticate %s: %v", username, err)
return false
} }
if user != nil { if user == nil {
log.Info("got user information via basic auth") return false
}
} }
// session var securCtx security.Context
if user == nil { var pm projectmanager.ProjectManager
log.Debug("got user information via basic auth")
if config.WithAdmiral() {
// integration with admiral
// we can add logic here to support basic auth in integration mode
log.Debug("basic auth isn't supported in integration mode")
return false
}
// standalone
log.Debug("using local database project manager")
pm = config.GlobalProjectMgr
log.Debug("creating local database security context...")
securCtx = local.NewSecurityContext(user, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
}
type sessionReqCtxModifier struct{}
func (s *sessionReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
username := ctx.Input.Session("username") username := ctx.Input.Session("username")
isSysAdmin := ctx.Input.Session("isSysAdmin") if username == nil {
if username != nil { return false
user = &models.User{ }
log.Debug("got user information from session")
user := &models.User{
Username: username.(string), Username: username.(string),
} }
isSysAdmin := ctx.Input.Session("isSysAdmin")
if isSysAdmin != nil && isSysAdmin.(bool) { if isSysAdmin != nil && isSysAdmin.(bool) {
user.HasAdminRole = 1 user.HasAdminRole = 1
} }
log.Info("got user information from session")
log.Debug("using local database project manager")
pm := config.GlobalProjectMgr
log.Debug("creating local database security context...")
securCtx := local.NewSecurityContext(user, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
} }
// TODO maybe need to get token from session type tokenReqCtxModifier struct{}
func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
token := ctx.Request.Header.Get(authcontext.AuthTokenHeader)
if len(token) == 0 {
return false
} }
if user == nil { log.Debug("got token from request")
log.Info("user information is nil")
authContext, err := authcontext.GetByToken(token)
if err != nil {
log.Errorf("failed to get auth context: %v", err)
return false
} }
pm := getProjectManager(ctx) log.Debug("creating PMS project manager...")
ct := context.WithValue(ctx.Request.Context(), HarborProjectManager, pm) pm := pms.NewProjectManager(config.AdmiralEndpoint(), token)
log.Debug("creating admiral security context...")
securCtx := admiral.NewSecurityContext(authContext, pm)
setSecurCtxAndPM(ctx.Request, securCtx, pm)
log.Info("creating a rbac security context...") return true
ct = context.WithValue(ct, HarborSecurityContext,
rbac.NewSecurityContext(user, pm))
ctx.Request = ctx.Request.WithContext(ct)
return
} }
func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager { // use this one as the last modifier in the modifier list for unauthorized request
if !config.WithAdmiral() { type unauthorizedReqCtxModifier struct{}
log.Info("filling a project manager based on database...")
return config.GlobalProjectMgr func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
log.Debug("user information is nil")
var securCtx security.Context
var pm projectmanager.ProjectManager
if config.WithAdmiral() {
// integration with admiral
log.Debug("creating PMS project manager...")
pm = pms.NewProjectManager(config.AdmiralEndpoint(), "")
log.Debug("creating admiral security context...")
securCtx = admiral.NewSecurityContext(nil, pm)
} else {
// standalone
log.Debug("using local database project manager")
pm = config.GlobalProjectMgr
log.Debug("creating local database security context...")
securCtx = local.NewSecurityContext(nil, pm)
}
setSecurCtxAndPM(ctx.Request, securCtx, pm)
return true
} }
log.Info("filling a project manager based on PMS...") func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) {
// TODO pass the token to the function addToReqContext(req, securCtxKey, ctx)
return pms.NewProjectManager(config.AdmiralEndpoint(), "") addToReqContext(req, pmKey, pm)
}
func addToReqContext(req *http.Request, key, value interface{}) {
*req = *(req.WithContext(context.WithValue(req.Context(), key, value)))
} }
// GetSecurityContext tries to get security context from request and returns it // GetSecurityContext tries to get security context from request and returns it
@ -149,7 +255,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) {
return nil, fmt.Errorf("request is nil") return nil, fmt.Errorf("request is nil")
} }
ctx := req.Context().Value(HarborSecurityContext) ctx := req.Context().Value(securCtxKey)
if ctx == nil { if ctx == nil {
return nil, fmt.Errorf("the security context got from request is nil") return nil, fmt.Errorf("the security context got from request is nil")
} }
@ -168,7 +274,7 @@ func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error)
return nil, fmt.Errorf("request is nil") return nil, fmt.Errorf("request is nil")
} }
pm := req.Context().Value(HarborProjectManager) pm := req.Context().Value(pmKey)
if pm == nil { if pm == nil {
return nil, fmt.Errorf("the project manager got from request is nil") return nil, fmt.Errorf("the project manager got from request is nil")
} }

View File

@ -32,7 +32,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/security" "github.com/vmware/harbor/src/common/security"
"github.com/vmware/harbor/src/common/security/rbac" "github.com/vmware/harbor/src/common/security/local"
"github.com/vmware/harbor/src/common/security/secret" "github.com/vmware/harbor/src/common/security/secret"
_ "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"
@ -73,6 +73,8 @@ func TestMain(m *testing.M) {
log.Fatalf("failed to initialize database: %v", err) log.Fatalf("failed to initialize database: %v", err)
} }
Init()
os.Exit(m.Run()) os.Exit(m.Run())
} }
@ -80,7 +82,7 @@ func TestSecurityFilter(t *testing.T) {
// nil request // nil request
ctx, err := newContext(nil) ctx, err := newContext(nil)
if err != nil { if err != nil {
t.Fatalf("failed to crate context: %v", err) t.Fatalf("failed to create context: %v", err)
} }
SecurityFilter(ctx) SecurityFilter(ctx)
assert.Nil(t, securityContext(ctx)) assert.Nil(t, securityContext(ctx))
@ -116,8 +118,7 @@ func TestSecurityFilter(t *testing.T) {
assert.NotNil(t, projectManager(ctx)) assert.NotNil(t, projectManager(ctx))
} }
func TestFillContext(t *testing.T) { func TestSecretReqCtxModifier(t *testing.T) {
// secret
req, err := http.NewRequest(http.MethodGet, req, err := http.NewRequest(http.MethodGet,
"http://127.0.0.1/api/projects/", nil) "http://127.0.0.1/api/projects/", nil)
if err != nil { if err != nil {
@ -133,13 +134,40 @@ func TestFillContext(t *testing.T) {
t.Fatalf("failed to crate context: %v", err) t.Fatalf("failed to crate context: %v", err)
} }
fillContext(ctx) modifier := &secretReqCtxModifier{}
modified := modifier.Modify(ctx)
assert.True(t, modified)
assert.IsType(t, &secret.SecurityContext{}, assert.IsType(t, &secret.SecurityContext{},
securityContext(ctx)) securityContext(ctx))
assert.NotNil(t, projectManager(ctx)) assert.NotNil(t, projectManager(ctx))
}
// session func TestBasicAuthReqCtxModifier(t *testing.T) {
req, err = http.NewRequest(http.MethodGet, req, err := http.NewRequest(http.MethodGet,
"http://127.0.0.1/api/projects/", nil)
if err != nil {
t.Fatalf("failed to create request: %v", req)
}
req.SetBasicAuth("admin", "Harbor12345")
ctx, err := newContext(req)
if err != nil {
t.Fatalf("failed to crate context: %v", err)
}
modifier := &basicAuthReqCtxModifier{}
modified := modifier.Modify(ctx)
assert.True(t, modified)
sc := securityContext(ctx)
assert.IsType(t, &local.SecurityContext{}, sc)
s := sc.(security.Context)
assert.Equal(t, "admin", s.GetUsername())
assert.NotNil(t, projectManager(ctx))
}
func TestSessionReqCtxModifier(t *testing.T) {
req, err := http.NewRequest(http.MethodGet,
"http://127.0.0.1/api/projects/", nil) "http://127.0.0.1/api/projects/", nil)
if err != nil { if err != nil {
t.Fatalf("failed to create request: %v", req) t.Fatalf("failed to create request: %v", req)
@ -162,52 +190,47 @@ func TestFillContext(t *testing.T) {
} }
addSessionIDToCookie(req, store.SessionID()) addSessionIDToCookie(req, store.SessionID())
ctx, err = newContext(req) ctx, err := newContext(req)
if err != nil { if err != nil {
t.Fatalf("failed to crate context: %v", err) t.Fatalf("failed to crate context: %v", err)
} }
fillContext(ctx)
modifier := &sessionReqCtxModifier{}
modified := modifier.Modify(ctx)
assert.True(t, modified)
sc := securityContext(ctx) sc := securityContext(ctx)
assert.IsType(t, &rbac.SecurityContext{}, sc) assert.IsType(t, &local.SecurityContext{}, sc)
s := sc.(security.Context) s := sc.(security.Context)
assert.Equal(t, "admin", s.GetUsername()) assert.Equal(t, "admin", s.GetUsername())
assert.True(t, s.IsSysAdmin()) assert.True(t, s.IsSysAdmin())
assert.NotNil(t, projectManager(ctx)) assert.NotNil(t, projectManager(ctx))
// basic auth
req, err = http.NewRequest(http.MethodGet,
"http://127.0.0.1/api/projects/", nil)
if err != nil {
t.Fatalf("failed to create request: %v", req)
} }
req.SetBasicAuth("admin", "Harbor12345")
ctx, err = newContext(req) // TODO add test case
if err != nil { func TestTokenReqCtxModifier(t *testing.T) {
t.Fatalf("failed to crate context: %v", err)
} }
fillContext(ctx)
sc = securityContext(ctx)
assert.IsType(t, &rbac.SecurityContext{}, sc)
s = sc.(security.Context)
assert.Equal(t, "admin", s.GetUsername())
assert.NotNil(t, projectManager(ctx))
// no credential func TestUnauthorizedReqCtxModifier(t *testing.T) {
req, err = http.NewRequest(http.MethodGet, req, err := http.NewRequest(http.MethodGet,
"http://127.0.0.1/api/projects/", nil) "http://127.0.0.1/api/projects/", nil)
if err != nil { if err != nil {
t.Fatalf("failed to create request: %v", req) t.Fatalf("failed to create request: %v", req)
} }
ctx, err = newContext(req) ctx, err := newContext(req)
if err != nil { if err != nil {
t.Fatalf("failed to crate context: %v", err) t.Fatalf("failed to crate context: %v", err)
} }
fillContext(ctx)
sc = securityContext(ctx) modifier := &unauthorizedReqCtxModifier{}
assert.IsType(t, &rbac.SecurityContext{}, sc) modified := modifier.Modify(ctx)
s = sc.(security.Context) assert.True(t, modified)
sc := securityContext(ctx)
assert.NotNil(t, sc)
s := sc.(security.Context)
assert.False(t, s.IsAuthenticated()) assert.False(t, s.IsAuthenticated())
assert.NotNil(t, projectManager(ctx)) assert.NotNil(t, projectManager(ctx))
} }
@ -241,6 +264,7 @@ func addSessionIDToCookie(req *http.Request, sessionID string) {
func securityContext(ctx *beegoctx.Context) interface{} { func securityContext(ctx *beegoctx.Context) interface{} {
c, err := GetSecurityContext(ctx.Request) c, err := GetSecurityContext(ctx.Request)
if err != nil { if err != nil {
log.Printf("failed to get security context: %v \n", err)
return nil return nil
} }
return c return c
@ -250,7 +274,7 @@ func projectManager(ctx *beegoctx.Context) interface{} {
if ctx.Request == nil { if ctx.Request == nil {
return nil return nil
} }
return ctx.Request.Context().Value(HarborProjectManager) return ctx.Request.Context().Value(pmKey)
} }
func TestGetSecurityContext(t *testing.T) { func TestGetSecurityContext(t *testing.T) {
@ -268,7 +292,7 @@ func TestGetSecurityContext(t *testing.T) {
req, err = http.NewRequest("", "", nil) req, err = http.NewRequest("", "", nil)
assert.Nil(t, err) assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(), req = req.WithContext(context.WithValue(req.Context(),
HarborSecurityContext, "test")) securCtxKey, "test"))
ctx, err = GetSecurityContext(req) ctx, err = GetSecurityContext(req)
assert.NotNil(t, err) assert.NotNil(t, err)
@ -276,7 +300,7 @@ func TestGetSecurityContext(t *testing.T) {
req, err = http.NewRequest("", "", nil) req, err = http.NewRequest("", "", nil)
assert.Nil(t, err) assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(), req = req.WithContext(context.WithValue(req.Context(),
HarborSecurityContext, rbac.NewSecurityContext(nil, nil))) securCtxKey, local.NewSecurityContext(nil, nil)))
ctx, err = GetSecurityContext(req) ctx, err = GetSecurityContext(req)
assert.Nil(t, err) assert.Nil(t, err)
_, ok := ctx.(security.Context) _, ok := ctx.(security.Context)
@ -298,7 +322,7 @@ func TestGetProjectManager(t *testing.T) {
req, err = http.NewRequest("", "", nil) req, err = http.NewRequest("", "", nil)
assert.Nil(t, err) assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(), req = req.WithContext(context.WithValue(req.Context(),
HarborProjectManager, "test")) pmKey, "test"))
pm, err = GetProjectManager(req) pm, err = GetProjectManager(req)
assert.NotNil(t, err) assert.NotNil(t, err)
@ -306,7 +330,7 @@ func TestGetProjectManager(t *testing.T) {
req, err = http.NewRequest("", "", nil) req, err = http.NewRequest("", "", nil)
assert.Nil(t, err) assert.Nil(t, err)
req = req.WithContext(context.WithValue(req.Context(), req = req.WithContext(context.WithValue(req.Context(),
HarborProjectManager, &db.ProjectManager{})) pmKey, &db.ProjectManager{}))
pm, err = GetProjectManager(req) pm, err = GetProjectManager(req)
assert.Nil(t, err) assert.Nil(t, err)
_, ok := pm.(projectmanager.ProjectManager) _, ok := pm.(projectmanager.ProjectManager)

View File

@ -98,6 +98,7 @@ func main() {
log.Error(err) log.Error(err)
} }
filter.Init()
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
initRouters() initRouters()