Skip to content

Commit 5d63271

Browse files
committed
Improve Space permissions
Closes out loopholes that allowed managers to kick owners.
1 parent 09635b6 commit 5d63271

File tree

30 files changed

+1016
-878
lines changed

30 files changed

+1016
-878
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ Space view.
5858

5959
## Latest Release
6060

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

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

6565
## OS support
6666

domain/category/endpoint.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,11 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
504504
fetch := category.FetchSpaceModel{}
505505

506506
// get space categories visible to user
507-
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
508-
if err != nil && err != sql.ErrNoRows {
507+
var cat []category.Category
508+
var err error
509+
510+
cat, err = h.Store.Category.GetBySpace(ctx, spaceID)
511+
if err != nil {
509512
h.Runtime.Log.Error("get space categories visible to user failed", err)
510513
response.WriteServerError(w, method, err)
511514
return
@@ -527,7 +530,7 @@ func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
527530

528531
// get category membership records
529532
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
530-
if err != nil && err != sql.ErrNoRows {
533+
if err != nil {
531534
h.Runtime.Log.Error("get document category membership for space", err)
532535
response.WriteServerError(w, method, err)
533536
return

domain/organization/endpoint.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
5151
return
5252
}
5353

54+
org.StripSecrets()
55+
5456
response.WriteJSON(w, org)
5557
}
5658

domain/organization/store.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ type Store struct {
3434
func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (err error) {
3535
_, 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
3636
o.RefID, o.Company, o.Title, o.Message, strings.ToLower(o.Domain),
37-
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags, o.Subscription, o.Created, o.Revised)
37+
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags,
38+
o.Subscription, o.Created, o.Revised)
3839

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

46-
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
47+
// GetOrganization returns the Organization record from the organization database table with the given id.
4748
func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
4849
err = s.Runtime.Db.Get(&org, s.Bind(`SELECT id, c_refid AS refid,
4950
c_title AS title, c_message AS message, c_domain AS domain,
5051
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
5152
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
52-
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
53+
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
54+
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
5355
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
5456
FROM dmz_org
5557
WHERE c_refid=?`),
@@ -80,7 +82,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
8082
c_title AS title, c_message AS message, c_domain AS domain,
8183
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
8284
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
83-
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
85+
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
86+
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
8487
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
8588
FROM dmz_org
8689
WHERE c_domain=? AND c_active=true`),
@@ -95,7 +98,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
9598
c_title AS title, c_message AS message, c_domain AS domain,
9699
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
97100
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
98-
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
101+
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
102+
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
99103
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
100104
FROM dmz_org
101105
WHERE c_domain='' AND c_active=true`))

domain/permission/endpoint.go

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,6 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
120120
return
121121
}
122122

123-
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
124-
me := false
125-
hasEveryoneRole := false
126-
roleCount := 0
127-
128123
// Permissions can be assigned to both groups and individual users.
129124
// Pre-fetch users with group membership to help us work out
130125
// if user belongs to a group with permissions.
@@ -136,6 +131,18 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
136131
return
137132
}
138133

134+
// url is sent in 'space shared with you' invitation emails.
135+
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
136+
// me tracks if the user who changed permissions, also has some space permissions.
137+
me := false
138+
// hasEveryRole tracks if "everyone" has been give access to space.
139+
hasEveryoneRole := false
140+
// hasOwner tracks is at least one person or user group has been marked as space owner.
141+
hasOwner := false
142+
// roleCount tracks the number of permission records created for this space.
143+
// It's used to determine if space has multiple participants, see below.
144+
roleCount := 0
145+
139146
for _, perm := range model.Permissions {
140147
perm.OrgID = ctx.OrgID
141148
perm.SpaceID = id
@@ -154,6 +161,12 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
154161
me = true
155162
}
156163

164+
// Detect is we have at least one space owner permission.
165+
// Result used below to prevent lock-outs.
166+
if hasOwner == false && perm.SpaceOwner {
167+
hasOwner = true
168+
}
169+
157170
// Only persist if there is a role!
158171
if permission.HasAnyPermission(perm) {
159172
// identify publically shared spaces
@@ -209,24 +222,49 @@ func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
209222
}
210223
}
211224

212-
// Do we need to ensure permissions for space owner when shared?
213-
if !me {
225+
// Catch and prevent lock-outs so we don't have
226+
// zombie spaces that nobody can access.
227+
if len(model.Permissions) == 0 {
228+
// When no permissions are assigned we
229+
// default to current user as being owner and viewer.
214230
perm := permission.Permission{}
215231
perm.OrgID = ctx.OrgID
216232
perm.Who = permission.UserPermission
217233
perm.WhoID = ctx.UserID
218234
perm.Scope = permission.ScopeRow
219235
perm.Location = permission.LocationSpace
220236
perm.RefID = id
221-
perm.Action = "" // we send array for actions below
222-
223-
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView, permission.SpaceManage)
237+
perm.Action = "" // we send allowable actions in function call...
238+
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
224239
if err != nil {
225240
ctx.Transaction.Rollback()
226241
response.WriteServerError(w, method, err)
227242
h.Runtime.Log.Error(method, err)
228243
return
229244
}
245+
} else {
246+
// So we have permissions but we must check for at least one space owner.
247+
if !hasOwner {
248+
// So we have no space owner, make current user the owner
249+
// if we have no permssions thus far.
250+
if !me {
251+
perm := permission.Permission{}
252+
perm.OrgID = ctx.OrgID
253+
perm.Who = permission.UserPermission
254+
perm.WhoID = ctx.UserID
255+
perm.Scope = permission.ScopeRow
256+
perm.Location = permission.LocationSpace
257+
perm.RefID = id
258+
perm.Action = "" // we send allowable actions in function call...
259+
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceView)
260+
if err != nil {
261+
ctx.Transaction.Rollback()
262+
response.WriteServerError(w, method, err)
263+
h.Runtime.Log.Error(method, err)
264+
return
265+
}
266+
}
267+
}
230268
}
231269

232270
// Mark up space type as either public, private or restricted access.

domain/space/store.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,35 @@ func (s Store) GetViewable(ctx domain.RequestContext) (sp []space.Space, err err
116116
return
117117
}
118118

119-
// GetAll for admin users!
120-
func (s Store) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
121-
qry := s.Bind(`SELECT id, c_refid AS refid,
119+
// AdminList returns all shared spaces and orphaned spaces that have no owner.
120+
func (s Store) AdminList(ctx domain.RequestContext) (sp []space.Space, err error) {
121+
qry := s.Bind(`
122+
SELECT id, c_refid AS refid,
122123
c_name AS name, c_orgid AS orgid, c_userid AS userid,
123124
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
124125
c_created AS created, c_revised AS revised
125-
FROM dmz_space
126-
WHERE c_orgid=?
127-
ORDER BY c_name`)
128-
129-
err = s.Runtime.Db.Select(&sp, qry, ctx.OrgID)
130-
126+
FROM dmz_space
127+
WHERE c_orgid=? AND (c_type=? OR c_type=?)
128+
UNION ALL
129+
SELECT id, c_refid AS refid,
130+
c_name AS name, c_orgid AS orgid, c_userid AS userid,
131+
c_type AS type, c_lifecycle AS lifecycle, c_likes AS likes,
132+
c_created AS created, c_revised AS revised
133+
FROM dmz_space
134+
WHERE c_orgid=? AND (c_type=? OR c_type=?) AND c_refid NOT IN
135+
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_action='own')
136+
ORDER BY name`)
137+
138+
err = s.Runtime.Db.Select(&sp, qry,
139+
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
140+
ctx.OrgID, space.ScopePublic, space.ScopeRestricted,
141+
ctx.OrgID)
131142
if err == sql.ErrNoRows {
132143
err = nil
133144
sp = []space.Space{}
134145
}
135146
if err != nil {
136-
err = errors.Wrap(err, fmt.Sprintf("failed space.GetAll org %s", ctx.OrgID))
147+
err = errors.Wrap(err, fmt.Sprintf("failed space.AdminList org %s", ctx.OrgID))
137148
}
138149

139150
return

domain/store/storer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ type SpaceStorer interface {
5959
Get(ctx domain.RequestContext, id string) (sp space.Space, err error)
6060
PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error)
6161
GetViewable(ctx domain.RequestContext) (sp []space.Space, err error)
62-
GetAll(ctx domain.RequestContext) (sp []space.Space, err error)
6362
Update(ctx domain.RequestContext, sp space.Space) (err error)
6463
Delete(ctx domain.RequestContext, id string) (rows int64, err error)
64+
AdminList(ctx domain.RequestContext) (sp []space.Space, err error)
6565
}
6666

6767
// CategoryStorer defines required methods for category and category membership management

edition/community.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ func main() {
4141
rt.Product = domain.Product{}
4242
rt.Product.Major = "1"
4343
rt.Product.Minor = "76"
44-
rt.Product.Patch = "0"
45-
rt.Product.Revision = 181113144232
44+
rt.Product.Patch = "1"
45+
rt.Product.Revision = 181116171324
4646
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
4747
rt.Product.Edition = domain.CommunityEdition
4848
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)

0 commit comments

Comments
 (0)