Explorar el Código

add get policy doc endpoint

Alexander Belanger hace 4 años
padre
commit
4ad13f1b21

+ 101 - 0
api/types/policy.go

@@ -0,0 +1,101 @@
+package types
+
+type PermissionScope string
+
+const (
+	UserScope        PermissionScope = "user"
+	ProjectScope     PermissionScope = "project"
+	ClusterScope     PermissionScope = "cluster"
+	NamespaceScope   PermissionScope = "namespace"
+	SettingsScope    PermissionScope = "settings"
+	ApplicationScope PermissionScope = "application"
+)
+
+type NameOrUInt struct {
+	Name string `json:"name"`
+	UInt uint   `json:"uint"`
+}
+
+type PolicyDocument struct {
+	Scope     PermissionScope                     `json:"scope"`
+	Resources []NameOrUInt                        `json:"resources"`
+	Verbs     []APIVerb                           `json:"verbs"`
+	Children  map[PermissionScope]*PolicyDocument `json:"children"`
+}
+
+type ScopeTree map[PermissionScope]ScopeTree
+
+/* ScopeHeirarchy describes the scope tree:
+			Project
+		   /	   \
+		Cluster   Settings
+		/
+	Namespace
+       |
+	 Release
+*/
+var ScopeHeirarchy = ScopeTree{
+	ProjectScope: {
+		ClusterScope: {
+			NamespaceScope: {
+				ApplicationScope: {},
+			},
+		},
+		SettingsScope: {},
+	},
+}
+
+type Policy []*PolicyDocument
+
+type APIVerb string
+
+const (
+	APIVerbGet    APIVerb = "get"
+	APIVerbCreate APIVerb = "create"
+	APIVerbList   APIVerb = "list"
+	APIVerbUpdate APIVerb = "update"
+	APIVerbDelete APIVerb = "delete"
+)
+
+type APIVerbGroup []APIVerb
+
+func ReadVerbGroup() APIVerbGroup {
+	return []APIVerb{APIVerbGet, APIVerbList}
+}
+
+func ReadWriteVerbGroup() APIVerbGroup {
+	return []APIVerb{APIVerbGet, APIVerbList, APIVerbCreate, APIVerbUpdate, APIVerbDelete}
+}
+
+var AdminPolicy = []*PolicyDocument{
+	{
+		Scope: ProjectScope,
+		Verbs: ReadWriteVerbGroup(),
+	},
+}
+
+var DeveloperPolicy = []*PolicyDocument{
+	{
+		Scope: ProjectScope,
+		Verbs: ReadWriteVerbGroup(),
+		Children: map[PermissionScope]*PolicyDocument{
+			SettingsScope: {
+				Scope: SettingsScope,
+				Verbs: ReadVerbGroup(),
+			},
+		},
+	},
+}
+
+var ViewerPolicy = []*PolicyDocument{
+	{
+		Scope: ProjectScope,
+		Verbs: ReadVerbGroup(),
+		Children: map[PermissionScope]*PolicyDocument{
+			SettingsScope: {
+				Scope: SettingsScope,
+				Verbs: []APIVerb{},
+			},
+		},
+	},
+}

+ 3 - 2
internal/models/role.go

@@ -6,8 +6,9 @@ import (
 
 // The roles available for a project
 const (
-	RoleAdmin  string = "admin"
-	RoleViewer string = "viewer"
+	RoleAdmin     string = "admin"
+	RoleDeveloper string = "developer"
+	RoleViewer    string = "viewer"
 )
 
 // Role type that extends gorm.Model

+ 43 - 0
internal/repository/gorm/project.go

@@ -41,6 +41,22 @@ func (repo *ProjectRepository) CreateProjectRole(project *models.Project, role *
 	return role, nil
 }
 
+func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error) {
+	foundRole := &models.Role{}
+
+	if err := repo.db.Where("project_id = ? AND user_id = ?", projID, role.UserID).First(&foundRole).Error; err != nil {
+		return nil, err
+	}
+
+	role.ID = foundRole.ID
+
+	if err := repo.db.Save(&role).Error; err != nil {
+		return nil, err
+	}
+
+	return role, nil
+}
+
 // ReadProject gets a projects specified by a unique id
 func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
 	project := &models.Project{}
@@ -52,6 +68,18 @@ func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
 	return project, nil
 }
 
+// ReadProject gets a projects specified by a unique id
+func (repo *ProjectRepository) ReadProjectRole(projID, userID uint) (*models.Role, error) {
+	// find the role
+	role := &models.Role{}
+
+	if err := repo.db.Where("project_id = ? AND user_id = ?", projID, userID).First(&role).Error; err != nil {
+		return nil, err
+	}
+
+	return role, nil
+}
+
 // ListProjectsByUserID lists projects where a user has an associated role
 func (repo *ProjectRepository) ListProjectsByUserID(userID uint) ([]*models.Project, error) {
 	projects := make([]*models.Project, 0)
@@ -72,3 +100,18 @@ func (repo *ProjectRepository) DeleteProject(project *models.Project) (*models.P
 	}
 	return project, nil
 }
+
+func (repo *ProjectRepository) DeleteProjectRole(projID, userID uint) (*models.Role, error) {
+	// find the role
+	role := &models.Role{}
+
+	if err := repo.db.Where("project_id = ? AND user_id = ?", projID, userID).First(&role).Error; err != nil {
+		return nil, err
+	}
+
+	if err := repo.db.Delete(&role).Error; err != nil {
+		return nil, err
+	}
+
+	return role, nil
+}

+ 117 - 1
internal/repository/gorm/project_test.go

@@ -108,6 +108,72 @@ func TestCreateProjectRole(t *testing.T) {
 	}
 }
 
+func TestUpdateProjectRole(t *testing.T) {
+	tester := &tester{
+		dbFileName: "./porter_update_proj_role.db",
+	}
+
+	setupTestEnv(tester, t)
+	initProject(tester, t)
+	initUser(tester, t)
+	initProjectRole(tester, t)
+	defer cleanup(tester, t)
+
+	role := &models.Role{
+		Kind:      models.RoleViewer,
+		UserID:    tester.initUsers[0].Model.ID,
+		ProjectID: tester.initProjects[0].Model.ID,
+	}
+
+	role, err := tester.repo.Project.UpdateProjectRole(tester.initProjects[0].Model.ID, role)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	proj, err := tester.repo.Project.ReadProject(tester.initProjects[0].Model.ID)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// make sure IDs are correct
+	if proj.Model.ID != 1 {
+		t.Errorf("incorrect project ID: expected %d, got %d\n", 1, proj.Model.ID)
+	}
+
+	if len(proj.Roles) != 1 {
+		t.Fatalf("project roles incorrect length: expected %d, got %d\n", 1, len(proj.Roles))
+	}
+
+	if proj.Roles[0].Model.ID != 1 {
+		t.Fatalf("incorrect role ID: expected %d, got %d\n", 1, proj.Roles[0].Model.ID)
+	}
+
+	// make sure data is correct
+	expProj := &models.Project{
+		Name: "project-test",
+		Roles: []models.Role{
+			{
+				Kind:      models.RoleViewer,
+				UserID:    1,
+				ProjectID: 1,
+			},
+		},
+	}
+
+	copyProj := proj
+
+	// reset fields for reflect.DeepEqual
+	copyProj.Model = orm.Model{}
+	copyProj.Roles[0].Model = orm.Model{}
+
+	if diff := deep.Equal(copyProj, expProj); diff != nil {
+		t.Errorf("incorrect project")
+		t.Error(diff)
+	}
+}
+
 func TestListProjectsByUserID(t *testing.T) {
 	tester := &tester{
 		dbFileName: "./list_projects_user_id.db",
@@ -172,7 +238,7 @@ func TestListProjectsByUserID(t *testing.T) {
 
 func TestDeleteProject(t *testing.T) {
 	tester := &tester{
-		dbFileName: "./porter_create_proj_role.db",
+		dbFileName: "./porter_delete_proj.db",
 	}
 
 	setupTestEnv(tester, t)
@@ -192,3 +258,53 @@ func TestDeleteProject(t *testing.T) {
 		t.Fatalf("read should have returned record not found: returned %v\n", err)
 	}
 }
+
+func TestDeleteProjectRole(t *testing.T) {
+	tester := &tester{
+		dbFileName: "./porter_delete_proj_role.db",
+	}
+
+	setupTestEnv(tester, t)
+	initProject(tester, t)
+	initUser(tester, t)
+	initProjectRole(tester, t)
+	defer cleanup(tester, t)
+
+	_, err := tester.repo.Project.DeleteProjectRole(tester.initProjects[0].Model.ID, tester.initUsers[0].Model.ID)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// attempt to read the project and ensure that the error is gorm.ErrRecordNotFound
+	proj, err := tester.repo.Project.ReadProject(tester.initProjects[0].Model.ID)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// make sure IDs are correct
+	if proj.Model.ID != 1 {
+		t.Errorf("incorrect project ID: expected %d, got %d\n", 1, proj.Model.ID)
+	}
+
+	if len(proj.Roles) != 0 {
+		t.Fatalf("project roles incorrect length: expected %d, got %d\n", 0, len(proj.Roles))
+	}
+
+	// make sure data is correct
+	expProj := &models.Project{
+		Name:  "project-test",
+		Roles: []models.Role{},
+	}
+
+	copyProj := proj
+
+	// reset fields for reflect.DeepEqual
+	copyProj.Model = orm.Model{}
+
+	if diff := deep.Equal(copyProj, expProj); diff != nil {
+		t.Errorf("incorrect project")
+		t.Error(diff)
+	}
+}

+ 107 - 0
internal/repository/memory/project.go

@@ -51,6 +51,41 @@ func (repo *ProjectRepository) CreateProjectRole(project *models.Project, role *
 	return role, nil
 }
 
+// CreateProjectRole appends a role to the existing array of roles
+func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	var foundProject *models.Project
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		if project.ID == projID {
+			foundProject = project
+		}
+	}
+
+	if foundProject == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	var index int
+
+	for i, _role := range foundProject.Roles {
+		if _role.UserID == role.UserID {
+			index = i
+		}
+	}
+
+	if index == 0 {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	foundProject.Roles[index] = *role
+	return role, nil
+}
+
 // ReadProject gets a projects specified by a unique id
 func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
 	if !repo.canQuery {
@@ -65,6 +100,42 @@ func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
 	return repo.projects[index], nil
 }
 
+// ReadProjectRole gets a role specified by a project ID and user ID
+func (repo *ProjectRepository) ReadProjectRole(projID, userID uint) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	var foundProject *models.Project
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		if project.ID == projID {
+			foundProject = project
+		}
+	}
+
+	if foundProject == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	var index int
+
+	for i, _role := range foundProject.Roles {
+		if _role.UserID == userID {
+			index = i
+		}
+	}
+
+	if index == 0 {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	res := foundProject.Roles[index]
+
+	return &res, nil
+}
+
 // ListProjectsByUserID lists projects where a user has an associated role
 func (repo *ProjectRepository) ListProjectsByUserID(userID uint) ([]*models.Project, error) {
 	if !repo.canQuery {
@@ -100,3 +171,39 @@ func (repo *ProjectRepository) DeleteProject(project *models.Project) (*models.P
 
 	return project, nil
 }
+
+func (repo *ProjectRepository) DeleteProjectRole(projID, userID uint) (*models.Role, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	var foundProject *models.Project
+
+	// find all roles matching
+	for _, project := range repo.projects {
+		if project.ID == projID {
+			foundProject = project
+		}
+	}
+
+	if foundProject == nil {
+		return nil, gorm.ErrRecordNotFound
+	}
+
+	var index int
+
+	for i, _role := range foundProject.Roles {
+		if _role.UserID == userID {
+			index = i
+		}
+	}
+
+	if index == 0 {
+		return nil, gorm.ErrRecordNotFound
+	}
+	res := foundProject.Roles[index]
+
+	foundProject.Roles = append(foundProject.Roles[:index], foundProject.Roles[index+1:]...)
+
+	return &res, nil
+}

+ 3 - 0
internal/repository/project.go

@@ -11,7 +11,10 @@ type WriteProject func(project *models.Project) (*models.Project, error)
 type ProjectRepository interface {
 	CreateProject(project *models.Project) (*models.Project, error)
 	CreateProjectRole(project *models.Project, role *models.Role) (*models.Role, error)
+	UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error)
 	ReadProject(id uint) (*models.Project, error)
+	ReadProjectRole(projID, userID uint) (*models.Role, error)
 	ListProjectsByUserID(userID uint) ([]*models.Project, error)
 	DeleteProject(project *models.Project) (*models.Project, error)
+	DeleteProjectRole(projID, userID uint) (*models.Role, error)
 }

+ 41 - 0
server/api/project_handler.go

@@ -6,6 +6,7 @@ import (
 	"strconv"
 
 	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/forms"
 	"github.com/porter-dev/porter/internal/models"
 )
@@ -110,6 +111,46 @@ func (app *App) HandleReadProject(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// HandleReadProjectPolicy returns the policy document given the current user
+func (app *App) HandleReadProjectPolicy(w http.ResponseWriter, r *http.Request) {
+	id, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || id == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	userID, err := app.getUserIDFromRequest(r)
+
+	if err != nil {
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	role, err := app.Repo.Project.ReadProjectRole(uint(id), userID)
+
+	if err != nil {
+		app.handleErrorRead(err, ErrProjectDataRead, w)
+		return
+	}
+
+	// case on the role to get the policy document
+	var policy types.Policy
+	switch role.Kind {
+	case models.RoleAdmin:
+		policy = types.AdminPolicy
+	case models.RoleDeveloper:
+		policy = types.DeveloperPolicy
+	case models.RoleViewer:
+		policy = types.ViewerPolicy
+	}
+
+	if err := json.NewEncoder(w).Encode(policy); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}
+
 // HandleDeleteProject deletes a project from the db, reading from the project_id
 // in the URL param
 func (app *App) HandleDeleteProject(w http.ResponseWriter, r *http.Request) {

+ 10 - 0
server/router/router.go

@@ -254,6 +254,16 @@ func New(a *api.App) *chi.Mux {
 				),
 			)
 
+			r.Method(
+				"GET",
+				"/projects/{project_id}/policy",
+				auth.DoesUserHaveProjectAccess(
+					requestlog.NewHandler(a.HandleReadProjectPolicy, l),
+					mw.URLParam,
+					mw.ReadAccess,
+				),
+			)
+
 			r.Method(
 				"POST",
 				"/projects",