Sharing of lists via public links (#94)
This commit is contained in:
@ -22,6 +22,29 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Generic
|
||||
|
||||
// ErrGenericForbidden represents a "UsernameAlreadyExists" kind of error.
|
||||
type ErrGenericForbidden struct{}
|
||||
|
||||
// IsErrGenericForbidden checks if an error is a ErrGenericForbidden.
|
||||
func IsErrGenericForbidden(err error) bool {
|
||||
_, ok := err.(ErrGenericForbidden)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrGenericForbidden) Error() string {
|
||||
return fmt.Sprintf("Forbidden")
|
||||
}
|
||||
|
||||
// ErrorCodeGenericForbidden holds the unique world-error code of this error
|
||||
const ErrorCodeGenericForbidden = 0001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrGenericForbidden) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrorCodeGenericForbidden, Message: "You're not allowed to do this."}
|
||||
}
|
||||
|
||||
// =====================
|
||||
// User Operation Errors
|
||||
// =====================
|
||||
@ -423,6 +446,30 @@ func (err ErrListTitleCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."}
|
||||
}
|
||||
|
||||
// ErrListShareDoesNotExist represents a "ErrListShareDoesNotExist" kind of error. Used if the list share does not exist.
|
||||
type ErrListShareDoesNotExist struct {
|
||||
ID int64
|
||||
Hash string
|
||||
}
|
||||
|
||||
// IsErrListShareDoesNotExist checks if an error is a ErrListShareDoesNotExist.
|
||||
func IsErrListShareDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrListShareDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListShareDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("List share does not exist.")
|
||||
}
|
||||
|
||||
// ErrCodeListShareDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeListShareDoesNotExist = 3006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListShareDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListShareDoesNotExist, Message: "The list share does not exist."}
|
||||
}
|
||||
|
||||
// ================
|
||||
// List task errors
|
||||
// ================
|
||||
|
24
pkg/models/fixtures/link_sharing.yml
Normal file
24
pkg/models/fixtures/link_sharing.yml
Normal file
@ -0,0 +1,24 @@
|
||||
- id: 1
|
||||
hash: test
|
||||
list_id: 1
|
||||
right: 0
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
||||
- id: 2
|
||||
hash: test2
|
||||
list_id: 2
|
||||
right: 1
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
||||
- id: 3
|
||||
hash: test3
|
||||
list_id: 3
|
||||
right: 2
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
@ -198,4 +198,10 @@
|
||||
hex_color: f0f0f0
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 32
|
||||
text: 'task #32'
|
||||
created_by_id: 1
|
||||
list_id: 3
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
|
||||
|
@ -131,6 +131,9 @@ func (l *Label) Delete() (err error) {
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [get]
|
||||
func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return nil, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
u := &User{ID: a.GetID()}
|
||||
|
||||
@ -192,7 +195,18 @@ func getLabelByIDSimple(labelID int64) (*Label, error) {
|
||||
|
||||
// Helper method to get all task ids a user has
|
||||
func getUserTaskIDs(u *User) (taskIDs []int64, err error) {
|
||||
tasks, err := GetTasksByUser("", u, -1, SortTasksByUnsorted, time.Unix(0, 0), time.Unix(0, 0))
|
||||
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser("", u, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tasks, err := getTasksForLists(lists, &taskOptions{
|
||||
startDate: time.Unix(0, 0),
|
||||
endDate: time.Unix(0, 0),
|
||||
sortby: SortTasksByUnsorted,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -39,10 +39,19 @@ func (l *Label) CanRead(a web.Auth) (bool, error) {
|
||||
// CanCreate checks if the user can create a label
|
||||
// Currently a dummy.
|
||||
func (l *Label) CanCreate(a web.Auth) (bool, error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (l *Label) isLabelOwner(a web.Auth) (bool, error) {
|
||||
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
lorig, err := getLabelByIDSimple(l.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -52,6 +61,9 @@ func (l *Label) isLabelOwner(a web.Auth) (bool, error) {
|
||||
|
||||
// Helper method to check if a user can see a specific label
|
||||
func (l *Label) hasAccessToLabel(a web.Auth) (bool, error) {
|
||||
|
||||
// TODO: add an extra check for link share handling
|
||||
|
||||
// Get all tasks
|
||||
taskIDs, err := getUserTaskIDs(&User{ID: a.GetID()})
|
||||
if err != nil {
|
||||
|
207
pkg/models/link_sharing.go
Normal file
207
pkg/models/link_sharing.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/web"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
// SharingType holds the sharing type
|
||||
type SharingType int
|
||||
|
||||
// These consts represent all valid link sharing types
|
||||
const (
|
||||
SharingTypeUnknown SharingType = iota
|
||||
SharingTypeWithoutPassword
|
||||
SharingTypeWithPassword
|
||||
)
|
||||
|
||||
// LinkSharing represents a shared list
|
||||
type LinkSharing struct {
|
||||
// The ID of the shared thing
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"share"`
|
||||
// The public id to get this shared list
|
||||
Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`
|
||||
// The ID of the shared list
|
||||
ListID int64 `xorm:"int(11) not null" json:"-" param:"list"`
|
||||
// The right this list is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
|
||||
Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
List *List `xorm:"-" json:"list" param:"fullist"`
|
||||
|
||||
// The kind of this link. 0 = undefined, 1 = without password, 2 = with password (currently not implemented).
|
||||
SharingType SharingType `xorm:"int(11) INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// The user who shared this list
|
||||
SharedBy *User `xorm:"-" json:"shared_by"`
|
||||
SharedByID int64 `xorm:"int(11) INDEX not null" json:"-"`
|
||||
|
||||
// A unix timestamp when this list was shared. You cannot change this value.
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
// A unix timestamp when this share was last updated. You cannot change this value.
|
||||
Updated int64 `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName holds the table name
|
||||
func (LinkSharing) TableName() string {
|
||||
return "link_sharing"
|
||||
}
|
||||
|
||||
// GetID returns the ID of the links sharing object
|
||||
func (share *LinkSharing) GetID() int64 {
|
||||
return share.ID
|
||||
}
|
||||
|
||||
// GetLinkShareFromClaims builds a link sharing object from jwt claims
|
||||
func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error) {
|
||||
share = &LinkSharing{}
|
||||
share.ID = int64(claims["id"].(float64))
|
||||
share.Hash = claims["hash"].(string)
|
||||
share.ListID = int64(claims["listID"].(float64))
|
||||
share.Right = Right(claims["right"].(float64))
|
||||
share.SharedByID = int64(claims["sharedByID"].(float64))
|
||||
return
|
||||
}
|
||||
|
||||
// Create creates a new link share for a given list
|
||||
// @Summary Share a list via link
|
||||
// @Description Share a list via link. The user needs to have write-access to the list to be able do this.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param list path int true "List ID"
|
||||
// @Param label body models.LinkSharing true "The new link share object"
|
||||
// @Success 200 {object} models.LinkSharing "The created link share object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid link share object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to add the list share."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "The list does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares [put]
|
||||
func (share *LinkSharing) Create(a web.Auth) (err error) {
|
||||
share.SharedByID = a.GetID()
|
||||
share.Hash = utils.MakeRandomString(40)
|
||||
_, err = x.Insert(share)
|
||||
return
|
||||
}
|
||||
|
||||
// ReadOne returns one share
|
||||
// @Summary Get one link shares for a list
|
||||
// @Description Returns one link share by its ID.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param list path int true "List ID"
|
||||
// @Param share path int true "Share ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.LinkSharing "The share links"
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "No access to the list"
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Share Link not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares/{share} [get]
|
||||
func (share *LinkSharing) ReadOne() (err error) {
|
||||
exists, err := x.Where("id = ?", share.ID).Get(share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return ErrListShareDoesNotExist{ID: share.ID, Hash: share.Hash}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadAll returns all shares for a given list
|
||||
// @Summary Get all link shares for a list
|
||||
// @Description Returns all link shares which exist for a given list
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param list path int true "List ID"
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search shares by hash."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.LinkSharing "The share links"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares [get]
|
||||
func (share *LinkSharing) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
list := &List{ID: share.ListID}
|
||||
can, err := list.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !can {
|
||||
return nil, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
var shares []*LinkSharing
|
||||
err = x.
|
||||
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&shares)
|
||||
return shares, err
|
||||
}
|
||||
|
||||
// Delete removes a link share
|
||||
// @Summary Remove a link share
|
||||
// @Description Remove a link share. The user needs to have write-access to the list to be able do this.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param list path int true "List ID"
|
||||
// @Param share path int true "Share Link ID"
|
||||
// @Success 200 {object} models.Message "The link was successfully removed."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to remove the link."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Share Link not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares/{share} [delete]
|
||||
func (share *LinkSharing) Delete() (err error) {
|
||||
_, err = x.Where("id = ?", share.ID).Delete(share)
|
||||
return
|
||||
}
|
||||
|
||||
// GetLinkShareByHash returns a link share by hash
|
||||
func GetLinkShareByHash(hash string) (share *LinkSharing, err error) {
|
||||
share = &LinkSharing{}
|
||||
has, err := x.Where("hash = ?", hash).Get(share)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !has {
|
||||
return share, ErrListShareDoesNotExist{Hash: hash}
|
||||
}
|
||||
share.List = &List{ID: share.ListID}
|
||||
return
|
||||
}
|
||||
|
||||
// GetListByShareHash returns a link share by its hash
|
||||
func GetListByShareHash(hash string) (list *List, err error) {
|
||||
share, err := GetLinkShareByHash(hash)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list = &List{ID: share.ListID}
|
||||
err = list.GetSimpleByID()
|
||||
return
|
||||
}
|
61
pkg/models/link_sharing_rights.go
Normal file
61
pkg/models/link_sharing_rights.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
import "code.vikunja.io/web"
|
||||
|
||||
// CanRead implements the read right check for a link share
|
||||
func (share *LinkSharing) CanRead(a web.Auth) (bool, error) {
|
||||
// Don't allow creating link shares if the user itself authenticated with a link share
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
l, err := GetListByShareHash(share.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return l.CanRead(a)
|
||||
}
|
||||
|
||||
// CanDelete implements the delete right check for a link share
|
||||
func (share *LinkSharing) CanDelete(a web.Auth) (bool, error) {
|
||||
return share.canDoLinkShare(a)
|
||||
}
|
||||
|
||||
// CanUpdate implements the update right check for a link share
|
||||
func (share *LinkSharing) CanUpdate(a web.Auth) (bool, error) {
|
||||
return share.canDoLinkShare(a)
|
||||
}
|
||||
|
||||
// CanCreate implements the create right check for a link share
|
||||
func (share *LinkSharing) CanCreate(a web.Auth) (bool, error) {
|
||||
return share.canDoLinkShare(a)
|
||||
}
|
||||
|
||||
func (share *LinkSharing) canDoLinkShare(a web.Auth) (bool, error) {
|
||||
// Don't allow creating link shares if the user itself authenticated with a link share
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
l, err := GetListByShareHash(share.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return l.CanWrite(a)
|
||||
}
|
@ -85,14 +85,26 @@ func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) {
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists [get]
|
||||
func (l *List) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
shareAuth.List = &List{ID: shareAuth.ListID}
|
||||
err := shareAuth.List.GetSimpleByID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lists := []*List{shareAuth.List}
|
||||
err = AddListDetails(lists)
|
||||
return lists, err
|
||||
}
|
||||
|
||||
lists, err := getRawListsForUser(search, &User{ID: a.GetID()}, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add more list details
|
||||
AddListDetails(lists)
|
||||
|
||||
err = AddListDetails(lists)
|
||||
return lists, err
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,13 @@ func (l *List) CanWrite(a web.Auth) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
return originalList.ID == shareAuth.ListID &&
|
||||
(shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), nil
|
||||
}
|
||||
|
||||
// Check if the user is either owner or can write to the list
|
||||
if originalList.isOwner(&User{ID: a.GetID()}) {
|
||||
return true, nil
|
||||
@ -45,6 +52,14 @@ func (l *List) CanRead(a web.Auth) (bool, error) {
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
return l.ID == shareAuth.ListID &&
|
||||
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), nil
|
||||
}
|
||||
|
||||
if l.isOwner(&User{ID: a.GetID()}) {
|
||||
return true, nil
|
||||
}
|
||||
@ -61,7 +76,7 @@ func (l *List) CanDelete(a web.Auth) (bool, error) {
|
||||
return l.IsAdmin(a)
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can update a list
|
||||
// CanCreate checks if the user can create a list
|
||||
func (l *List) CanCreate(a web.Auth) (bool, error) {
|
||||
// A user can create a list if he has write access to the namespace
|
||||
n := &Namespace{ID: l.NamespaceID}
|
||||
@ -76,6 +91,12 @@ func (l *List) IsAdmin(a web.Auth) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
return originalList.ID == shareAuth.ListID && shareAuth.Right == RightAdmin, nil
|
||||
}
|
||||
|
||||
// Check all the things
|
||||
// Check if the user is either owner or can write to the list
|
||||
// Owners are always admins
|
||||
|
@ -22,20 +22,25 @@ import (
|
||||
|
||||
// CanCreate checks if the user can create a new user <-> list relation
|
||||
func (lu *ListUser) CanCreate(a web.Auth) (bool, error) {
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
return l.CanWrite(a)
|
||||
return lu.canDoListUser(a)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a user <-> list relation
|
||||
func (lu *ListUser) CanDelete(a web.Auth) (bool, error) {
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
return l.CanWrite(a)
|
||||
return lu.canDoListUser(a)
|
||||
}
|
||||
|
||||
// CanUpdate checks if the user can update a user <-> list relation
|
||||
func (lu *ListUser) CanUpdate(a web.Auth) (bool, error) {
|
||||
return lu.canDoListUser(a)
|
||||
}
|
||||
|
||||
func (lu *ListUser) canDoListUser(a web.Auth) (bool, error) {
|
||||
// Link shares aren't allowed to do anything
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get the list and check if the user has write access on it
|
||||
l := List{ID: lu.ListID}
|
||||
return l.CanWrite(a)
|
||||
|
@ -48,6 +48,7 @@ func GetTables() []interface{} {
|
||||
&Label{},
|
||||
&LabelTask{},
|
||||
&TaskReminder{},
|
||||
&LinkSharing{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,10 @@ type NamespaceWithLists struct {
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /namespaces [get]
|
||||
func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return nil, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
doer, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -48,12 +48,21 @@ func (n *Namespace) CanDelete(a web.Auth) (bool, error) {
|
||||
|
||||
// CanCreate checks if the user can create a new namespace
|
||||
func (n *Namespace) CanCreate(a web.Auth) (bool, error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// This is currently a dummy function, later on we could imagine global limits etc.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Namespace) checkRight(a web.Auth, rights ...Right) (bool, error) {
|
||||
|
||||
// If the auth is a link share, don't do anything
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get the namespace and check the right
|
||||
err := n.GetSimpleByID()
|
||||
if err != nil {
|
||||
|
@ -221,13 +221,27 @@ func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
|
||||
// @Produce json
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search assignees by their username."
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.User "The assignees"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [get]
|
||||
// @Router /tasks/{taskID}/assignees [get]
|
||||
func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
task, err := GetListSimplByTaskID(la.TaskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
can, err := task.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !can {
|
||||
return nil, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
var taskAssignees []*User
|
||||
err := x.Table("task_assignees").
|
||||
err = x.Table("task_assignees").
|
||||
Select("users.*").
|
||||
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%").
|
||||
|
@ -41,5 +41,5 @@ func canDoTaskAssingee(taskID int64, a web.Auth) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return list.CanCreate(a)
|
||||
return list.CanUpdate(a)
|
||||
}
|
||||
|
@ -317,6 +317,15 @@ func sortTasksForTesting(by SortBy) (tasks []*Task) {
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
},
|
||||
{
|
||||
ID: 32,
|
||||
Text: "task #32",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
ListID: 3,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
},
|
||||
}
|
||||
|
||||
switch by {
|
||||
|
@ -147,17 +147,40 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error)
|
||||
sortby = SortTasksByUnsorted
|
||||
}
|
||||
|
||||
return GetTasksByUser(search, &User{ID: a.GetID()}, page, sortby, time.Unix(t.StartDateSortUnix, 0), time.Unix(t.EndDateSortUnix, 0))
|
||||
}
|
||||
taskopts := &taskOptions{
|
||||
search: search,
|
||||
sortby: sortby,
|
||||
startDate: time.Unix(t.StartDateSortUnix, 0),
|
||||
endDate: time.Unix(t.EndDateSortUnix, 0),
|
||||
}
|
||||
|
||||
//GetTasksByUser returns all tasks for a user
|
||||
func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate time.Time, endDate time.Time) ([]*Task, error) {
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser("", u, page)
|
||||
shareAuth, is := a.(*LinkSharing)
|
||||
if is {
|
||||
shareAuth.List = &List{ID: shareAuth.ListID}
|
||||
err := shareAuth.List.GetSimpleByID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getTasksForLists([]*List{shareAuth.List}, taskopts)
|
||||
}
|
||||
|
||||
// Get all lists for the user
|
||||
lists, err := getRawListsForUser("", &User{ID: a.GetID()}, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getTasksForLists(lists, taskopts)
|
||||
}
|
||||
|
||||
type taskOptions struct {
|
||||
search string
|
||||
sortby SortBy
|
||||
startDate time.Time
|
||||
endDate time.Time
|
||||
}
|
||||
|
||||
func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, err error) {
|
||||
// Get all list IDs and get the tasks
|
||||
var listIDs []int64
|
||||
for _, l := range lists {
|
||||
@ -165,7 +188,7 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate t
|
||||
}
|
||||
|
||||
var orderby string
|
||||
switch sortby {
|
||||
switch opts.sortby {
|
||||
case SortTasksByPriorityDesc:
|
||||
orderby = "priority desc"
|
||||
case SortTasksByPriorityAsc:
|
||||
@ -179,20 +202,20 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate t
|
||||
taskMap := make(map[int64]*Task)
|
||||
|
||||
// Then return all tasks for that lists
|
||||
if startDate.Unix() != 0 || endDate.Unix() != 0 {
|
||||
if opts.startDate.Unix() != 0 || opts.endDate.Unix() != 0 {
|
||||
|
||||
startDateUnix := time.Now().Unix()
|
||||
if startDate.Unix() != 0 {
|
||||
startDateUnix = startDate.Unix()
|
||||
if opts.startDate.Unix() != 0 {
|
||||
startDateUnix = opts.startDate.Unix()
|
||||
}
|
||||
|
||||
endDateUnix := time.Now().Unix()
|
||||
if endDate.Unix() != 0 {
|
||||
endDateUnix = endDate.Unix()
|
||||
if opts.endDate.Unix() != 0 {
|
||||
endDateUnix = opts.endDate.Unix()
|
||||
}
|
||||
|
||||
if err := x.In("list_id", listIDs).
|
||||
Where("text LIKE ?", "%"+search+"%").
|
||||
Where("text LIKE ?", "%"+opts.search+"%").
|
||||
And("((due_date_unix BETWEEN ? AND ?) OR "+
|
||||
"(start_date_unix BETWEEN ? and ?) OR "+
|
||||
"(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix).
|
||||
@ -203,7 +226,7 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate t
|
||||
}
|
||||
} else {
|
||||
if err := x.In("list_id", listIDs).
|
||||
Where("text LIKE ?", "%"+search+"%").
|
||||
Where("text LIKE ?", "%"+opts.search+"%").
|
||||
And("(parent_task_id = 0 OR parent_task_id IS NULL)").
|
||||
OrderBy(orderby).
|
||||
Find(&taskMap); err != nil {
|
||||
@ -211,13 +234,13 @@ func GetTasksByUser(search string, u *User, page int, sortby SortBy, startDate t
|
||||
}
|
||||
}
|
||||
|
||||
tasks, err := addMoreInfoToTasks(taskMap)
|
||||
tasks, err = addMoreInfoToTasks(taskMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Because the list is sorted by id which we don't want (since we're dealing with maps)
|
||||
// we have to manually sort the tasks again here.
|
||||
sortTasks(tasks, sortby)
|
||||
sortTasks(tasks, opts.sortby)
|
||||
|
||||
return tasks, err
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ func (t *Task) CanRead(a web.Auth) (canRead bool, err error) {
|
||||
}
|
||||
|
||||
// A user can read a task if it has access to the list
|
||||
list := &List{ID: t.ListID}
|
||||
return list.CanRead(a)
|
||||
l := &List{ID: t.ListID}
|
||||
return l.CanRead(a)
|
||||
}
|
||||
|
||||
// Helper function to check if a user can do stuff on a list task
|
||||
|
@ -36,6 +36,11 @@ func (tl *TeamList) CanUpdate(a web.Auth) (bool, error) {
|
||||
}
|
||||
|
||||
func (tl *TeamList) canDoTeamList(a web.Auth) (bool, error) {
|
||||
// Link shares aren't allowed to do anything
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
l := List{ID: tl.ListID}
|
||||
return l.IsAdmin(a)
|
||||
}
|
||||
|
@ -32,6 +32,11 @@ func (tm *TeamMember) CanDelete(a web.Auth) (bool, error) {
|
||||
|
||||
// IsAdmin checks if the user is team admin
|
||||
func (tm *TeamMember) IsAdmin(a web.Auth) (bool, error) {
|
||||
// Don't allow anything if we're dealing with a list share here
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// A user can add a member to a team if he is admin of that team
|
||||
exists, err := x.Where("user_id = ? AND team_id = ? AND admin = ?", a.GetID(), tm.TeamID, true).
|
||||
Get(&TeamMember{})
|
||||
|
@ -142,6 +142,10 @@ func (t *Team) ReadOne() (err error) {
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /teams [get]
|
||||
func (t *Team) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return nil, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
all := []*Team{}
|
||||
err := x.Select("teams.*").
|
||||
Table("teams").
|
||||
|
@ -22,6 +22,10 @@ import (
|
||||
|
||||
// CanCreate checks if the user can create a new team
|
||||
func (t *Team) CanCreate(a web.Auth) (bool, error) {
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// This is currently a dummy function, later on we could imagine global limits etc.
|
||||
return true, nil
|
||||
}
|
||||
@ -38,6 +42,11 @@ func (t *Team) CanDelete(a web.Auth) (bool, error) {
|
||||
|
||||
// IsAdmin returns true when the user is admin of a team
|
||||
func (t *Team) IsAdmin(a web.Auth) (bool, error) {
|
||||
// Don't do anything if we're deadling with a link share auth here
|
||||
if _, is := a.(*LinkSharing); is {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if the team exists to be able to return a proper error message if not
|
||||
_, err := GetTeamByID(t.ID)
|
||||
if err != nil {
|
||||
|
@ -189,6 +189,11 @@ func CheckUserCredentials(u *UserLogin) (*User, error) {
|
||||
func GetCurrentUser(c echo.Context) (user *User, err error) {
|
||||
jwtinf := c.Get("user").(*jwt.Token)
|
||||
claims := jwtinf.Claims.(jwt.MapClaims)
|
||||
return GetUserFromClaims(claims)
|
||||
}
|
||||
|
||||
// GetUserFromClaims Returns a new user from jwt claims
|
||||
func GetUserFromClaims(claims jwt.MapClaims) (user *User, err error) {
|
||||
userID, ok := claims["id"].(float64)
|
||||
if !ok {
|
||||
return user, ErrCouldNotGetUserID{}
|
||||
|
Reference in New Issue
Block a user