From 8191f4a476ed4813b161e274a20236e9a0416eff Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Sun, 18 Jun 2017 13:51:42 +0800 Subject: [PATCH] add security context based on admiral --- src/common/dao/repository.go | 4 + src/common/security/admiral/context.go | 174 ++++++++++++ src/common/security/admiral/context_test.go | 17 ++ .../security/authcontext/authcontext.go | 129 +++++++++ .../security/authcontext/authcontext_test.go | 17 ++ .../security/{rbac => local}/context.go | 2 +- .../security/{rbac => local}/context_test.go | 2 +- src/ui/api/base.go | 4 +- src/ui/api/harborapi_test.go | 1 + src/ui/config/config.go | 9 +- src/ui/filter/security.go | 262 ++++++++++++------ src/ui/filter/security_test.go | 102 ++++--- src/ui/main.go | 1 + 13 files changed, 602 insertions(+), 122 deletions(-) create mode 100644 src/common/security/admiral/context.go create mode 100644 src/common/security/admiral/context_test.go create mode 100644 src/common/security/authcontext/authcontext.go create mode 100644 src/common/security/authcontext/authcontext_test.go rename src/common/security/{rbac => local}/context.go (99%) rename src/common/security/{rbac => local}/context_test.go (99%) diff --git a/src/common/dao/repository.go b/src/common/dao/repository.go index d9f47ddd3..d6da58f82 100644 --- a/src/common/dao/repository.go +++ b/src/common/dao/repository.go @@ -107,6 +107,10 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) { // in projectIDs func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) { repositories := []*models.RepoRecord{} + if len(projectIDs) == 0 { + return repositories, nil + } + _, err := GetOrmer().QueryTable(&models.RepoRecord{}). Filter("project_id__in", projectIDs). OrderBy("-pull_count"). diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go new file mode 100644 index 000000000..295e4ce81 --- /dev/null +++ b/src/common/security/admiral/context.go @@ -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 +} diff --git a/src/common/security/admiral/context_test.go b/src/common/security/admiral/context_test.go new file mode 100644 index 000000000..842cac2db --- /dev/null +++ b/src/common/security/admiral/context_test.go @@ -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 diff --git a/src/common/security/authcontext/authcontext.go b/src/common/security/authcontext/authcontext.go new file mode 100644 index 000000000..a9b924449 --- /dev/null +++ b/src/common/security/authcontext/authcontext.go @@ -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 +} diff --git a/src/common/security/authcontext/authcontext_test.go b/src/common/security/authcontext/authcontext_test.go new file mode 100644 index 000000000..609062c6b --- /dev/null +++ b/src/common/security/authcontext/authcontext_test.go @@ -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 diff --git a/src/common/security/rbac/context.go b/src/common/security/local/context.go similarity index 99% rename from src/common/security/rbac/context.go rename to src/common/security/local/context.go index 9c13e4a28..440bc3bcc 100644 --- a/src/common/security/rbac/context.go +++ b/src/common/security/local/context.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package rbac +package local import ( "github.com/vmware/harbor/src/common" diff --git a/src/common/security/rbac/context_test.go b/src/common/security/local/context_test.go similarity index 99% rename from src/common/security/rbac/context_test.go rename to src/common/security/local/context_test.go index f23958c58..95fe7c7aa 100644 --- a/src/common/security/rbac/context_test.go +++ b/src/common/security/local/context_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package rbac +package local import ( "fmt" diff --git a/src/ui/api/base.go b/src/ui/api/base.go index 6359de799..c940c8714 100644 --- a/src/ui/api/base.go +++ b/src/ui/api/base.go @@ -39,14 +39,14 @@ type BaseController struct { func (b *BaseController) Prepare() { ctx, err := filter.GetSecurityContext(b.Ctx.Request) if err != nil { - log.Error("failed to get security context") + log.Errorf("failed to get security context: %v", err) b.CustomAbort(http.StatusInternalServerError, "") } b.SecurityCtx = ctx pm, err := filter.GetProjectManager(b.Ctx.Request) if err != nil { - log.Error("failed to get project manager") + log.Errorf("failed to get project manager: %v", err) b.CustomAbort(http.StatusInternalServerError, "") } b.ProjectMgr = pm diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 56bb3318f..3b580d9cc 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -87,6 +87,7 @@ func init() { beego.BConfig.WebConfig.Session.SessionOn = true beego.TestBeegoInit(apppath) + filter.Init() beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) beego.Router("/api/search/", &SearchAPI{}) diff --git a/src/ui/config/config.go b/src/ui/config/config.go index b8ebce6c7..2bc7c5ae8 100644 --- a/src/ui/config/config.go +++ b/src/ui/config/config.go @@ -28,6 +28,7 @@ import ( "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/projectmanager" "github.com/vmware/harbor/src/ui/projectmanager/db" + "github.com/vmware/harbor/src/ui/projectmanager/pms" ) const ( @@ -96,10 +97,16 @@ func initSecretStore() { func initProjectManager() { if !WithAdmiral() { + // standalone log.Info("initializing the project manager based on database...") 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 diff --git a/src/ui/filter/security.go b/src/ui/filter/security.go index 4735f4c33..900acad54 100644 --- a/src/ui/filter/security.go +++ b/src/ui/filter/security.go @@ -22,8 +22,11 @@ import ( beegoctx "github.com/astaxie/beego/context" "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/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/utils/log" "github.com/vmware/harbor/src/ui/auth" @@ -35,12 +38,33 @@ import ( type key string const ( - // HarborSecurityContext is the name of security context passed to handlers - HarborSecurityContext key = "harbor_security_context" - // HarborProjectManager is the name of project manager passed to handlers - HarborProjectManager key = "harbor_project_manager" + securCtxKey key = "harbor_security_context" + pmKey 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 // and a project manager with it which can be used to do some authN & authZ func SecurityFilter(ctx *beegoctx.Context) { @@ -54,93 +78,175 @@ func SecurityFilter(ctx *beegoctx.Context) { } if !strings.HasPrefix(req.URL.RequestURI(), "/api/") && - !strings.HasPrefix(req.URL.RequestURI(), "/service/") { + !strings.HasPrefix(req.URL.RequestURI(), "/service/token") { return } - // fill ctx with security context and project manager - fillContext(ctx) + // add security context and project manager to request context + for _, modifier := range reqCtxModifiers { + if modifier.Modify(ctx) { + break + } + } } -func fillContext(ctx *beegoctx.Context) { - // secret +// ReqCtxModifier modifies the context of request +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") - if len(scrt) != 0 { - ct := context.WithValue(ctx.Request.Context(), - 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 + if len(scrt) == 0 { + return false } - var user *models.User - var err error + log.Debug("got secret from request") - // basic auth - username, password, ok := ctx.Request.BasicAuth() - if ok { - // TODO the return data contains other params when integrated - // with vic - user, err = auth.Login(models.AuthModel{ - Principal: username, - Password: password, - }) - if err != nil { - log.Errorf("failed to authenticate %s: %v", username, err) - } - if user != nil { - log.Info("got user information via 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 } - // session - if user == nil { - username := ctx.Input.Session("username") - isSysAdmin := ctx.Input.Session("isSysAdmin") - if username != nil { - user = &models.User{ - Username: username.(string), - } + log.Debug("creating a secret security context...") + securCtx := secret.NewSecurityContext(scrt, s.store) + setSecurCtxAndPM(ctx.Request, securCtx, pm) - if isSysAdmin != nil && isSysAdmin.(bool) { - user.HasAdminRole = 1 - } - log.Info("got user information from session") - } - - // TODO maybe need to get token from session - } - - if user == nil { - log.Info("user information is nil") - } - - pm := getProjectManager(ctx) - ct := context.WithValue(ctx.Request.Context(), HarborProjectManager, pm) - - log.Info("creating a rbac security context...") - ct = context.WithValue(ct, HarborSecurityContext, - rbac.NewSecurityContext(user, pm)) - ctx.Request = ctx.Request.WithContext(ct) - - return + return true } -func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager { - if !config.WithAdmiral() { - log.Info("filling a project manager based on database...") - return config.GlobalProjectMgr +type basicAuthReqCtxModifier struct{} + +func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { + username, password, ok := ctx.Request.BasicAuth() + if !ok { + return false } - log.Info("filling a project manager based on PMS...") - // TODO pass the token to the function - return pms.NewProjectManager(config.AdmiralEndpoint(), "") + user, err := auth.Login(models.AuthModel{ + Principal: username, + Password: password, + }) + if err != nil { + log.Errorf("failed to authenticate %s: %v", username, err) + return false + } + if user == nil { + return false + } + + var securCtx security.Context + 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") + if username == nil { + return false + } + + log.Debug("got user information from session") + user := &models.User{ + Username: username.(string), + } + isSysAdmin := ctx.Input.Session("isSysAdmin") + if isSysAdmin != nil && isSysAdmin.(bool) { + user.HasAdminRole = 1 + } + + 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 tokenReqCtxModifier struct{} + +func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool { + token := ctx.Request.Header.Get(authcontext.AuthTokenHeader) + if len(token) == 0 { + return false + } + + log.Debug("got token from request") + + authContext, err := authcontext.GetByToken(token) + if err != nil { + log.Errorf("failed to get auth context: %v", err) + return false + } + + log.Debug("creating PMS project manager...") + pm := pms.NewProjectManager(config.AdmiralEndpoint(), token) + log.Debug("creating admiral security context...") + securCtx := admiral.NewSecurityContext(authContext, pm) + setSecurCtxAndPM(ctx.Request, securCtx, pm) + + return true +} + +// use this one as the last modifier in the modifier list for unauthorized request +type unauthorizedReqCtxModifier struct{} + +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 +} + +func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) { + addToReqContext(req, securCtxKey, ctx) + 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 @@ -149,7 +255,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) { return nil, fmt.Errorf("request is nil") } - ctx := req.Context().Value(HarborSecurityContext) + ctx := req.Context().Value(securCtxKey) if ctx == 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") } - pm := req.Context().Value(HarborProjectManager) + pm := req.Context().Value(pmKey) if pm == nil { return nil, fmt.Errorf("the project manager got from request is nil") } diff --git a/src/ui/filter/security_test.go b/src/ui/filter/security_test.go index cc569dee6..1ceef2212 100644 --- a/src/ui/filter/security_test.go +++ b/src/ui/filter/security_test.go @@ -32,7 +32,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/vmware/harbor/src/common/dao" "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/ui/auth/db" _ "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) } + Init() + os.Exit(m.Run()) } @@ -80,7 +82,7 @@ func TestSecurityFilter(t *testing.T) { // nil request ctx, err := newContext(nil) if err != nil { - t.Fatalf("failed to crate context: %v", err) + t.Fatalf("failed to create context: %v", err) } SecurityFilter(ctx) assert.Nil(t, securityContext(ctx)) @@ -116,8 +118,7 @@ func TestSecurityFilter(t *testing.T) { assert.NotNil(t, projectManager(ctx)) } -func TestFillContext(t *testing.T) { - // secret +func TestSecretReqCtxModifier(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/api/projects/", nil) if err != nil { @@ -133,13 +134,40 @@ func TestFillContext(t *testing.T) { 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{}, securityContext(ctx)) assert.NotNil(t, projectManager(ctx)) +} - // session - req, err = http.NewRequest(http.MethodGet, +func TestBasicAuthReqCtxModifier(t *testing.T) { + 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) if err != nil { t.Fatalf("failed to create request: %v", req) @@ -162,52 +190,47 @@ func TestFillContext(t *testing.T) { } addSessionIDToCookie(req, store.SessionID()) - ctx, err = newContext(req) + ctx, err := newContext(req) if err != nil { t.Fatalf("failed to crate context: %v", err) } - fillContext(ctx) + + modifier := &sessionReqCtxModifier{} + modified := modifier.Modify(ctx) + + assert.True(t, modified) sc := securityContext(ctx) - assert.IsType(t, &rbac.SecurityContext{}, sc) + assert.IsType(t, &local.SecurityContext{}, sc) s := sc.(security.Context) assert.Equal(t, "admin", s.GetUsername()) assert.True(t, s.IsSysAdmin()) 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") +// TODO add test case +func TestTokenReqCtxModifier(t *testing.T) { - ctx, err = newContext(req) - if err != nil { - 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 - req, err = http.NewRequest(http.MethodGet, +func TestUnauthorizedReqCtxModifier(t *testing.T) { + 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) } - ctx, err = newContext(req) + ctx, err := newContext(req) if err != nil { t.Fatalf("failed to crate context: %v", err) } - fillContext(ctx) - sc = securityContext(ctx) - assert.IsType(t, &rbac.SecurityContext{}, sc) - s = sc.(security.Context) + + modifier := &unauthorizedReqCtxModifier{} + modified := modifier.Modify(ctx) + assert.True(t, modified) + + sc := securityContext(ctx) + assert.NotNil(t, sc) + s := sc.(security.Context) assert.False(t, s.IsAuthenticated()) assert.NotNil(t, projectManager(ctx)) } @@ -241,6 +264,7 @@ func addSessionIDToCookie(req *http.Request, sessionID string) { func securityContext(ctx *beegoctx.Context) interface{} { c, err := GetSecurityContext(ctx.Request) if err != nil { + log.Printf("failed to get security context: %v \n", err) return nil } return c @@ -250,7 +274,7 @@ func projectManager(ctx *beegoctx.Context) interface{} { if ctx.Request == nil { return nil } - return ctx.Request.Context().Value(HarborProjectManager) + return ctx.Request.Context().Value(pmKey) } func TestGetSecurityContext(t *testing.T) { @@ -268,7 +292,7 @@ func TestGetSecurityContext(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - HarborSecurityContext, "test")) + securCtxKey, "test")) ctx, err = GetSecurityContext(req) assert.NotNil(t, err) @@ -276,7 +300,7 @@ func TestGetSecurityContext(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - HarborSecurityContext, rbac.NewSecurityContext(nil, nil))) + securCtxKey, local.NewSecurityContext(nil, nil))) ctx, err = GetSecurityContext(req) assert.Nil(t, err) _, ok := ctx.(security.Context) @@ -298,7 +322,7 @@ func TestGetProjectManager(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - HarborProjectManager, "test")) + pmKey, "test")) pm, err = GetProjectManager(req) assert.NotNil(t, err) @@ -306,7 +330,7 @@ func TestGetProjectManager(t *testing.T) { req, err = http.NewRequest("", "", nil) assert.Nil(t, err) req = req.WithContext(context.WithValue(req.Context(), - HarborProjectManager, &db.ProjectManager{})) + pmKey, &db.ProjectManager{})) pm, err = GetProjectManager(req) assert.Nil(t, err) _, ok := pm.(projectmanager.ProjectManager) diff --git a/src/ui/main.go b/src/ui/main.go index eae509f1c..5a3651912 100644 --- a/src/ui/main.go +++ b/src/ui/main.go @@ -98,6 +98,7 @@ func main() { log.Error(err) } + filter.Init() beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter) initRouters()