Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions handlers/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,157 @@ func (oh *workspaceHandler) CreateWorkspaceUser(w http.ResponseWriter, r *http.R
json.NewEncoder(w).Encode(user)
}

type WorkspaceUserUpdateRequest struct {
OwnerPubKey string `json:"owner_pubkey"`
NewOwnerPubKey string `json:"new_owner_pubkey,omitempty"`
OrgUuid string `json:"org_uuid"`
WorkspaceUuid string `json:"workspace_uuid,omitempty"`
}

// UpdateWorkspaceUser godoc
//
// @Summary Update Workspace User
// @Description Update a user in a workspace
// @Tags Workspace - Users
// @Accept json
// @Produce json
// @Security PubKeyContextAuth
// @Param uuid path string true "Workspace UUID"
// @Param user path string true "Current User PubKey"
// @Param workspaceUser body WorkspaceUserUpdateRequest true "Workspace User Update Data"
// @Success 200 {object} db.WorkspaceUsers
// @Router /workspaces/users/{uuid}/{user} [put]
func (oh *workspaceHandler) UpdateWorkspaceUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
workspaceUUID := chi.URLParam(r, "uuid")
currentOwnerPubKey := chi.URLParam(r, "user")
now := time.Now()

workspaceUserUpdate := WorkspaceUserUpdateRequest{}
body, err := io.ReadAll(r.Body)
r.Body.Close()

if err != nil {
logger.Log.Error("[body] %v", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}

err = json.Unmarshal(body, &workspaceUserUpdate)
if err != nil {
logger.Log.Error("[workspaces] %v", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}

if workspaceUserUpdate.WorkspaceUuid == "" && workspaceUserUpdate.OrgUuid != "" {
workspaceUserUpdate.WorkspaceUuid = workspaceUserUpdate.OrgUuid
}

if workspaceUUID == "" {
workspaceUUID = workspaceUserUpdate.WorkspaceUuid
} else if workspaceUserUpdate.WorkspaceUuid != "" && workspaceUserUpdate.WorkspaceUuid != workspaceUUID {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("workspace uuid does not match route")
return
}

newOwnerPubKey := workspaceUserUpdate.NewOwnerPubKey
if newOwnerPubKey == "" {
newOwnerPubKey = workspaceUserUpdate.OwnerPubKey
}

if workspaceUUID == "" || currentOwnerPubKey == "" || newOwnerPubKey == "" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("no uuid, current user pubkey, or new user pubkey")
return
}

if pubKeyFromAuth == "" {
logger.Log.Info("[workspaces] no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

if currentOwnerPubKey == newOwnerPubKey {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("new user pubkey must be different from current user pubkey")
return
}

workspace := oh.db.GetWorkspaceByUuid(workspaceUUID)

if currentOwnerPubKey == workspace.OwnerPubKey || newOwnerPubKey == workspace.OwnerPubKey {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("Cannot update workspace admin as a user")
return
}

if pubKeyFromAuth == currentOwnerPubKey || pubKeyFromAuth == newOwnerPubKey {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("Cannot update userself as a user")
return
}

hasRole := oh.userHasAccess(pubKeyFromAuth, workspaceUUID, db.UpdateUser)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("Don't have access to update user")
return
}

existingWorkspaceUser := oh.db.GetWorkspaceUser(currentOwnerPubKey, workspaceUUID)
if existingWorkspaceUser.ID == 0 {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("User does not exists in the workspace")
return
}

newUser := oh.db.GetPersonByPubkey(newOwnerPubKey)
if newUser.OwnerPubKey != newOwnerPubKey {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("User doesn't exists in people")
return
}

existingNewWorkspaceUser := oh.db.GetWorkspaceUser(newOwnerPubKey, workspaceUUID)
if existingNewWorkspaceUser.ID != 0 {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("User already exists")
return
}

roles := oh.db.GetUserRoles(workspaceUUID, currentOwnerPubKey)
oh.db.DeleteWorkspaceUser(db.WorkspaceUsersData{
WorkspaceUuid: workspaceUUID,
Person: db.Person{
OwnerPubKey: currentOwnerPubKey,
},
}, workspaceUUID)

updatedWorkspaceUser := db.WorkspaceUsers{
OwnerPubKey: newOwnerPubKey,
WorkspaceUuid: workspaceUUID,
Created: existingWorkspaceUser.Created,
Updated: &now,
}

user := oh.db.CreateWorkspaceUser(updatedWorkspaceUser)

if len(roles) > 0 {
for i := range roles {
roles[i].OwnerPubKey = newOwnerPubKey
roles[i].WorkspaceUuid = workspaceUUID
roles[i].OrgUuid = ""
}
oh.db.CreateUserRoles(roles, workspaceUUID, newOwnerPubKey)
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(user)
}

// GetWorkspaceUsers godoc
//
// @Summary Get Workspace Users
Expand Down
119 changes: 119 additions & 0 deletions handlers/workspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,125 @@ func TestCreateWorkspaceUser(t *testing.T) {
})
}

func TestUpdateWorkspaceUser(t *testing.T) {
teardownSuite := SetupSuite(t)
defer teardownSuite(t)

oHandler := NewWorkspaceHandler(db.TestDB)

admin := db.Person{
Uuid: "update-admin-uuid",
OwnerAlias: "update-admin",
UniqueName: "update_admin",
OwnerPubKey: "update-admin-pubkey",
PriceToMeet: 0,
Description: "update admin",
}
oldUser := db.Person{
Uuid: "update-old-user-uuid",
OwnerAlias: "update-old-user",
UniqueName: "update_old_user",
OwnerPubKey: "update-old-user-pubkey",
PriceToMeet: 0,
Description: "update old user",
}
newUser := db.Person{
Uuid: "update-new-user-uuid",
OwnerAlias: "update-new-user",
UniqueName: "update_new_user",
OwnerPubKey: "update-new-user-pubkey",
PriceToMeet: 0,
Description: "update new user",
}

db.TestDB.CreateOrEditPerson(admin)
db.TestDB.CreateOrEditPerson(oldUser)
db.TestDB.CreateOrEditPerson(newUser)

workspace := db.Workspace{
Uuid: "update-workspace-uuid",
Name: "update_workspace",
OwnerPubKey: admin.OwnerPubKey,
Github: "github",
Website: "website",
Description: "description",
}
db.TestDB.CreateOrEditWorkspace(workspace)

db.TestDB.DeleteWorkspaceUser(db.WorkspaceUsersData{
WorkspaceUuid: workspace.Uuid,
Person: oldUser,
}, workspace.Uuid)
db.TestDB.DeleteWorkspaceUser(db.WorkspaceUsersData{
WorkspaceUuid: workspace.Uuid,
Person: newUser,
}, workspace.Uuid)

oldWorkspaceUser := db.WorkspaceUsers{
OwnerPubKey: oldUser.OwnerPubKey,
WorkspaceUuid: workspace.Uuid,
}
db.TestDB.CreateWorkspaceUser(oldWorkspaceUser)

role := db.WorkspaceUserRoles{
WorkspaceUuid: workspace.Uuid,
OwnerPubKey: oldUser.OwnerPubKey,
Role: db.UpdateUser,
}
db.TestDB.CreateUserRoles([]db.WorkspaceUserRoles{role}, workspace.Uuid, oldUser.OwnerPubKey)

oHandler.userHasAccess = func(pubKeyFromAuth string, uuid string, role string) bool {
return pubKeyFromAuth == admin.OwnerPubKey && uuid == workspace.Uuid && role == db.UpdateUser
}

t.Run("Should test that when an unauthorized user hits the endpoint it returns a 401 error", func(t *testing.T) {
requestBody, _ := json.Marshal(WorkspaceUserUpdateRequest{
OwnerPubKey: newUser.OwnerPubKey,
})
rctx := chi.NewRouteContext()
rctx.URLParams.Add("uuid", workspace.Uuid)
rctx.URLParams.Add("user", oldUser.OwnerPubKey)
req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodPut, "/users/"+workspace.Uuid+"/"+oldUser.OwnerPubKey, bytes.NewReader(requestBody))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(oHandler.UpdateWorkspaceUser).ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code)
})

t.Run("Should test that when the right conditions are met a workspace user can be updated", func(t *testing.T) {
requestBody, _ := json.Marshal(WorkspaceUserUpdateRequest{
OwnerPubKey: newUser.OwnerPubKey,
})
ctx := context.WithValue(context.Background(), auth.ContextKey, admin.OwnerPubKey)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("uuid", workspace.Uuid)
rctx.URLParams.Add("user", oldUser.OwnerPubKey)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodPut, "/users/"+workspace.Uuid+"/"+oldUser.OwnerPubKey, bytes.NewReader(requestBody))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(oHandler.UpdateWorkspaceUser).ServeHTTP(rr, req)

updatedWorkspaceUser := db.TestDB.GetWorkspaceUser(newUser.OwnerPubKey, workspace.Uuid)
deletedWorkspaceUser := db.TestDB.GetWorkspaceUser(oldUser.OwnerPubKey, workspace.Uuid)
updatedRoles := db.TestDB.GetUserRoles(workspace.Uuid, newUser.OwnerPubKey)
deletedRoles := db.TestDB.GetUserRoles(workspace.Uuid, oldUser.OwnerPubKey)

assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, newUser.OwnerPubKey, updatedWorkspaceUser.OwnerPubKey)
assert.Equal(t, uint(0), deletedWorkspaceUser.ID)
assert.Equal(t, 1, len(updatedRoles))
assert.Equal(t, db.UpdateUser, updatedRoles[0].Role)
assert.Equal(t, 0, len(deletedRoles))
})
}

func TestGetWorkspaceUsers(t *testing.T) {

}
Expand Down
58 changes: 57 additions & 1 deletion mocks/Database.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions routes/workspace_routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func TestWorkspaceRoutes(t *testing.T) {

workspaceRouter.Get("/users/{uuid}", MockHandler(t, http.StatusOK, nil))
workspaceRouter.Get("/users/{uuid}/count", MockHandler(t, http.StatusOK, nil))
workspaceRouter.Put("/users/{uuid}/{user}", MockHandler(t, http.StatusOK, nil))
workspaceRouter.Post("/users/role/{uuid}/{user}", MockHandler(t, http.StatusOK, nil))

workspaceRouter.Get("/bounties/{uuid}", MockHandler(t, http.StatusOK, nil))
Expand Down Expand Up @@ -220,6 +221,13 @@ func TestWorkspaceRoutes(t *testing.T) {
path: "/workspaces/users/123e4567-e89b-12d3-a456-426614174000/count",
expectedStatus: http.StatusOK,
},
{
name: "Update Workspace User",
method: "PUT",
path: "/workspaces/users/123e4567-e89b-12d3-a456-426614174000/user123",
body: map[string]interface{}{"owner_pubkey": "user456"},
expectedStatus: http.StatusOK,
},
{
name: "Get Workspace Budget",
method: "GET",
Expand Down
1 change: 1 addition & 0 deletions routes/workspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func WorkspaceRoutes() chi.Router {

r.Post("/", workspaceHandlers.CreateOrEditWorkspace)
r.Post("/users/{uuid}", workspaceHandlers.CreateWorkspaceUser)
r.Put("/users/{uuid}/{user}", workspaceHandlers.UpdateWorkspaceUser)
r.Delete("/users/{uuid}", handlers.DeleteWorkspaceUser)
r.Post("/users/role/{uuid}/{user}", workspaceHandlers.AddUserRoles)

Expand Down