Skip to content

Commit

Permalink
Improve Space permissions
Browse files Browse the repository at this point in the history
Closes out loopholes that allowed managers to kick owners.
  • Loading branch information
mcmatts committed Nov 16, 2018
1 parent 09635b6 commit 5d63271
Show file tree
Hide file tree
Showing 30 changed files with 1,016 additions and 878 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Space view.

## Latest Release

[Community Edition: v1.76.0](https://github.com/documize/community/releases)
[Community Edition: v1.76.1](https://github.com/documize/community/releases)

[Enterprise Edition: v1.76.0](https://documize.com/downloads)
[Enterprise Edition: v1.76.1](https://documize.com/downloads)

## OS support

Expand Down
9 changes: 6 additions & 3 deletions domain/category/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,11 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
fetch := category.FetchSpaceModel{}

// get space categories visible to user
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
var cat []category.Category
var err error

cat, err = h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil {
h.Runtime.Log.Error("get space categories visible to user failed", err)
response.WriteServerError(w, method, err)
return
Expand All @@ -527,7 +530,7 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {

// get category membership records
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
if err != nil {
h.Runtime.Log.Error("get document category membership for space", err)
response.WriteServerError(w, method, err)
return
Expand Down
2 changes: 2 additions & 0 deletions domain/organization/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return
}

org.StripSecrets()

response.WriteJSON(w, org)
}

Expand Down
14 changes: 9 additions & 5 deletions domain/organization/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ type Store struct {
func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_sub, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
o.RefID, o.Company, o.Title, o.Message, strings.ToLower(o.Domain),
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags, o.Subscription, o.Created, o.Revised)
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags,
o.Subscription, o.Created, o.Revised)

if err != nil {
err = errors.Wrap(err, "unable to execute insert for org")
Expand All @@ -43,13 +44,14 @@ func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (e
return nil
}

// GetOrganization returns the Organization reocrod from the organization database table with the given id.
// GetOrganization returns the Organization record from the organization database table with the given id.
func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
err = s.Runtime.Db.Get(&org, s.Bind(`SELECT id, c_refid AS refid,
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_refid=?`),
Expand Down Expand Up @@ -80,7 +82,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_domain=? AND c_active=true`),
Expand All @@ -95,7 +98,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_domain='' AND c_active=true`))
Expand Down
58 changes: 48 additions & 10 deletions domain/permission/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,6 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
return
}

url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
me := false
hasEveryoneRole := false
roleCount := 0

// Permissions can be assigned to both groups and individual users.
// Pre-fetch users with group membership to help us work out
// if user belongs to a group with permissions.
Expand All @@ -136,6 +131,18 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
return
}

// url is sent in 'space shared with you' invitation emails.
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
// me tracks if the user who changed permissions, also has some space permissions.
me := false
// hasEveryRole tracks if "everyone" has been give access to space.
hasEveryoneRole := false
// hasOwner tracks is at least one person or user group has been marked as space owner.
hasOwner := false
// roleCount tracks the number of permission records created for this space.
// It's used to determine if space has multiple participants, see below.
roleCount := 0

for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID
perm.SpaceID = id
Expand All @@ -154,6 +161,12 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
me = true
}

// Detect is we have at least one space owner permission.
// Result used below to prevent lock-outs.
if hasOwner == false && perm.SpaceOwner {
hasOwner = true
}

// Only persist if there is a role!
if permission.HasAnyPermission(perm) {
// identify publically shared spaces
Expand Down Expand Up @@ -209,24 +222,49 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
}
}

// Do we need to ensure permissions for space owner when shared?
if !me {
// Catch and prevent lock-outs so we don't have
// zombie spaces that nobody can access.
if len(model.Permissions) == 0 {
// When no permissions are assigned we
// default to current user as being owner and viewer.
perm := permission.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = permission.UserPermission
perm.WhoID = ctx.UserID
perm.Scope = permission.ScopeRow
perm.Location = permission.LocationSpace
perm.RefID = id
perm.Action = "" // we send array for actions below

err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView, permission.SpaceManage)
perm.Action = "" // we send allowable actions in function call...
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
} else {
// So we have permissions but we must check for at least one space owner.
if !hasOwner {
// So we have no space owner, make current user the owner
// if we have no permssions thus far.
if !me {
perm := permission.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = permission.UserPermission
perm.WhoID = ctx.UserID
perm.Scope = permission.ScopeRow
perm.Location = permission.LocationSpace
perm.RefID = id
perm.Action = "" // we send allowable actions in function call...
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
}
}

// Mark up space type as either public, private or restricted access.
Expand Down
31 changes: 21 additions & 10 deletions domain/space/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,35 @@ func (s Store) GetViewable(ctx domain.RequestContext) (sp []space.Space, err err
return
}

// GetAll for admin users!
func (s Store) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
qry := s.Bind(`SELECT id, c_refid AS refid,
// AdminList returns all shared spaces and orphaned spaces that have no owner.
func (s Store) AdminList(ctx domain.RequestContext) (sp []space.Space, err error) {
qry := s.Bind(`
SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_created AS created, c_revised AS revised
FROM dmz_space
WHERE c_orgid=?
ORDER BY c_name`)

err = s.Runtime.Db.Select(&sp, qry, ctx.OrgID)

FROM dmz_space
WHERE c_orgid=? AND (c_type=? OR c_type=?)
UNION ALL
SELECT id, c_refid AS refid,
c_name AS name, c_orgid AS orgid, c_userid AS userid,
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
c_created AS created, c_revised AS revised
FROM dmz_space
WHERE c_orgid=? AND (c_type=? OR c_type=?) AND c_refid NOT IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_action='own')
ORDER BY name`)

err = s.Runtime.Db.Select(&sp, qry,
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
sp = []space.Space{}
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed space.GetAll org %s", ctx.OrgID))
err = errors.Wrap(err, fmt.Sprintf("failed space.AdminList org %s", ctx.OrgID))
}

return
Expand Down
2 changes: 1 addition & 1 deletion domain/store/storer.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ type SpaceStorer interface {
Get(ctx domain.RequestContext, id string) (sp space.Space, err error)
PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error)
GetViewable(ctx domain.RequestContext) (sp []space.Space, err error)
GetAll(ctx domain.RequestContext) (sp []space.Space, err error)
Update(ctx domain.RequestContext, sp space.Space) (err error)
Delete(ctx domain.RequestContext, id string) (rows int64, err error)
AdminList(ctx domain.RequestContext) (sp []space.Space, err error)
}

// CategoryStorer defines required methods for category and category membership management
Expand Down
4 changes: 2 additions & 2 deletions edition/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func main() {
rt.Product = domain.Product{}
rt.Product.Major = "1"
rt.Product.Minor = "76"
rt.Product.Patch = "0"
rt.Product.Revision = 181113144232
rt.Product.Patch = "1"
rt.Product.Revision = 181116171324
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = domain.CommunityEdition
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
Expand Down
Loading

0 comments on commit 5d63271

Please sign in to comment.