Skip to content

Commit

Permalink
Resolved user access management issue
Browse files Browse the repository at this point in the history
  • Loading branch information
AbdulWahab3181 committed Mar 13, 2024
1 parent d74acdf commit 9fc15b3
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 54 deletions.
26 changes: 16 additions & 10 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,27 @@ import (
"github.com/stakwork/sphinx-tribes/config"
"github.com/stakwork/sphinx-tribes/db"
"github.com/stakwork/sphinx-tribes/utils"
"gorm.io/gorm"
)

type bountyHandler struct {
httpClient HttpClient
db db.Database
getSocketConnections func(host string) (db.Client, error)
generateBountyResponse func(bounties []db.Bounty) []db.BountyResponse
httpClient HttpClient
db db.Database
getSocketConnections func(host string) (db.Client, error)
generateBountyResponse func(bounties []db.Bounty) []db.BountyResponse
userHasAccess func(pubKeyFromAuth string, uuid string, role string) bool
userHasManageBountyRoles func(pubKeyFromAuth string, uuid string) bool
}

func NewBountyHandler(httpClient HttpClient, database db.Database) *bountyHandler {
dbConf := db.NewDatabaseConfig(&gorm.DB{})
return &bountyHandler{

httpClient: httpClient,
db: database,
getSocketConnections: db.Store.GetSocketConnections,
httpClient: httpClient,
db: database,
getSocketConnections: db.Store.GetSocketConnections,
userHasAccess: dbConf.UserHasAccess,
userHasManageBountyRoles: dbConf.UserHasManageBountyRoles,
}
}

Expand Down Expand Up @@ -239,7 +245,7 @@ func (h *bountyHandler) CreateOrEditBounty(w http.ResponseWriter, r *http.Reques
// check if bounty belongs to user
if pubKeyFromAuth != dbBounty.OwnerID {
if bounty.OrgUuid != "" {
hasBountyRoles := h.db.UserHasManageBountyRoles(pubKeyFromAuth, bounty.OrgUuid)
hasBountyRoles := h.userHasManageBountyRoles(pubKeyFromAuth, bounty.OrgUuid)
if !hasBountyRoles {
msg := "You don't have a=the right permission ton update bounty"
fmt.Println(msg)
Expand Down Expand Up @@ -448,7 +454,7 @@ func (h *bountyHandler) MakeBountyPayment(w http.ResponseWriter, r *http.Request

// check if user is the admin of the organization
// or has a pay bounty role
hasRole := h.db.UserHasAccess(pubKeyFromAuth, bounty.OrgUuid, db.PayBounty)
hasRole := h.userHasAccess(pubKeyFromAuth, bounty.OrgUuid, db.PayBounty)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("You don't have appropriate permissions to pay bounties")
Expand Down Expand Up @@ -567,7 +573,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ

// check if user is the admin of the organization
// or has a withdraw bounty budget role
hasRole := h.db.UserHasAccess(pubKeyFromAuth, request.OrgUuid, db.WithdrawBudget)
hasRole := h.userHasAccess(pubKeyFromAuth, request.OrgUuid, db.WithdrawBudget)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
errMsg := formatPayError("You don't have appropriate permissions to withdraw bounty budget")
Expand Down
63 changes: 33 additions & 30 deletions handlers/bounty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ func TestCreateOrEditBounty(t *testing.T) {
ctx := context.WithValue(context.Background(), auth.ContextKey, "test-key")
mockDb := dbMocks.NewDatabase(t)
mockClient := mocks.NewHttpClient(t)
mockUserHasManageBountyRolesTrue := func(pubKeyFromAuth string, uuid string) bool {
return true
}
mockUserHasManageBountyRolesFalse := func(pubKeyFromAuth string, uuid string) bool {
return false
}
bHandler := NewBountyHandler(mockClient, mockDb)

t.Run("should return error if body is not a valid json", func(t *testing.T) {
Expand Down Expand Up @@ -127,13 +133,8 @@ func TestCreateOrEditBounty(t *testing.T) {
t.Run("return error if user does not have required roles", func(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.CreateOrEditBounty)
bHandler.userHasManageBountyRoles = mockUserHasManageBountyRolesFalse

mockOrg := db.Organization{
ID: 1,
Uuid: "org-1",
Name: "custom org",
OwnerPubKey: "org-key",
}
existingBounty := db.Bounty{
ID: 1,
Type: "coding",
Expand All @@ -147,7 +148,6 @@ func TestCreateOrEditBounty(t *testing.T) {
mockDb.On("UpdateBountyBoolColumn", mock.AnythingOfType("db.Bounty"), "show").Return(existingBounty)
mockDb.On("UpdateBountyNullColumn", mock.AnythingOfType("db.Bounty"), "assignee").Return(existingBounty)
mockDb.On("GetBounty", uint(1)).Return(existingBounty).Once()
mockDb.On("UserHasManageBountyRoles", "test-key", mockOrg.Uuid).Return(false).Once()

body, _ := json.Marshal(updatedBounty)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", bytes.NewReader(body))
Expand All @@ -163,13 +163,8 @@ func TestCreateOrEditBounty(t *testing.T) {
t.Run("should allow to add or edit bounty if user has role", func(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.CreateOrEditBounty)
bHandler.userHasManageBountyRoles = mockUserHasManageBountyRolesTrue

mockOrg := db.Organization{
ID: 1,
Uuid: "org-1",
Name: "custom org",
OwnerPubKey: "org-key",
}
existingBounty := db.Bounty{
ID: 1,
Type: "coding",
Expand All @@ -183,7 +178,6 @@ func TestCreateOrEditBounty(t *testing.T) {
mockDb.On("UpdateBountyBoolColumn", mock.AnythingOfType("db.Bounty"), "show").Return(existingBounty)
mockDb.On("UpdateBountyNullColumn", mock.AnythingOfType("db.Bounty"), "assignee").Return(existingBounty)
mockDb.On("GetBounty", uint(1)).Return(existingBounty).Once()
mockDb.On("UserHasManageBountyRoles", "test-key", mockOrg.Uuid).Return(true).Once()
mockDb.On("CreateOrEditBounty", mock.AnythingOfType("db.Bounty")).Return(updatedBounty, nil).Once()

body, _ := json.Marshal(updatedBounty)
Expand All @@ -201,13 +195,9 @@ func TestCreateOrEditBounty(t *testing.T) {
t.Run("should not update created at when bounty is updated", func(t *testing.T) {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.CreateOrEditBounty)
bHandler.userHasManageBountyRoles = mockUserHasManageBountyRolesTrue

now := time.Now().UnixMilli()
mockOrg := db.Organization{
ID: 1,
Uuid: "org-1",
Name: "custom org",
OwnerPubKey: "org-key",
}
existingBounty := db.Bounty{
ID: 1,
Type: "coding",
Expand All @@ -222,7 +212,6 @@ func TestCreateOrEditBounty(t *testing.T) {
mockDb.On("UpdateBountyBoolColumn", mock.AnythingOfType("db.Bounty"), "show").Return(existingBounty)
mockDb.On("UpdateBountyNullColumn", mock.AnythingOfType("db.Bounty"), "assignee").Return(existingBounty)
mockDb.On("GetBounty", uint(1)).Return(existingBounty).Once()
mockDb.On("UserHasManageBountyRoles", "test-key", mockOrg.Uuid).Return(true).Once()
mockDb.On("CreateOrEditBounty", mock.MatchedBy(func(b db.Bounty) bool {
return b.Created == now
})).Return(updatedBounty, nil).Once()
Expand Down Expand Up @@ -1151,6 +1140,12 @@ func TestMakeBountyPayment(t *testing.T) {
ctx := context.Background()
mockDb := &dbMocks.Database{}
mockHttpClient := &mocks.HttpClient{}
mockUserHasAccessTrue := func(pubKeyFromAuth string, uuid string, role string) bool {
return true
}
mockUserHasAccessFalse := func(pubKeyFromAuth string, uuid string, role string) bool {
return false
}
mockGetSocketConnections := func(host string) (db.Client, error) {
s, ws := MockNewWSServer(t)
defer s.Close()
Expand Down Expand Up @@ -1253,14 +1248,15 @@ func TestMakeBountyPayment(t *testing.T) {
})

t.Run("401 error if user not organization admin or does not have PAY BOUNTY role", func(t *testing.T) {
bHandler.userHasAccess = mockUserHasAccessFalse

mockDb.On("GetBounty", mock.AnythingOfType("uint")).Return(db.Bounty{
ID: 1,
Price: 1000,
OrgUuid: "org-1",
Assignee: "assignee-1",
Paid: false,
}, nil)
mockDb.On("UserHasAccess", "valid-key", "org-1", db.PayBounty).Return(false)

r := chi.NewRouter()
r.Post("/gobounties/pay/{id}", bHandler.MakeBountyPayment)
Expand All @@ -1283,14 +1279,14 @@ func TestMakeBountyPayment(t *testing.T) {
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
bHandler.userHasAccess = mockUserHasAccessTrue
mockDb.On("GetBounty", mock.AnythingOfType("uint")).Return(db.Bounty{
ID: 1,
Price: 1000,
OrgUuid: "org-1",
Assignee: "assignee-1",
Paid: false,
}, nil)
mockDb.On("UserHasAccess", "valid-key", "org-1", db.PayBounty).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: 500,
}, nil)
Expand All @@ -1313,6 +1309,7 @@ func TestMakeBountyPayment(t *testing.T) {
t.Run("Should test that a successful WebSocket message is sent if the payment is successful", func(t *testing.T) {
mockDb.ExpectedCalls = nil
bHandler.getSocketConnections = mockGetSocketConnections
bHandler.userHasAccess = mockUserHasAccessTrue

now := time.Now()
expectedBounty := db.Bounty{
Expand All @@ -1326,7 +1323,6 @@ func TestMakeBountyPayment(t *testing.T) {
}

mockDb.On("GetBounty", bountyID).Return(bounty, nil)
mockDb.On("UserHasAccess", "valid-key", bounty.OrgUuid, db.PayBounty).Return(true)
mockDb.On("GetOrganizationBudget", bounty.OrgUuid).Return(db.BountyBudget{TotalBudget: 2000}, nil)
mockDb.On("GetPersonByPubkey", bounty.Assignee).Return(db.Person{OwnerPubKey: "assignee-1", OwnerRouteHint: "OwnerRouteHint"}, nil)
mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")).Return(db.PaymentHistory{ID: 1})
Expand Down Expand Up @@ -1373,9 +1369,9 @@ func TestMakeBountyPayment(t *testing.T) {

bHandler2 := NewBountyHandler(mockHttpClient2, mockDb2)
bHandler2.getSocketConnections = mockGetSocketConnections
bHandler2.userHasAccess = mockUserHasAccessTrue

mockDb2.On("GetBounty", bountyID).Return(bounty, nil)
mockDb2.On("UserHasAccess", "valid-key", bounty.OrgUuid, db.PayBounty).Return(true)
mockDb2.On("GetOrganizationBudget", bounty.OrgUuid).Return(db.BountyBudget{TotalBudget: 2000}, nil)
mockDb2.On("GetPersonByPubkey", bounty.Assignee).Return(db.Person{OwnerPubKey: "assignee-1", OwnerRouteHint: "OwnerRouteHint"}, nil)

Expand Down Expand Up @@ -1413,6 +1409,12 @@ func TestBountyBudgetWithdraw(t *testing.T) {
ctx := context.Background()
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
mockUserHasAccessTrue := func(pubKeyFromAuth string, uuid string, role string) bool {
return true
}
mockUserHasAccessFalse := func(pubKeyFromAuth string, uuid string, role string) bool {
return false
}
bHandler := NewBountyHandler(mockHttpClient, mockDb)
unauthorizedCtx := context.WithValue(context.Background(), auth.ContextKey, "")
authorizedCtx := context.WithValue(ctx, auth.ContextKey, "valid-key")
Expand Down Expand Up @@ -1446,9 +1448,10 @@ func TestBountyBudgetWithdraw(t *testing.T) {
})

t.Run("401 error if user is not the organization admin or does not have WithdrawBudget role", func(t *testing.T) {
bHandler.userHasAccess = mockUserHasAccessFalse

rr := httptest.NewRecorder()
handler := http.HandlerFunc(bHandler.BountyBudgetWithdraw)
mockDb.On("UserHasAccess", "valid-key", mock.AnythingOfType("string"), db.WithdrawBudget).Return(false)

validData := []byte(`{"orgUuid": "org-1", "paymentRequest": "invoice"}`)
req, err := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/budget/withdraw", bytes.NewReader(validData))
Expand All @@ -1467,8 +1470,8 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
bHandler.userHasAccess = mockUserHasAccessTrue

mockDb.On("UserHasAccess", "valid-key", "org-1", db.WithdrawBudget).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: 500,
}, nil)
Expand Down Expand Up @@ -1497,10 +1500,10 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
bHandler.userHasAccess = mockUserHasAccessTrue

paymentAmount := uint(1500)

mockDb.On("UserHasAccess", "valid-key", "org-1", db.WithdrawBudget).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: 5000,
}, nil)
Expand Down Expand Up @@ -1536,8 +1539,8 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
bHandler.userHasAccess = mockUserHasAccessTrue

mockDb.On("UserHasAccess", "valid-key", "org-1", db.WithdrawBudget).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: 5000,
}, nil)
Expand Down Expand Up @@ -1573,6 +1576,7 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
bHandler.userHasAccess = mockUserHasAccessTrue

paymentAmount := uint(1500)
initialBudget := uint(5000)
Expand All @@ -1586,7 +1590,6 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockHttpClient.ExpectedCalls = nil
mockHttpClient.Calls = nil

mockDb.On("UserHasAccess", "valid-key", "org-1", db.WithdrawBudget).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: expectedFinalBudget,
}, nil)
Expand Down
16 changes: 13 additions & 3 deletions handlers/organization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ func TestGetOrganizationBounties(t *testing.T) {
func TestGetOrganizationBudget(t *testing.T) {
ctx := context.WithValue(context.Background(), auth.ContextKey, "test-key")
mockDb := mocks.NewDatabase(t)
mockUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool {
return true
}
oHandler := NewOrganizationHandler(mockDb)

t.Run("Should test that a 401 is returned when trying to view an organization's budget without a token", func(t *testing.T) {
Expand Down Expand Up @@ -437,7 +440,7 @@ func TestGetOrganizationBudget(t *testing.T) {
Updated: nil,
}

mockDb.On("UserHasAccess", "test-key", orgUUID, "VIEW REPORT").Return(true).Once()
oHandler.userHasAccess = mockUserHasAccess
mockDb.On("GetOrganizationBudget", orgUUID).Return(expectedBudget).Once()

rctx := chi.NewRouteContext()
Expand Down Expand Up @@ -470,7 +473,10 @@ func TestGetOrganizationBudgetHistory(t *testing.T) {
t.Run("Should test that a 401 is returned when trying to view an organization's budget history without a token", func(t *testing.T) {
orgUUID := "valid-uuid"

mockDb.On("UserHasAccess", "", orgUUID, "VIEW REPORT").Return(false).Once()
mockUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool {
return false
}
oHandler.userHasAccess = mockUserHasAccess

rctx := chi.NewRouteContext()
rctx.URLParams.Add("uuid", orgUUID)
Expand All @@ -492,7 +498,11 @@ func TestGetOrganizationBudgetHistory(t *testing.T) {
{BudgetHistory: db.BudgetHistory{ID: 2, OrgUuid: orgUUID, Created: nil, Updated: nil}, SenderName: "Sender2"},
}

mockDb.On("UserHasAccess", "test-key", orgUUID, "VIEW REPORT").Return(true).Once()
mockUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool {
return true
}
oHandler.userHasAccess = mockUserHasAccess

mockDb.On("GetOrganizationBudgetHistory", orgUUID).Return(expectedBudgetHistory).Once()

rctx := chi.NewRouteContext()
Expand Down
28 changes: 17 additions & 11 deletions handlers/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,26 @@ import (
"github.com/stakwork/sphinx-tribes/auth"
"github.com/stakwork/sphinx-tribes/db"
"github.com/stakwork/sphinx-tribes/utils"
"gorm.io/gorm"
)

type organizationHandler struct {
db db.Database
generateBountyHandler func(bounties []db.Bounty) []db.BountyResponse
getLightningInvoice func(payment_request string) (db.InvoiceResult, db.InvoiceError)
db db.Database
generateBountyHandler func(bounties []db.Bounty) []db.BountyResponse
getLightningInvoice func(payment_request string) (db.InvoiceResult, db.InvoiceError)
userHasAccess func(pubKeyFromAuth string, uuid string, role string) bool
userHasManageBountyRoles func(pubKeyFromAuth string, uuid string) bool
}

func NewOrganizationHandler(db db.Database) *organizationHandler {
bHandler := NewBountyHandler(http.DefaultClient, db)
func NewOrganizationHandler(database db.Database) *organizationHandler {
bHandler := NewBountyHandler(http.DefaultClient, database)
dbConf := db.NewDatabaseConfig(&gorm.DB{})
return &organizationHandler{
db: db,
generateBountyHandler: bHandler.GenerateBountyResponse,
getLightningInvoice: bHandler.GetLightningInvoice,
db: database,
generateBountyHandler: bHandler.GenerateBountyResponse,
getLightningInvoice: bHandler.GetLightningInvoice,
userHasAccess: dbConf.UserHasAccess,
userHasManageBountyRoles: dbConf.UserHasManageBountyRoles,
}
}

Expand Down Expand Up @@ -487,7 +493,7 @@ func (oh *organizationHandler) GetUserDropdownOrganizations(w http.ResponseWrite
organization := db.DB.GetOrganizationByUuid(uuid)
bountyCount := db.DB.GetOrganizationBountyCount(uuid)
hasRole := db.UserHasAccess(user.OwnerPubKey, uuid, db.ViewReport)
hasBountyRoles := oh.db.UserHasManageBountyRoles(user.OwnerPubKey, uuid)
hasBountyRoles := oh.userHasManageBountyRoles(user.OwnerPubKey, uuid)

// don't add deleted organizations to the list
if !organization.Deleted && hasBountyRoles {
Expand Down Expand Up @@ -558,7 +564,7 @@ func (oh *organizationHandler) GetOrganizationBudget(w http.ResponseWriter, r *h
}

// if not the organization admin
hasRole := oh.db.UserHasAccess(pubKeyFromAuth, uuid, db.ViewReport)
hasRole := oh.userHasAccess(pubKeyFromAuth, uuid, db.ViewReport)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("Don't have access to view budget")
Expand All @@ -578,7 +584,7 @@ func (oh *organizationHandler) GetOrganizationBudgetHistory(w http.ResponseWrite
uuid := chi.URLParam(r, "uuid")

// if not the organization admin
hasRole := oh.db.UserHasAccess(pubKeyFromAuth, uuid, db.ViewReport)
hasRole := oh.userHasAccess(pubKeyFromAuth, uuid, db.ViewReport)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("Don't have access to view budget history")
Expand Down

0 comments on commit 9fc15b3

Please sign in to comment.