Skip to content
Merged
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
142 changes: 142 additions & 0 deletions server/action/admin/organisation/user/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package user

// userContext is imported from route.go

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"

"github.com/factly/kavach-server/model"
keto "github.com/factly/kavach-server/util/keto/relationTuple"
"github.com/factly/x/errorx"
"github.com/factly/x/loggerx"
"github.com/factly/x/renderx"
"github.com/factly/x/validationx"
"github.com/go-chi/chi"
)

type invites struct {
// flag to send email or not, default is true
SendEmail bool `json:"send_email"`
Users []invite `json:"users"`
}

type invite struct {
FirstName string `gorm:"column:first_name" json:"first_name" validate:"required"`
LastName string `gorm:"column:last_name" json:"last_name"`
Email string `json:"email" validate:"required"`
Role string `json:"role" validate:"required"`
}

// create - Create organisation user (admin)
// @Summary Create organisation user (admin)
// @Description Create organisation user (admin)
// @Tags AdminOrganisationUser
// @ID add-admin-organisation-user
// @Consume json
// @Produce json
// @Param X-KAVACH-MASTER-KEY header string true "Master Key"
// @Param organisation_id path string true "Organisation ID"
// @Param Invite body invites true "Invite Object"
// @Success 201 {array} userWithPermission
// @Failure 400 {array} string
// @Router /admin/organisations/{organisation_id}/users [post]
func create(w http.ResponseWriter, r *http.Request) {
organisationID := chi.URLParam(r, "organisation_id")
orgID, err := strconv.Atoi(organisationID)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
return
}

// FindOrCreate invitee
req := invites{SendEmail: false}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DecodeError()))
return
}
for _, user := range req.Users {
validationError := validationx.Check(user)
if validationError != nil {
loggerx.Error(errors.New("validation error"))
errorx.Render(w, validationError)
return
}
}

for _, user := range req.Users {
tx := model.DB.WithContext(context.WithValue(r.Context(), userContext, 0)).Begin()
invitee := model.User{
Email: user.Email,
FirstName: user.FirstName,
LastName: user.LastName,
}

err = tx.Where(&model.User{
Email: invitee.Email,
}).First(&invitee).Error

if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DBError()))
return
}

// Check if user already exists in organisation
var totPermissions int64
permission := &model.OrganisationUser{}
permission.OrganisationID = uint(orgID)
permission.UserID = invitee.ID

tx.Model(&model.OrganisationUser{}).Where(permission).Count(&totPermissions)
if totPermissions != 0 {
tx.Rollback()
loggerx.Error(errors.New("user already exists in organisation"))
continue
}

// Create organisation user entry
orgUser := &model.OrganisationUser{
UserID: invitee.ID,
OrganisationID: uint(orgID),
Role: user.Role,
}

// Creating a relation tuple for users in keto api
tuple := &model.KetoRelationTupleWithSubjectID{
KetoSubjectSet: model.KetoSubjectSet{
Namespace: "organisations",
Object: fmt.Sprintf("org:%d", orgID),
Relation: user.Role,
},
SubjectID: fmt.Sprintf("%d", invitee.ID),
}

err = keto.CreateRelationTupleWithSubjectID(tuple)
if err != nil {
tx.Rollback()
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}

// Adding user to organisation
err = tx.Model(&model.OrganisationUser{}).Create(orgUser).Error
if err != nil {
loggerx.Error(err)
tx.Rollback()
errorx.Render(w, errorx.Parser(errorx.DBError()))
return
}

tx.Commit()
}
renderx.JSON(w, http.StatusOK, nil)
}
118 changes: 118 additions & 0 deletions server/action/admin/organisation/user/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package user

import (
"errors"
"fmt"
"net/http"
"strconv"

keto "github.com/factly/kavach-server/util/keto/relationTuple"
"github.com/factly/kavach-server/util/user"

"github.com/factly/kavach-server/model"
"github.com/factly/x/errorx"
"github.com/factly/x/loggerx"
"github.com/factly/x/renderx"
"github.com/go-chi/chi"
)

// delete - Delete organisation user by id (admin)
// @Summary Delete a organisation user (admin)
// @Description Delete organisation user by ID (admin)
// @Tags AdminOrganisationUser
// @ID delete-admin-organisation-user-by-id
// @Param X-KAVACH-MASTER-KEY header string true "Master Key"
// @Param organisation_id path string true "Organisation ID"
// @Param user_id path string true "User ID"
// @Success 200
// @Router /admin/organisations/{organisation_id}/users/{user_id} [delete]
func delete(w http.ResponseWriter, r *http.Request) {
/* Check if record exist */
organisationID := chi.URLParam(r, "organisation_id")
orgID, err := strconv.Atoi(organisationID)

if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
return
}

userID := chi.URLParam(r, "user_id")
uID, err := strconv.Atoi(userID)

if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
return
}

result := &model.OrganisationUser{}

// Check if record exist
err = model.DB.Where(&model.OrganisationUser{
OrganisationID: uint(orgID),
UserID: uint(uID),
}).First(&result).Error

if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.RecordNotFound()))
return
}

// Check if the user to delete is not last owner of organisation
var totalOwners int64
model.DB.Model(&model.OrganisationUser{}).Where(&model.OrganisationUser{
Role: "owner",
OrganisationID: uint(orgID),
}).Count(&totalOwners)

if result.Role == "owner" && totalOwners < 2 {
loggerx.Error(errors.New("cannot delete last user of organisation"))
errorx.Render(w, errorx.Parser(errorx.CannotSaveChanges()))
return
}
err = user.DeleteUserFromOrganisationRoles(uint(orgID), result.UserID)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}
err = user.DeleteUserFromApplications(uint(orgID), result.UserID)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}
err = keto.DeleteRelationTuplesOfSubjectIDInNamespace(namespace, userID, fmt.Sprintf("org:%d", orgID))
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}
err = keto.DeleteRelationTuplesOfSubjectIDInNamespace("applications", userID, fmt.Sprintf("org:%d", orgID))
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}
err = keto.DeleteRelationTuplesOfSubjectIDInNamespace("spaces", userID, fmt.Sprintf("org:%d", orgID))
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
return
}

deletePermission := &model.OrganisationUser{}
deletePermission.ID = result.ID

/* DELETE */
err = model.DB.Delete(&deletePermission).Error
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DBError()))
return
}

renderx.JSON(w, http.StatusOK, nil)
}
99 changes: 99 additions & 0 deletions server/action/admin/organisation/user/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package user

import (
"fmt"
"net/http"
"strconv"

"github.com/factly/kavach-server/model"
keto "github.com/factly/kavach-server/util/keto/relationTuple"
"github.com/factly/x/errorx"
"github.com/factly/x/loggerx"
"github.com/factly/x/renderx"
"github.com/go-chi/chi"
)

// list - Get all organisations users (admin)
// @Summary Show all organisations users (admin)
// @Description Get all organisations users (admin)
// @Tags AdminOrganisationUser
// @ID get-all-admin-organisations-users
// @Produce json
// @Param X-KAVACH-MASTER-KEY header string true "Master Key"
// @Param organisation_id path string true "Organisation ID"
// @Success 200 {array} []userWithPermission
// @Router /admin/organisations/{organisation_id}/users [get]
func list(w http.ResponseWriter, r *http.Request) {
organisationID := chi.URLParam(r, "organisation_id")
orgID, err := strconv.Atoi(organisationID)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
return
}

role := r.URL.Query().Get("role")

var userIDs []string

if role == "owner" || role == "member" {
userIDs, err = keto.ListSubjectsByObjectID(namespace, role, fmt.Sprintf("org:%d", orgID))
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.RecordNotFound()))
return
}
} else {
userIDs, err = keto.ListSubjectsByObjectID(namespace, "", fmt.Sprintf("org:%d", orgID))
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.RecordNotFound()))
return
}
}

result := make([]userWithPermission, 0)
for _, userID := range userIDs {
var user userWithPermission

uID, err := strconv.Atoi(userID)
if err != nil {
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DecodeError()))
return
}

tx := model.DB.Begin()
var userModel model.User
err = tx.Model(&model.User{}).Where(&model.User{
Base: model.Base{
ID: uint(uID),
},
}).Preload("Medium").First(&userModel).Error
if err != nil {
tx.Rollback()
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DBError()))
return
}

var userPermissions model.OrganisationUser
err = tx.Model(&model.OrganisationUser{}).Where(&model.OrganisationUser{
OrganisationID: uint(orgID),
UserID: uint(uID),
}).First(&userPermissions).Error
if err != nil {
tx.Rollback()
loggerx.Error(err)
errorx.Render(w, errorx.Parser(errorx.DBError()))
return
}

user.User = userModel
user.Permission = userPermissions
result = append(result, user)
tx.Commit()
}

renderx.JSON(w, http.StatusOK, result)
}
28 changes: 28 additions & 0 deletions server/action/admin/organisation/user/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package user

import (
"github.com/factly/kavach-server/model"
"github.com/go-chi/chi"
)

type userWithPermission struct {
model.User
Permission model.OrganisationUser `json:"permission"`
}

const namespace string = "organisations"

var userContext model.ContextKey = "organisation_user"

// Router organisation user routes for admin
func Router() chi.Router {
r := chi.NewRouter()

r.Get("/", list)
r.Post("/", create)
r.Route("/{user_id}", func(r chi.Router) {
r.Delete("/", delete)
})

return r
}
Loading