diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go index 295e4ce81..a848ceae1 100644 --- a/src/common/security/admiral/context.go +++ b/src/common/security/admiral/context.go @@ -16,6 +16,7 @@ package admiral import ( "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/security/authcontext" "github.com/vmware/harbor/src/common/utils/log" "github.com/vmware/harbor/src/ui/projectmanager" @@ -41,7 +42,7 @@ func (s *SecurityContext) IsAuthenticated() bool { if s.ctx == nil { return false } - return len(s.ctx.GetUsername()) > 0 + return len(s.ctx.PrincipalID) > 0 } // GetUsername returns the username of the authenticated user @@ -50,7 +51,7 @@ func (s *SecurityContext) GetUsername() string { if !s.IsAuthenticated() { return "" } - return s.ctx.GetUsername() + return s.ctx.PrincipalID } // IsSysAdmin returns whether the authenticated user is system admin @@ -59,12 +60,12 @@ 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", @@ -85,27 +86,9 @@ func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { return true } - if name, ok := projectIDOrName.(string); ok { - return s.ctx.HasReadPerm(name) - } + roles := s.GetProjectRoles(projectIDOrName) - 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 + return len(roles) > 0 } // HasWritePerm returns whether the user has write permission to the project @@ -119,17 +102,7 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { 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 - } - + roles := s.GetProjectRoles(projectIDOrName) for _, role := range roles { switch role { case common.RoleProjectAdmin, @@ -152,17 +125,7 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { 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 - } - + roles := s.GetProjectRoles(projectIDOrName) for _, role := range roles { switch role { case common.RoleProjectAdmin: @@ -172,3 +135,17 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return false } + +// GetMyProjects ... +func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) { + return s.ctx.GetMyProjects(), nil +} + +// GetProjectRoles ... +func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int { + if !s.IsAuthenticated() || projectIDOrName == nil { + return []int{} + } + + return s.ctx.GetProjectRoles(projectIDOrName) +} diff --git a/src/common/security/authcontext/authcontext.go b/src/common/security/authcontext/authcontext.go index 370ae73fe..927be96d0 100644 --- a/src/common/security/authcontext/authcontext.go +++ b/src/common/security/authcontext/authcontext.go @@ -21,9 +21,12 @@ import ( "io/ioutil" "net/http" "strings" + + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" + "github.com/vmware/harbor/src/common/utils/log" ) -// TODO update the value of role when admiral API is ready const ( // AuthTokenHeader is the key of auth token header AuthTokenHeader = "x-xenon-auth-token" @@ -48,70 +51,83 @@ type AuthContext struct { Projects []*project `json:"projects"` } -// GetUsername ... -func (a *AuthContext) GetUsername() string { - return a.PrincipalID -} - // IsSysAdmin ... func (a *AuthContext) IsSysAdmin() bool { - isSysAdmin := false for _, role := range a.Roles { if role == sysAdminRole { - isSysAdmin = true - break - } - } - return isSysAdmin -} - -// HasReadPerm ... -func (a *AuthContext) HasReadPerm(projectName string) bool { - roles := a.getRoles(projectName) - return len(roles) > 0 -} - -// HasWritePerm ... -func (a *AuthContext) HasWritePerm(projectName string) bool { - roles := a.getRoles(projectName) - for _, role := range roles { - if role == projectAdminRole || role == developerRole { return true } } return false } -// HasAllPerm ... -func (a *AuthContext) HasAllPerm(projectName string) bool { - roles := a.getRoles(projectName) - for _, role := range roles { - if role == projectAdminRole { - return true - } - } - return false -} +// GetProjectRoles ... +func (a *AuthContext) GetProjectRoles(projectIDOrName interface{}) []int { + var isID bool + var id int64 + var name string -func (a *AuthContext) getRoles(projectName string) []string { + id, isID = projectIDOrName.(int64) + if !isID { + name, _ = projectIDOrName.(string) + } + + roles := []string{} for _, project := range a.Projects { - if project.Name == projectName { - return project.Roles + p := convertProject(project) + if isID { + if p.ProjectID == id { + roles = append(roles, project.Roles...) + break + } + } else { + if p.Name == name { + roles = append(roles, project.Roles...) + break + } } } - return []string{} + return convertRoles(roles) } // GetMyProjects returns all projects which the user is a member of -func (a *AuthContext) GetMyProjects() []string { - projects := []string{} +func (a *AuthContext) GetMyProjects() []*models.Project { + projects := []*models.Project{} for _, project := range a.Projects { - projects = append(projects, project.Name) + projects = append(projects, convertProject(project)) } return projects } +// TODO populate harbor ID to the project +// convert project returned by Admiral to project used in Harbor +func convertProject(p *project) *models.Project { + project := &models.Project{ + Name: p.Name, + } + return project +} + +// convert roles defined by Admiral to roles used in Harbor +func convertRoles(roles []string) []int { + list := []int{} + for _, role := range roles { + switch role { + case projectAdminRole: + list = append(list, common.RoleProjectAdmin) + case developerRole: + list = append(list, common.RoleDeveloper) + case guestRole: + list = append(list, common.RoleGuest) + default: + log.Warningf("unknow role: %s", role) + } + } + + return list +} + // GetAuthCtx returns the auth context of the current user func GetAuthCtx(client *http.Client, url, token string) (*AuthContext, error) { return get(client, url, token) diff --git a/src/common/security/authcontext/authcontext_test.go b/src/common/security/authcontext/authcontext_test.go index 609062c6b..a18169c5c 100644 --- a/src/common/security/authcontext/authcontext_test.go +++ b/src/common/security/authcontext/authcontext_test.go @@ -14,4 +14,61 @@ package authcontext -// TODO add test cases +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsSysAdmin(t *testing.T) { + // nil roles + ctx := &AuthContext{} + assert.False(t, ctx.IsSysAdmin()) + + // has no admin role + ctx = &AuthContext{ + Roles: []string{projectAdminRole, developerRole, guestRole}, + } + assert.False(t, ctx.IsSysAdmin()) + + // has admin role + ctx = &AuthContext{ + Roles: []string{sysAdminRole}, + } + assert.True(t, ctx.IsSysAdmin()) +} + +func TestGetProjectRoles(t *testing.T) { + ctx := &AuthContext{ + Projects: []*project{ + &project{ + Name: "project", + Roles: []string{projectAdminRole, developerRole, guestRole}, + }, + }, + } + + // test with name + roles := ctx.GetProjectRoles("project") + assert.Equal(t, 3, len(roles)) + + // TODO add test case with ID +} + +func TestGetMyProjects(t *testing.T) { + ctx := &AuthContext{ + Projects: []*project{ + &project{ + Name: "project1", + Roles: []string{projectAdminRole}, + }, + &project{ + Name: "project2", + Roles: []string{developerRole}, + }, + }, + } + + projects := ctx.GetMyProjects() + assert.Equal(t, 2, len(projects)) +} diff --git a/src/common/security/context.go b/src/common/security/context.go index e491285a1..312db2438 100644 --- a/src/common/security/context.go +++ b/src/common/security/context.go @@ -14,6 +14,10 @@ package security +import ( + "github.com/vmware/harbor/src/common/models" +) + // Context abstracts the operations related with authN and authZ type Context interface { // IsAuthenticated returns whether the context has been authenticated or not @@ -28,4 +32,6 @@ type Context interface { HasWritePerm(projectIDOrName interface{}) bool // HasAllPerm returns whether the user has all permissions to the project HasAllPerm(projectIDOrName interface{}) bool + GetMyProjects() ([]*models.Project, error) + GetProjectRoles(projectIDOrName interface{}) []int } diff --git a/src/common/security/local/context.go b/src/common/security/local/context.go index 440bc3bcc..fc923d4ee 100644 --- a/src/common/security/local/context.go +++ b/src/common/security/local/context.go @@ -16,6 +16,7 @@ package local import ( "github.com/vmware/harbor/src/common" + "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/projectmanager" @@ -60,18 +61,6 @@ func (s *SecurityContext) IsSysAdmin() bool { // HasReadPerm returns whether the user has read permission to the project func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { - // not exist - exist, err := s.pm.Exist(projectIDOrName) - if err != nil { - log.Errorf("failed to check the existence of project %v: %v", - projectIDOrName, err) - return false - } - - if !exist { - return false - } - // public project public, err := s.pm.IsPublic(projectIDOrName) if err != nil { @@ -93,23 +82,9 @@ func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool { return true } - 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 - } + roles := s.GetProjectRoles(projectIDOrName) - for _, role := range roles { - switch role { - case common.RoleProjectAdmin, - common.RoleDeveloper, - common.RoleGuest: - return true - } - } - - return false + return len(roles) > 0 } // HasWritePerm returns whether the user has write permission to the project @@ -118,30 +93,12 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { return false } - // project does not exist - exist, err := s.pm.Exist(projectIDOrName) - if err != nil { - log.Errorf("failed to check the existence of project %v: %v", - projectIDOrName, err) - return false - } - - if !exist { - return false - } - // system admin if s.IsSysAdmin() { return true } - 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 - } - + roles := s.GetProjectRoles(projectIDOrName) for _, role := range roles { switch role { case common.RoleProjectAdmin, @@ -159,30 +116,12 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return false } - // project does not exist - exist, err := s.pm.Exist(projectIDOrName) - if err != nil { - log.Errorf("failed to check the existence of project %v: %v", - projectIDOrName, err) - return false - } - - if !exist { - return false - } - // system admin if s.IsSysAdmin() { return true } - 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 - } - + roles := s.GetProjectRoles(projectIDOrName) for _, role := range roles { switch role { case common.RoleProjectAdmin: @@ -192,3 +131,61 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return false } + +// GetMyProjects ... +func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) { + return dao.GetProjects(&models.ProjectQueryParam{ + Member: &models.MemberQuery{ + Name: s.GetUsername(), + }, + }) +} + +// GetProjectRoles ... +func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int { + if !s.IsAuthenticated() || projectIDOrName == nil { + return []int{} + } + + roles := []int{} + user, err := dao.GetUser(models.User{ + Username: s.GetUsername(), + }) + if err != nil { + log.Errorf("failed to get user %s: %v", s.GetUsername(), err) + return roles + } + if user == nil { + log.Debugf("user %s not found", s.GetUsername()) + return roles + } + + project, err := s.pm.Get(projectIDOrName) + if err != nil { + log.Errorf("failed to get project %v: %v", projectIDOrName, err) + return roles + } + if project == nil { + log.Errorf("project %v not found", projectIDOrName) + return roles + } + + roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID) + if err != nil { + log.Errorf("failed to get roles of user %d to project %d: %v", user.UserID, project.ProjectID, err) + return roles + } + + for _, role := range roleList { + switch role.RoleCode { + case "MDRWS": + roles = append(roles, common.RoleProjectAdmin) + case "RWS": + roles = append(roles, common.RoleDeveloper) + case "RS": + roles = append(roles, common.RoleGuest) + } + } + + return roles +} diff --git a/src/common/security/local/context_test.go b/src/common/security/local/context_test.go index 95fe7c7aa..0bee8da22 100644 --- a/src/common/security/local/context_test.go +++ b/src/common/security/local/context_test.go @@ -15,109 +15,132 @@ package local import ( - "fmt" + "os" + "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/vmware/harbor/src/common" + "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/projectmanager/db" ) var ( - public = &models.Project{ - Name: "public_project", - Public: 1, - } - private = &models.Project{ - Name: "private_project", - Public: 0, + Name: "private_project", + OwnerID: 1, } - read = &models.Project{ - Name: "has_read_perm_project", + projectAdminUser = &models.User{ + Username: "projectAdminUser", + Email: "projectAdminUser@vmware.com", + } + developerUser = &models.User{ + Username: "developerUser", + Email: "developerUser@vmware.com", + } + guestUser = &models.User{ + Username: "guestUser", + Email: "guestUser@vmware.com", } - write = &models.Project{ - Name: "has_write_perm_project", - } - - all = &models.Project{ - Name: "has_all_perm_project", - } + pm = &db.ProjectManager{} ) -type fakePM struct { - projects []*models.Project - roles map[string][]int -} - -func (f *fakePM) IsPublic(projectIDOrName interface{}) (bool, error) { - for _, project := range f.projects { - if project.Name == projectIDOrName.(string) { - return project.Public == 1, nil - } +func TestMain(m *testing.M) { + dbHost := os.Getenv("MYSQL_HOST") + if len(dbHost) == 0 { + log.Fatalf("environment variable MYSQL_HOST is not set") } - return false, nil -} -func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) ([]int, error) { - return f.roles[projectIDOrName.(string)], nil -} -func (f *fakePM) Get(projectIDOrName interface{}) (*models.Project, error) { - for _, project := range f.projects { - if project.Name == projectIDOrName.(string) { - return project, nil - } + dbPortStr := os.Getenv("MYSQL_PORT") + if len(dbPortStr) == 0 { + log.Fatalf("environment variable MYSQL_PORT is not set") } - return nil, nil -} -func (f *fakePM) Exist(projectIDOrName interface{}) (bool, error) { - for _, project := range f.projects { - if project.Name == projectIDOrName.(string) { - return true, nil - } + dbPort, err := strconv.Atoi(dbPortStr) + if err != nil { + log.Fatalf("invalid MYSQL_PORT: %v", err) + } + dbUser := os.Getenv("MYSQL_USR") + if len(dbUser) == 0 { + log.Fatalf("environment variable MYSQL_USR is not set") } - return false, nil -} -// nil implement -func (f *fakePM) GetPublic() ([]*models.Project, error) { - return []*models.Project{}, nil -} + dbPassword := os.Getenv("MYSQL_PWD") + dbDatabase := os.Getenv("MYSQL_DATABASE") + if len(dbDatabase) == 0 { + log.Fatalf("environment variable MYSQL_DATABASE is not set") + } -// nil implement -func (f *fakePM) GetByMember(username string) ([]*models.Project, error) { - return []*models.Project{}, nil -} + database := &models.Database{ + Type: "mysql", + MySQL: &models.MySQL{ + Host: dbHost, + Port: dbPort, + Username: dbUser, + Password: dbPassword, + Database: dbDatabase, + }, + } -// nil implement -func (f *fakePM) Create(*models.Project) (int64, error) { - return 0, fmt.Errorf("not support") -} + log.Infof("MYSQL_HOST: %s, MYSQL_USR: %s, MYSQL_PORT: %d, MYSQL_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword) -// nil implement -func (f *fakePM) Delete(projectIDOrName interface{}) error { - return fmt.Errorf("not support") -} + if err := dao.InitDatabase(database); err != nil { + log.Fatalf("failed to initialize database: %v", err) + } -// nil implement -func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) error { - return fmt.Errorf("not support") -} + // regiser users + id, err := dao.Register(*projectAdminUser) + if err != nil { + log.Fatalf("failed to register user: %v", err) + } + projectAdminUser.UserID = int(id) + defer dao.DeleteUser(int(id)) -// nil implement -func (f *fakePM) GetAll(*models.ProjectQueryParam, ...*models.BaseProjectCollection) ([]*models.Project, error) { - return []*models.Project{}, nil -} + id, err = dao.Register(*developerUser) + if err != nil { + log.Fatalf("failed to register user: %v", err) + } + developerUser.UserID = int(id) + defer dao.DeleteUser(int(id)) -// nil implement -func (f *fakePM) GetHasReadPerm(username ...string) ([]*models.Project, error) { - return []*models.Project{}, nil -} + id, err = dao.Register(*guestUser) + if err != nil { + log.Fatalf("failed to register user: %v", err) + } + guestUser.UserID = int(id) + defer dao.DeleteUser(int(id)) -// nil implement -func (f *fakePM) GetTotal(*models.ProjectQueryParam, ...*models.BaseProjectCollection) (int64, error) { - return 0, nil + // add project + id, err = dao.AddProject(*private) + if err != nil { + log.Fatalf("failed to add project: %v", err) + } + private.ProjectID = id + defer dao.DeleteProject(id) + + // add project members + err = dao.AddProjectMember(private.ProjectID, projectAdminUser.UserID, common.RoleProjectAdmin) + if err != nil { + log.Fatalf("failed to add member: %v", err) + } + defer dao.DeleteProjectMember(private.ProjectID, projectAdminUser.UserID) + + err = dao.AddProjectMember(private.ProjectID, developerUser.UserID, common.RoleDeveloper) + if err != nil { + log.Fatalf("failed to add member: %v", err) + } + defer dao.DeleteProjectMember(private.ProjectID, developerUser.UserID) + + err = dao.AddProjectMember(private.ProjectID, guestUser.UserID, common.RoleGuest) + if err != nil { + log.Fatalf("failed to add member: %v", err) + } + defer dao.DeleteProjectMember(private.ProjectID, guestUser.UserID) + + os.Exit(m.Run()) } func TestIsAuthenticated(t *testing.T) { @@ -164,147 +187,104 @@ func TestIsSysAdmin(t *testing.T) { } func TestHasReadPerm(t *testing.T) { - pm := &fakePM{ - projects: []*models.Project{public, private, read}, - roles: map[string][]int{ - "has_read_perm_project": []int{common.RoleGuest}, - }, - } - - // non-exist project - 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")) + ctx := NewSecurityContext(nil, pm) + assert.True(t, ctx.HasReadPerm("library")) // private project, unauthenticated ctx = NewSecurityContext(nil, pm) - assert.False(t, ctx.HasReadPerm("private_project")) + assert.False(t, ctx.HasReadPerm(private.Name)) // private project, authenticated, has no perm ctx = NewSecurityContext(&models.User{ Username: "test", }, pm) - assert.False(t, ctx.HasReadPerm("private_project")) + assert.False(t, ctx.HasReadPerm(private.Name)) // private project, authenticated, has read perm - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.True(t, ctx.HasReadPerm("has_read_perm_project")) + ctx = NewSecurityContext(guestUser, pm) + assert.True(t, ctx.HasReadPerm(private.Name)) // private project, authenticated, system admin ctx = NewSecurityContext(&models.User{ - Username: "test", + Username: "admin", HasAdminRole: 1, }, pm) - 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")) + assert.True(t, ctx.HasReadPerm(private.Name)) } func TestHasWritePerm(t *testing.T) { - pm := &fakePM{ - projects: []*models.Project{read, write, private}, - roles: map[string][]int{ - "has_read_perm_project": []int{common.RoleGuest}, - "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, - }, - } - // unauthenticated ctx := NewSecurityContext(nil, pm) - 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")) + assert.False(t, ctx.HasWritePerm(private.Name)) // authenticated, has read perm - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.False(t, ctx.HasWritePerm("has_read_perm_project")) // authenticated, has read perm + ctx = NewSecurityContext(guestUser, pm) + assert.False(t, ctx.HasWritePerm(private.Name)) // authenticated, has write perm - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.True(t, ctx.HasWritePerm("has_write_perm_project")) + ctx = NewSecurityContext(developerUser, pm) + assert.True(t, ctx.HasWritePerm(private.Name)) // authenticated, system admin ctx = NewSecurityContext(&models.User{ - Username: "test", + Username: "admin", HasAdminRole: 1, }, pm) - 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")) + assert.True(t, ctx.HasReadPerm(private.Name)) } func TestHasAllPerm(t *testing.T) { - pm := &fakePM{ - projects: []*models.Project{read, write, all, private}, - roles: map[string][]int{ - "has_read_perm_project": []int{common.RoleGuest}, - "has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper}, - "has_all_perm_project": []int{common.RoleGuest, common.RoleDeveloper, common.RoleProjectAdmin}, - }, - } - // unauthenticated ctx := NewSecurityContext(nil, pm) - 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 - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.False(t, ctx.HasAllPerm("has_read_perm_project")) - - // authenticated, has write perm - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.False(t, ctx.HasAllPerm("has_write_perm_project")) + assert.False(t, ctx.HasAllPerm(private.Name)) // authenticated, has all perms - ctx = NewSecurityContext(&models.User{ - Username: "test", - }, pm) - assert.True(t, ctx.HasAllPerm("has_all_perm_project")) + ctx = NewSecurityContext(projectAdminUser, pm) + assert.True(t, ctx.HasAllPerm(private.Name)) // authenticated, system admin ctx = NewSecurityContext(&models.User{ - Username: "test", + Username: "admin", HasAdminRole: 1, }, pm) - 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")) + assert.True(t, ctx.HasAllPerm(private.Name)) +} + +func TestGetMyProjects(t *testing.T) { + ctx := NewSecurityContext(guestUser, pm) + projects, err := ctx.GetMyProjects() + require.Nil(t, err) + assert.Equal(t, 1, len(projects)) + assert.Equal(t, private.ProjectID, projects[0].ProjectID) +} + +func TestGetProjectRoles(t *testing.T) { + // unauthenticated + ctx := NewSecurityContext(nil, pm) + roles := ctx.GetProjectRoles(private.Name) + assert.Equal(t, 0, len(roles)) + + // authenticated, project name of ID is nil + ctx = NewSecurityContext(guestUser, pm) + roles = ctx.GetProjectRoles(nil) + assert.Equal(t, 0, len(roles)) + + // authenticated, has read perm + ctx = NewSecurityContext(guestUser, pm) + roles = ctx.GetProjectRoles(private.Name) + assert.Equal(t, 1, len(roles)) + assert.Equal(t, common.RoleGuest, roles[0]) + + // authenticated, has write perm + ctx = NewSecurityContext(developerUser, pm) + roles = ctx.GetProjectRoles(private.Name) + assert.Equal(t, 1, len(roles)) + assert.Equal(t, common.RoleDeveloper, roles[0]) + + // authenticated, has all perms + ctx = NewSecurityContext(projectAdminUser, pm) + roles = ctx.GetProjectRoles(private.Name) + assert.Equal(t, 1, len(roles)) + assert.Equal(t, common.RoleProjectAdmin, roles[0]) } diff --git a/src/common/security/secret/context.go b/src/common/security/secret/context.go index f8e8a30c4..328a134ba 100644 --- a/src/common/security/secret/context.go +++ b/src/common/security/secret/context.go @@ -15,6 +15,10 @@ package secret import ( + "fmt" + + "github.com/vmware/harbor/src/common" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/secret" "github.com/vmware/harbor/src/common/utils/log" ) @@ -79,3 +83,17 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool { func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return false } + +// GetMyProjects ... +func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) { + return nil, fmt.Errorf("GetMyProjects is unsupported") +} + +// GetProjectRoles return guest role if has read permission, otherwise return nil +func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int { + roles := []int{} + if s.HasReadPerm(projectIDOrName) { + roles = append(roles, common.RoleGuest) + } + return roles +} diff --git a/src/common/security/secret/context_test.go b/src/common/security/secret/context_test.go index 45db03182..8d221c14b 100644 --- a/src/common/security/secret/context_test.go +++ b/src/common/security/secret/context_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/secret" ) @@ -132,3 +133,34 @@ func TestHasAllPerm(t *testing.T) { hasAllPerm = context.HasAllPerm(1) assert.False(t, hasAllPerm) } + +func TestGetMyProjects(t *testing.T) { + context := NewSecurityContext("secret", + secret.NewStore(map[string]string{ + "secret": "username", + })) + + _, err := context.GetMyProjects() + assert.NotNil(t, err) +} + +func TestGetProjectRoles(t *testing.T) { + //invalid secret + context := NewSecurityContext("invalid_secret", + secret.NewStore(map[string]string{ + "jobservice_secret": secret.JobserviceUser, + })) + + roles := context.GetProjectRoles("any_project") + assert.Equal(t, 0, len(roles)) + + // valid secret + context = NewSecurityContext("jobservice_secret", + secret.NewStore(map[string]string{ + "jobservice_secret": secret.JobserviceUser, + })) + + roles = context.GetProjectRoles("any_project") + assert.Equal(t, 1, len(roles)) + assert.Equal(t, common.RoleGuest, roles[0]) +} diff --git a/src/ui/api/log.go b/src/ui/api/log.go index d9fdb6640..5e3833f0b 100644 --- a/src/ui/api/log.go +++ b/src/ui/api/log.go @@ -75,7 +75,7 @@ func (l *LogAPI) Get() { } if !l.isSysAdmin { - projects, err := l.ProjectMgr.GetByMember(l.username) + projects, err := l.SecurityCtx.GetMyProjects() if err != nil { l.HandleInternalServerError(fmt.Sprintf( "failed to get projects of user %s: %v", l.username, err)) diff --git a/src/ui/api/project.go b/src/ui/api/project.go index 1743b1097..55c4298f5 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -311,13 +311,7 @@ func (p *ProjectAPI) List() { for _, project := range projects { if p.SecurityCtx.IsAuthenticated() { - roles, err := p.ProjectMgr.GetRoles(p.SecurityCtx.GetUsername(), project.ProjectID) - if err != nil { - p.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v", - p.SecurityCtx.GetUsername(), project.ProjectID, err)) - return - } - + roles := p.SecurityCtx.GetProjectRoles(project.ProjectID) if len(roles) != 0 { project.Role = roles[0] } diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 9f2283348..2da5f9ec1 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -608,7 +608,7 @@ func (ra *RepositoryAPI) GetTopRepos() { return } if ra.SecurityCtx.IsAuthenticated() { - list, err := ra.ProjectMgr.GetByMember(ra.SecurityCtx.GetUsername()) + list, err := ra.SecurityCtx.GetMyProjects() if err != nil { ra.HandleInternalServerError(fmt.Sprintf("failed to get projects which the user %s is a member of: %v", ra.SecurityCtx.GetUsername(), err)) diff --git a/src/ui/api/search.go b/src/ui/api/search.go index 24b60bd7a..5f90c0823 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -43,23 +43,43 @@ type searchResult struct { func (s *SearchAPI) Get() { keyword := s.GetString("q") isAuthenticated := s.SecurityCtx.IsAuthenticated() - username := s.SecurityCtx.GetUsername() isSysAdmin := s.SecurityCtx.IsSysAdmin() var projects []*models.Project var err error - if !isAuthenticated { - projects, err = s.ProjectMgr.GetPublic() - } else if isSysAdmin { + if isSysAdmin { projects, err = s.ProjectMgr.GetAll(nil) + if err != nil { + s.HandleInternalServerError(fmt.Sprintf( + "failed to get projects: %v", err)) + return + } } else { - projects, err = s.ProjectMgr.GetHasReadPerm(username) - } - if err != nil { - s.HandleInternalServerError(fmt.Sprintf( - "failed to get projects: %v", err)) - return + projects, err = s.ProjectMgr.GetPublic() + if err != nil { + s.HandleInternalServerError(fmt.Sprintf( + "failed to get projects: %v", err)) + return + } + if isAuthenticated { + mys, err := s.SecurityCtx.GetMyProjects() + if err != nil { + s.HandleInternalServerError(fmt.Sprintf( + "failed to get projects: %v", err)) + return + } + exist := map[int64]bool{} + for _, p := range projects { + exist[p.ProjectID] = true + } + + for _, p := range mys { + if !exist[p.ProjectID] { + projects = append(projects, p) + } + } + } } projectSorter := &models.ProjectSorter{Projects: projects} @@ -71,13 +91,7 @@ func (s *SearchAPI) Get() { } if isAuthenticated { - roles, err := s.ProjectMgr.GetRoles(username, p.ProjectID) - if err != nil { - s.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v", - username, p.ProjectID, err)) - return - } - + roles := s.SecurityCtx.GetProjectRoles(p.ProjectID) if len(roles) != 0 { p.Role = roles[0] } diff --git a/src/ui/projectmanager/db/pm.go b/src/ui/projectmanager/db/pm.go index fdcc7561d..4f659d6a1 100644 --- a/src/ui/projectmanager/db/pm.go +++ b/src/ui/projectmanager/db/pm.go @@ -18,7 +18,6 @@ import ( "fmt" "time" - "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" ) @@ -62,48 +61,6 @@ func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) { return project.Public == 1, nil } -// GetRoles return a role list which contains the user's roles to the project -func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) ([]int, error) { - roles := []int{} - - user, err := dao.GetUser(models.User{ - Username: username, - }) - if err != nil { - return nil, fmt.Errorf("failed to get user %s: %v", - username, err) - } - if user == nil { - return roles, nil - } - - project, err := p.Get(projectIDOrName) - if err != nil { - return nil, err - } - if project == nil { - return roles, nil - } - - roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID) - if err != nil { - return nil, err - } - - for _, role := range roleList { - switch role.RoleCode { - case "MDRWS": - roles = append(roles, common.RoleProjectAdmin) - case "RWS": - roles = append(roles, common.RoleDeveloper) - case "RS": - roles = append(roles, common.RoleGuest) - } - } - - return roles, nil -} - // GetPublic returns all public projects func (p *ProjectManager) GetPublic() ([]*models.Project, error) { t := true @@ -112,20 +69,6 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) { }) } -// GetByMember returns all projects which the user is a member of -func (p *ProjectManager) GetByMember(username string) ( - []*models.Project, error) { - if len(username) == 0 { - return []*models.Project{}, nil - } - - return p.GetAll(&models.ProjectQueryParam{ - Member: &models.MemberQuery{ - Name: username, - }, - }) -} - // Create ... func (p *ProjectManager) Create(project *models.Project) (int64, error) { if project == nil { @@ -204,13 +147,3 @@ func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*mode int64, error) { return dao.GetTotalOfProjects(query, base...) } - -// GetHasReadPerm returns projects which are public or the user is a member of -func (p *ProjectManager) GetHasReadPerm(username ...string) ( - []*models.Project, error) { - if len(username) == 0 || len(username[0]) == 0 { - return p.GetPublic() - } - - return dao.GetHasReadPermProjects(username[0]) -} diff --git a/src/ui/projectmanager/db/pm_test.go b/src/ui/projectmanager/db/pm_test.go index b705132c9..227ed6063 100644 --- a/src/ui/projectmanager/db/pm_test.go +++ b/src/ui/projectmanager/db/pm_test.go @@ -20,7 +20,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/dao" "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/log" @@ -121,25 +120,6 @@ func TestIsPublic(t *testing.T) { assert.False(t, public) } -func TestGetRoles(t *testing.T) { - pm := &ProjectManager{} - - // non exist user - roles, err := pm.GetRoles("non_exist_user", int64(1)) - assert.Nil(t, err) - assert.Equal(t, []int{}, roles) - - // exist project - roles, err = pm.GetRoles("admin", "library") - assert.Nil(t, err) - assert.Equal(t, []int{common.RoleProjectAdmin}, roles) - - // non-exist project - roles, err = pm.GetRoles("admin", "non_exist_project") - assert.Nil(t, err) - assert.Equal(t, []int{}, roles) -} - func TestGetPublic(t *testing.T) { pm := &ProjectManager{} projects, err := pm.GetPublic() @@ -151,19 +131,6 @@ func TestGetPublic(t *testing.T) { } } -func TestGetByMember(t *testing.T) { - pm := &ProjectManager{} - // empty username - projects, err := pm.GetByMember("") - assert.Nil(t, err) - assert.Equal(t, 0, len(projects)) - - //non-empty username - projects, err = pm.GetByMember("admin") - assert.Nil(t, err) - assert.NotEqual(t, 0, len(projects)) -} - func TestCreateAndDelete(t *testing.T) { pm := &ProjectManager{} @@ -304,58 +271,3 @@ func TestGetAll(t *testing.T) { } assert.True(t, exist) } - -func TestGetHasReadPerm(t *testing.T) { - pm := &ProjectManager{} - - // do not pass username - projects, err := pm.GetHasReadPerm() - assert.Nil(t, err) - assert.NotEqual(t, 0, len(projects)) - exist := false - for _, project := range projects { - if project.ProjectID == 1 { - exist = true - break - } - } - assert.True(t, exist) - - // username is nil - projects, err = pm.GetHasReadPerm("") - assert.Nil(t, err) - assert.NotEqual(t, 0, len(projects)) - exist = false - for _, project := range projects { - if project.ProjectID == 1 { - exist = true - break - } - } - assert.True(t, exist) - - // valid username - id, err := pm.Create(&models.Project{ - Name: "get_has_read_perm_test", - OwnerID: 1, - Public: 0, - }) - assert.Nil(t, err) - defer pm.Delete(id) - - projects, err = pm.GetHasReadPerm("admin") - assert.Nil(t, err) - assert.NotEqual(t, 0, len(projects)) - exist1 := false - exist2 := false - for _, project := range projects { - if project.ProjectID == 1 { - exist1 = true - } - if project.ProjectID == id { - exist2 = true - } - } - assert.True(t, exist1) - assert.True(t, exist2) -} diff --git a/src/ui/projectmanager/pm.go b/src/ui/projectmanager/pm.go index 676dfe9c3..4a66966e1 100644 --- a/src/ui/projectmanager/pm.go +++ b/src/ui/projectmanager/pm.go @@ -24,11 +24,8 @@ type ProjectManager interface { Get(projectIDOrName interface{}) (*models.Project, error) IsPublic(projectIDOrName interface{}) (bool, error) Exist(projectIDOrName interface{}) (bool, error) - GetRoles(username string, projectIDOrName interface{}) ([]int, error) // get all public project GetPublic() ([]*models.Project, error) - // get projects which the user is a member of - GetByMember(username string) ([]*models.Project, error) Create(*models.Project) (int64, error) Delete(projectIDOrName interface{}) error Update(projectIDOrName interface{}, project *models.Project) error @@ -36,8 +33,4 @@ type ProjectManager interface { GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) // GetTotal returns the total count according to the query parameters GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) - // GetHasReadPerm returns a project list which the user has read - // permission of. The list should contains all public projects and - // projects which the user is a member of if the username is not nil - GetHasReadPerm(username ...string) ([]*models.Project, error) } diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go index 39517033b..571f81789 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/projectmanager/pms/pm.go @@ -25,9 +25,7 @@ import ( "strconv" "strings" - "github.com/vmware/harbor/src/common" "github.com/vmware/harbor/src/common/models" - "github.com/vmware/harbor/src/common/security/authcontext" er "github.com/vmware/harbor/src/common/utils/error" "github.com/vmware/harbor/src/common/utils/log" ) @@ -227,6 +225,7 @@ func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) { return project != nil, nil } +/* // GetRoles gets roles that the user has to the project // This method is used in GET /projects API. // Jobservice calls GET /projects API to get information of source @@ -279,6 +278,7 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) return roles, nil } +*/ func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) { pro, err := p.get(projectIDOrName) @@ -301,26 +301,6 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) { }) } -// GetByMember ... -func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) { - projects := []*models.Project{} - ctx, err := authcontext.GetAuthCtxOfUser(p.client, p.endpoint, p.getToken(), username) - if err != nil { - return projects, err - } - - names := ctx.GetMyProjects() - for _, name := range names { - project, err := p.Get(name) - if err != nil { - return projects, err - } - projects = append(projects, project) - } - - return projects, nil -} - // Create ... func (p *ProjectManager) Create(pro *models.Project) (int64, error) { proj := &project{ @@ -407,11 +387,6 @@ func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*mode return int64(len(projects)), err } -// GetHasReadPerm ... -func (p *ProjectManager) GetHasReadPerm(username ...string) ([]*models.Project, error) { - return nil, errors.New("GetHasReadPerm is unsupported") -} - func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, error) { req, err := http.NewRequest(method, p.endpoint+path, body) if err != nil { diff --git a/src/ui/projectmanager/pms/pm_test.go b/src/ui/projectmanager/pms/pm_test.go index aa14d6fde..bece67d0f 100644 --- a/src/ui/projectmanager/pms/pm_test.go +++ b/src/ui/projectmanager/pms/pm_test.go @@ -292,33 +292,6 @@ func TestExist(t *testing.T) { assert.True(t, exist) } -func TestGetRoles(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) - - // nil username, nil project - roles, err := pm.GetRoles("", nil) - assert.Nil(t, err) - assert.Zero(t, len(roles)) - - // non-exist project - _, err = pm.GetRoles("user01", "non_exist_project") - assert.NotNil(t, err) - - // exist project - name := "project_for_test_get_roles" - id, err := pm.Create(&models.Project{ - Name: name, - }) - require.Nil(t, err) - defer delete(t, id) - - roles, err = pm.GetRoles("user01", id) - assert.Nil(t, err) - assert.Zero(t, len(roles)) - - // TODO add test cases for real role of user -} - func TestGetPublic(t *testing.T) { pm := NewProjectManager(client, endpoint, tokenReader) @@ -348,11 +321,6 @@ func TestGetPublic(t *testing.T) { assert.True(t, found) } -// TODO add test case -func TestGetByMember(t *testing.T) { - -} - func TestCreate(t *testing.T) { pm := NewProjectManager(client, endpoint, tokenReader) @@ -492,12 +460,6 @@ func TestGetTotal(t *testing.T) { assert.Equal(t, total1+1, total2) } -func TestGetHasReadPerm(t *testing.T) { - pm := NewProjectManager(client, endpoint, tokenReader) - _, err := pm.GetHasReadPerm() - assert.NotNil(t, err) -} - func delete(t *testing.T, id int64) { pm := NewProjectManager(client, endpoint, tokenReader) if err := pm.Delete(id); err != nil { diff --git a/src/ui/service/token/token_test.go b/src/ui/service/token/token_test.go index a6a6207bc..a5828971e 100644 --- a/src/ui/service/token/token_test.go +++ b/src/ui/service/token/token_test.go @@ -29,6 +29,7 @@ import ( "runtime" "testing" + "github.com/vmware/harbor/src/common/models" "github.com/vmware/harbor/src/common/utils/test" "github.com/vmware/harbor/src/ui/config" ) @@ -225,6 +226,12 @@ func (f *fakeSecurityContext) HasWritePerm(projectIDOrName interface{}) bool { func (f *fakeSecurityContext) HasAllPerm(projectIDOrName interface{}) bool { return false } +func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) { + return nil, nil +} +func (f *fakeSecurityContext) GetProjectRoles(interface{}) []int { + return nil +} func TestFilterAccess(t *testing.T) { //TODO put initial data in DB to verify repository filter.