Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove orgId dependency in authz #132

Merged
merged 12 commits into from
Aug 9, 2022
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ linters:
- vet
- goimports
- thelper
- tparallel
- unconvert
- wastedassign
- revive
- unused
- gofmt
Expand Down
38 changes: 28 additions & 10 deletions internal/proxy/hook/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import (
"net/http"
"strings"

"github.com/mitchellh/mapstructure"

"github.com/odpf/salt/log"
"github.com/odpf/shield/core/namespace"
"github.com/odpf/shield/core/project"
"github.com/odpf/shield/core/resource"
"github.com/odpf/shield/core/user"
"github.com/odpf/shield/internal/api"
"github.com/odpf/shield/internal/proxy/hook"
"github.com/odpf/shield/internal/proxy/middleware"
"github.com/odpf/shield/pkg/body_extractor"

"github.com/mitchellh/mapstructure"
"github.com/odpf/salt/log"
)

type ResourceService interface {
Expand All @@ -30,12 +32,18 @@ type Authz struct {
// To skip all the next hooks and just respond back
escape hook.Service

Deps api.Deps
niharbansal02 marked this conversation as resolved.
Show resolved Hide resolved

// TODO need to figure out what best to pass this
identityProxyHeader string

resourceService ResourceService
}

type ProjectService interface {
Get(ctx context.Context, id string) (project.Project, error)
}

func New(log log.Logger, next, escape hook.Service, identityProxyHeader string, resourceService ResourceService) Authz {
return Authz{
log: log,
Expand Down Expand Up @@ -177,7 +185,7 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
attributes[key] = value
}

resources, err := createResources(attributes)
resources, err := createResources(res.Request.Context(), attributes, a.Deps.ProjectService)
if err != nil {
a.log.Error(err.Error())
return a.escape.ServeHook(res, fmt.Errorf(err.Error()))
Expand All @@ -194,16 +202,26 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
return a.next.ServeHook(res, nil)
}

func createResources(permissionAttributes map[string]interface{}) ([]resource.Resource, error) {
func createResources(ctx context.Context, permissionAttributes map[string]interface{}, p ProjectService) ([]resource.Resource, error) {
var resources []resource.Resource
projects, err := getAttributesValues(permissionAttributes["project"])
AkarshSatija marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

niharbansal02 marked this conversation as resolved.
Show resolved Hide resolved
orgs, err := getAttributesValues(permissionAttributes["organization"])
if err != nil {
return nil, err
var orgs []string
var projIds []string
for _, proj := range projects {
project, err := p.Get(ctx, proj)
niharbansal02 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

orgId := project.Organization.ID
orgs = append(orgs, orgId)

projId := project.ID
projIds = append(projIds, projId)
}

teams, err := getAttributesValues(permissionAttributes["team"])
Expand All @@ -227,11 +245,11 @@ func createResources(permissionAttributes map[string]interface{}) ([]resource.Re
}

if len(projects) < 1 || len(orgs) < 1 || len(resourceList) < 1 || (backendNamespace[0] == "") || (resourceType[0] == "") {
return nil, fmt.Errorf("namespace, resource type, projects, organizations, resource, and team are required")
return nil, fmt.Errorf("namespace, resource type, projects, resource, and team are required")
}

for _, org := range orgs {
for _, project := range projects {
for _, project := range projIds {
for _, res := range resourceList {
if len(teams) > 0 {
for _, team := range teams {
Expand Down
246 changes: 171 additions & 75 deletions internal/proxy/hook/authz/authz_test.go
Original file line number Diff line number Diff line change
@@ -1,90 +1,186 @@
package authz

import (
"context"
"fmt"
"testing"
"time"

"github.com/odpf/shield/core/resource"
"github.com/stretchr/testify/assert"

"github.com/odpf/shield/core/organization"
"github.com/odpf/shield/core/project"
"github.com/odpf/shield/core/resource"
"github.com/odpf/shield/internal/api"
)

func TestCreateResources(t *testing.T) {
t.Run("should should throw error if project is missing", func(t *testing.T) {
input := map[string]interface{}{
"abc": "abc",
}
output, err := createResources(input)
var expected []resource.Resource
assert.EqualValues(t, expected, output)
assert.Error(t, err)
})
var testPermissionAttributesMap = map[string]any{
"project": "ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71",
"team": "team1",
"resource": []string{"resc1", "resc2"},
"namespace": "ns1",
"resource_type": "kind",
}

t.Run("should should throw error if team is missing", func(t *testing.T) {
input := map[string]interface{}{
"project": "abc",
}
output, err := createResources(input)
var expected []resource.Resource
assert.EqualValues(t, expected, output)
assert.Error(t, err)
})
var testProjectMap = map[string]project.Project{
"ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71": {
ID: "ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71",
Name: "Prj 1",
Slug: "prj-1",
Metadata: map[string]any{
"email": "org1@org1.com",
},
Organization: organization.Organization{
ID: "org1",
Name: "Org 1",
Slug: "Org Slug 1",
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
"c7772c63-fca4-4c7c-bf93-c8f85115de4b": {
ID: "c7772c63-fca4-4c7c-bf93-c8f85115de4b",
Name: "Prj 2",
Slug: "prj-2",
Metadata: map[string]any{
"email": "org1@org2.com",
},
Organization: organization.Organization{
ID: "org2",
Name: "Org 2",
Slug: "Org Slug 2",
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
"project-3-slug": {
ID: "c3772d61-faa1-4d8d-fff3-c8fa5a1fdc4b",
Name: "Prj 3",
Slug: "project-3-slug",
Metadata: map[string]any{
"email": "org1@org2.com",
},
Organization: organization.Organization{
ID: "org2",
Name: "Org 2",
Slug: "Org Slug 2",
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
}

t.Run("should return resource", func(t *testing.T) {
input := map[string]interface{}{
"project": "project1",
"team": "team1",
"organization": "org1",
"resource": "res1",
"namespace": "ns1",
"resource_type": "type",
}
output, err := createResources(input)
expected := []resource.Resource{
{
ProjectID: "project1",
OrganizationID: "org1",
GroupID: "team1",
Name: "res1",
NamespaceID: "ns1_type",
},
}
assert.EqualValues(t, expected, output)
assert.NoError(t, err)
})
var expectedResources = []resource.Resource{
{
ProjectID: "ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71",
OrganizationID: "org1",
GroupID: "team1",
Name: "resc1",
NamespaceID: "ns1_kind",
}, {
ProjectID: "ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71",
OrganizationID: "org1",
GroupID: "team1",
Name: "resc2",
NamespaceID: "ns1_kind",
},
}

func TestCreateResources(t *testing.T) {
t.Parallel()

t.Run("should return multiple resource", func(t *testing.T) {
input := map[string]interface{}{
"project": "project1",
"team": "team1",
"organization": "org1",
"namespace": "ns1",
"resource": []string{"res1", "res2", "res3"},
"resource_type": "kind",
}
output, err := createResources(input)
expected := []resource.Resource{
{
ProjectID: "project1",
OrganizationID: "org1",
GroupID: "team1",
Name: "res1",
NamespaceID: "ns1_kind",
table := []struct {
title string
mockProjectServ mockProject
permissionAttributes map[string]any
v api.Deps
want []resource.Resource
err error
}{
{
title: "success/should return multiple resources",
mockProjectServ: mockProject{
GetProjectFunc: func(ctx context.Context, id string) (project.Project, error) {
return testProjectMap[id], nil
}},
permissionAttributes: testPermissionAttributesMap,
v: api.Deps{},
want: expectedResources,
err: nil,
}, {
title: "should should throw error if project is missing",
mockProjectServ: mockProject{
GetProjectFunc: func(ctx context.Context, id string) (project.Project, error) {
return project.Project{}, fmt.Errorf("Project ID not found")
},
},
{
ProjectID: "project1",
OrganizationID: "org1",
GroupID: "team1",
Name: "res2",
NamespaceID: "ns1_kind",
permissionAttributes: map[string]any{
"team": "team1",
"resource": []string{"resc1", "resc2"},
"namespace": "ns1",
"resource_type": "kind",
},
{
ProjectID: "project1",
OrganizationID: "org1",
GroupID: "team1",
Name: "res3",
NamespaceID: "ns1_kind",
v: api.Deps{},
want: nil,
err: fmt.Errorf("namespace, resource type, projects, resource, and team are required"),
}, {
title: "should should throw error if team is missing",
mockProjectServ: mockProject{
GetProjectFunc: func(ctx context.Context, id string) (project.Project, error) {
return testProjectMap[id], nil
},
},
}
assert.EqualValues(t, expected, output)
assert.NoError(t, err)
})
permissionAttributes: map[string]any{
"project": "ab657ae7-8c9e-45eb-9862-dd9ceb6d5c71",
"resource": []string{"resc1", "resc2"},
"namespace": "ns1",
"resource_type": "kind",
},
v: api.Deps{},
want: nil,
err: fmt.Errorf("namespace, resource type, projects, resource, and team are required"),
}, {
title: "success/should return resource",
mockProjectServ: mockProject{
GetProjectFunc: func(ctx context.Context, id string) (project.Project, error) {
return testProjectMap[id], nil
}},
permissionAttributes: map[string]any{
"project": "c7772c63-fca4-4c7c-bf93-c8f85115de4b",
"team": "team1",
"resource": "res1",
"namespace": "ns1",
"resource_type": "type",
},
v: api.Deps{},
want: []resource.Resource{
{
ProjectID: "c7772c63-fca4-4c7c-bf93-c8f85115de4b",
OrganizationID: "org2",
GroupID: "team1",
Name: "res1",
NamespaceID: "ns1_type",
},
},
err: nil,
},
}

for _, tt := range table {
t.Run(tt.title, func(t *testing.T) {
t.Parallel()

resp, err := createResources(context.Background(), tt.permissionAttributes, tt.mockProjectServ)
assert.EqualValues(t, tt.want, resp)
assert.EqualValues(t, tt.err, err)
})
}
}

type mockProject struct {
GetProjectFunc func(ctx context.Context, id string) (project.Project, error)
}

func (m mockProject) Get(ctx context.Context, id string) (project.Project, error) {
return m.GetProjectFunc(ctx, id)
}