Overhaul default access lists (API)
This commit is contained in:
parent
d2f57b98c1
commit
ffe1c9b652
|
@ -171,7 +171,6 @@ type ComplexityRoot struct {
|
|||
}
|
||||
|
||||
Ticket struct {
|
||||
ACL func(childComplexity int) int
|
||||
Assignees func(childComplexity int) int
|
||||
Authenticity func(childComplexity int) int
|
||||
Body func(childComplexity int) int
|
||||
|
@ -219,6 +218,7 @@ type ComplexityRoot struct {
|
|||
Subscription func(childComplexity int) int
|
||||
Tickets func(childComplexity int, cursor *model1.Cursor) int
|
||||
Updated func(childComplexity int) int
|
||||
Visibility func(childComplexity int) int
|
||||
}
|
||||
|
||||
TrackerACL struct {
|
||||
|
@ -324,7 +324,6 @@ type TicketResolver interface {
|
|||
Assignees(ctx context.Context, obj *model.Ticket) ([]model.Entity, error)
|
||||
Events(ctx context.Context, obj *model.Ticket, cursor *model1.Cursor) (*model.EventCursor, error)
|
||||
Subscription(ctx context.Context, obj *model.Ticket) (*model.TicketSubscription, error)
|
||||
ACL(ctx context.Context, obj *model.Ticket) (model.ACL, error)
|
||||
}
|
||||
type TicketMentionResolver interface {
|
||||
Ticket(ctx context.Context, obj *model.TicketMention) (*model.Ticket, error)
|
||||
|
@ -855,13 +854,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.SubscriptionCursor.Results(childComplexity), true
|
||||
|
||||
case "Ticket.acl":
|
||||
if e.complexity.Ticket.ACL == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Ticket.ACL(childComplexity), true
|
||||
|
||||
case "Ticket.assignees":
|
||||
if e.complexity.Ticket.Assignees == nil {
|
||||
break
|
||||
|
@ -1127,6 +1119,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Tracker.Updated(childComplexity), true
|
||||
|
||||
case "Tracker.visibility":
|
||||
if e.complexity.Tracker.Visibility == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Tracker.Visibility(childComplexity), true
|
||||
|
||||
case "TrackerACL.browse":
|
||||
if e.complexity.TrackerACL.Browse == nil {
|
||||
break
|
||||
|
@ -1481,6 +1480,12 @@ type ExternalUser implements Entity {
|
|||
externalUrl: String
|
||||
}
|
||||
|
||||
enum Visibility {
|
||||
PUBLIC
|
||||
UNLISTED
|
||||
PRIVATE
|
||||
}
|
||||
|
||||
type Tracker {
|
||||
id: Int!
|
||||
created: Time!
|
||||
|
@ -1488,6 +1493,7 @@ type Tracker {
|
|||
owner: Entity! @access(scope: PROFILE, kind: RO)
|
||||
name: String!
|
||||
description: String
|
||||
visibility: Visibility!
|
||||
|
||||
tickets(cursor: Cursor): TicketCursor! @access(scope: TICKETS, kind: RO)
|
||||
labels(cursor: Cursor): LabelCursor!
|
||||
|
@ -1559,10 +1565,6 @@ type Ticket {
|
|||
# If the authenticated user is subscribed to this ticket, this is that
|
||||
# subscription.
|
||||
subscription: TicketSubscription @access(scope: SUBSCRIPTIONS, kind: RO)
|
||||
|
||||
# The access control list entry (or the default ACL) which describes the
|
||||
# authenticated user's permissions with respect to this ticket.
|
||||
acl: ACL
|
||||
}
|
||||
|
||||
interface ACL {
|
||||
|
@ -1800,6 +1802,7 @@ type Query {
|
|||
# ACLs or (2) has implicit access to either by ownership or group membership.
|
||||
trackers(cursor: Cursor): TrackerCursor @access(scope: TRACKERS, kind: RO)
|
||||
|
||||
# Returns a specific tracker by ID.
|
||||
tracker(id: Int!): Tracker @access(scope: TRACKERS, kind: RO)
|
||||
|
||||
# Returns a specific tracker, owned by the authenticated user.
|
||||
|
@ -5692,38 +5695,6 @@ func (ec *executionContext) _Ticket_subscription(ctx context.Context, field grap
|
|||
return ec.marshalOTicketSubscription2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋtodoᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐTicketSubscription(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Ticket_acl(ctx context.Context, field graphql.CollectedField, obj *model.Ticket) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Ticket",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Ticket().ACL(rctx, obj)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(model.ACL)
|
||||
fc.Result = res
|
||||
return ec.marshalOACL2gitᚗsrᚗhtᚋאsircmpwnᚋtodoᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACL(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _TicketCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.TicketCursor) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -6383,6 +6354,41 @@ func (ec *executionContext) _Tracker_description(ctx context.Context, field grap
|
|||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Tracker_visibility(ctx context.Context, field graphql.CollectedField, obj *model.Tracker) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Tracker",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Visibility, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(model.Visibility)
|
||||
fc.Result = res
|
||||
return ec.marshalNVisibility2gitᚗsrᚗhtᚋאsircmpwnᚋtodoᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Tracker_tickets(ctx context.Context, field graphql.CollectedField, obj *model.Tracker) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -10182,17 +10188,6 @@ func (ec *executionContext) _Ticket(ctx context.Context, sel ast.SelectionSet, o
|
|||
res = ec._Ticket_subscription(ctx, field, obj)
|
||||
return res
|
||||
})
|
||||
case "acl":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._Ticket_acl(ctx, field, obj)
|
||||
return res
|
||||
})
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
@ -10395,6 +10390,11 @@ func (ec *executionContext) _Tracker(ctx context.Context, sel ast.SelectionSet,
|
|||
}
|
||||
case "description":
|
||||
out.Values[i] = ec._Tracker_description(ctx, field, obj)
|
||||
case "visibility":
|
||||
out.Values[i] = ec._Tracker_visibility(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
case "tickets":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
|
@ -11688,6 +11688,16 @@ func (ec *executionContext) marshalNVersion2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋtodo
|
|||
return ec._Version(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNVisibility2gitᚗsrᚗhtᚋאsircmpwnᚋtodoᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx context.Context, v interface{}) (model.Visibility, error) {
|
||||
var res model.Visibility
|
||||
err := res.UnmarshalGQL(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNVisibility2gitᚗsrᚗhtᚋאsircmpwnᚋtodoᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx context.Context, sel ast.SelectionSet, v model.Visibility) graphql.Marshaler {
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler {
|
||||
return ec.___Directive(ctx, sel, &v)
|
||||
}
|
||||
|
|
|
@ -343,3 +343,46 @@ func (e *TicketStatus) UnmarshalGQL(v interface{}) error {
|
|||
func (e TicketStatus) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type Visibility string
|
||||
|
||||
const (
|
||||
VisibilityPublic Visibility = "PUBLIC"
|
||||
VisibilityUnlisted Visibility = "UNLISTED"
|
||||
VisibilityPrivate Visibility = "PRIVATE"
|
||||
)
|
||||
|
||||
var AllVisibility = []Visibility{
|
||||
VisibilityPublic,
|
||||
VisibilityUnlisted,
|
||||
VisibilityPrivate,
|
||||
}
|
||||
|
||||
func (e Visibility) IsValid() bool {
|
||||
switch e {
|
||||
case VisibilityPublic, VisibilityUnlisted, VisibilityPrivate:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Visibility) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *Visibility) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = Visibility(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid Visibility", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Visibility) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@ const (
|
|||
)
|
||||
|
||||
type Tracker struct {
|
||||
ID int `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
ID int `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Visibility Visibility `json:"visibility"`
|
||||
|
||||
OwnerID int
|
||||
Access int
|
||||
|
@ -63,6 +64,7 @@ func (t *Tracker) Fields() *database.ModelFields {
|
|||
{ "updated", "updated", &t.Updated },
|
||||
{ "name", "name", &t.Name },
|
||||
{ "description", "description", &t.Description },
|
||||
{ "visibility", "visibility", &t.Visibility },
|
||||
|
||||
// Always fetch:
|
||||
{ "id", "", &t.ID },
|
||||
|
@ -92,7 +94,7 @@ func (t *Tracker) QueryWithCursor(ctx context.Context, runner sq.BaseRunner,
|
|||
tr_ua.permissions,
|
||||
CASE WHEN tr.owner_id = ?
|
||||
THEN ?
|
||||
ELSE tr.default_user_perms
|
||||
ELSE tr.default_access
|
||||
END)`,
|
||||
auser.UserID, ACCESS_ALL).
|
||||
Column(`tr_ua.id`).
|
||||
|
|
|
@ -73,6 +73,12 @@ type ExternalUser implements Entity {
|
|||
externalUrl: String
|
||||
}
|
||||
|
||||
enum Visibility {
|
||||
PUBLIC
|
||||
UNLISTED
|
||||
PRIVATE
|
||||
}
|
||||
|
||||
type Tracker {
|
||||
id: Int!
|
||||
created: Time!
|
||||
|
@ -80,6 +86,7 @@ type Tracker {
|
|||
owner: Entity! @access(scope: PROFILE, kind: RO)
|
||||
name: String!
|
||||
description: String
|
||||
visibility: Visibility!
|
||||
|
||||
tickets(cursor: Cursor): TicketCursor! @access(scope: TICKETS, kind: RO)
|
||||
labels(cursor: Cursor): LabelCursor!
|
||||
|
@ -151,10 +158,6 @@ type Ticket {
|
|||
# If the authenticated user is subscribed to this ticket, this is that
|
||||
# subscription.
|
||||
subscription: TicketSubscription @access(scope: SUBSCRIPTIONS, kind: RO)
|
||||
|
||||
# The access control list entry (or the default ACL) which describes the
|
||||
# authenticated user's permissions with respect to this ticket.
|
||||
acl: ACL
|
||||
}
|
||||
|
||||
interface ACL {
|
||||
|
@ -392,6 +395,7 @@ type Query {
|
|||
# ACLs or (2) has implicit access to either by ownership or group membership.
|
||||
trackers(cursor: Cursor): TrackerCursor @access(scope: TRACKERS, kind: RO)
|
||||
|
||||
# Returns a specific tracker by ID.
|
||||
tracker(id: Int!): Tracker @access(scope: TRACKERS, kind: RO)
|
||||
|
||||
# Returns a specific tracker, owned by the authenticated user.
|
||||
|
|
|
@ -91,6 +91,8 @@ func (r *labelResolver) Tickets(ctx context.Context, obj *model.Label, cursor *c
|
|||
Isolation: 0,
|
||||
ReadOnly: true,
|
||||
}, func(tx *sql.Tx) error {
|
||||
// No authentication necessary: if you have access to the label you
|
||||
// have access to the tickets.
|
||||
ticket := (&model.Ticket{}).As(`tk`)
|
||||
query := database.
|
||||
Select(ctx, ticket).
|
||||
|
@ -345,10 +347,6 @@ func (r *ticketResolver) Subscription(ctx context.Context, obj *model.Ticket) (*
|
|||
return loaders.ForContext(ctx).SubsByTicketIDUnsafe.Load(obj.PKID)
|
||||
}
|
||||
|
||||
func (r *ticketResolver) ACL(ctx context.Context, obj *model.Ticket) (model.ACL, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
func (r *ticketMentionResolver) Ticket(ctx context.Context, obj *model.TicketMention) (*model.Ticket, error) {
|
||||
return loaders.ForContext(ctx).TicketsByID.Load(obj.TicketID)
|
||||
}
|
||||
|
@ -370,10 +368,6 @@ func (r *trackerResolver) Owner(ctx context.Context, obj *model.Tracker) (model.
|
|||
}
|
||||
|
||||
func (r *trackerResolver) Tickets(ctx context.Context, obj *model.Tracker, cursor *coremodel.Cursor) (*model.TicketCursor, error) {
|
||||
if !obj.CanBrowse() {
|
||||
return nil, errors.New("You do not have permission to browse this tracker")
|
||||
}
|
||||
|
||||
if cursor == nil {
|
||||
cursor = coremodel.NewCursor(nil)
|
||||
}
|
||||
|
@ -384,10 +378,23 @@ func (r *trackerResolver) Tickets(ctx context.Context, obj *model.Tracker, curso
|
|||
ReadOnly: true,
|
||||
}, func(tx *sql.Tx) error {
|
||||
ticket := (&model.Ticket{}).As(`tk`)
|
||||
query := database.
|
||||
Select(ctx, ticket).
|
||||
From(`ticket tk`).
|
||||
Where(`tk.tracker_id = ?`, obj.ID)
|
||||
var query sq.SelectBuilder
|
||||
if obj.CanBrowse() {
|
||||
query = database.
|
||||
Select(ctx, ticket).
|
||||
From(`ticket tk`).
|
||||
Where(`tk.tracker_id = ?`, obj.ID)
|
||||
} else {
|
||||
user := auth.ForContext(ctx)
|
||||
query = database.
|
||||
Select(ctx, ticket).
|
||||
From(`ticket tk`).
|
||||
Join(`participant p ON p.user_id = ?`, user.UserID).
|
||||
Where(sq.And{
|
||||
sq.Expr(`tk.tracker_id = ?`, obj.ID),
|
||||
sq.Expr(`tk.submitter_id = p.id`),
|
||||
})
|
||||
}
|
||||
tickets, cursor = ticket.QueryWithCursor(ctx, tx, query, cursor)
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
@ -527,7 +534,7 @@ func (r *userResolver) Trackers(ctx context.Context, obj *model.User, cursor *co
|
|||
sq.Expr(`tr.owner_id = ?`, obj.ID),
|
||||
sq.Or{
|
||||
sq.Expr(`tr.owner_id = ?`, auser.UserID),
|
||||
sq.Expr(`tr.default_user_perms > 0`),
|
||||
sq.Expr(`tr.visibility = 'PUBLIC'`),
|
||||
sq.And{
|
||||
sq.Expr(`ua.user_id = ?`, auser.UserID),
|
||||
sq.Expr(`ua.permissions > 0`),
|
||||
|
|
|
@ -159,7 +159,7 @@ func fetchTrackersByID(ctx context.Context) func(ids []int) ([]*model.Tracker, [
|
|||
ua.permissions,
|
||||
CASE WHEN tr.owner_id = ?
|
||||
THEN ?
|
||||
ELSE tr.default_user_perms
|
||||
ELSE tr.default_access
|
||||
END)`,
|
||||
auser.UserID, model.ACCESS_ALL).
|
||||
Column(`ua.id`).
|
||||
|
@ -167,7 +167,7 @@ func fetchTrackersByID(ctx context.Context) func(ids []int) ([]*model.Tracker, [
|
|||
sq.Expr(`tr.id = ANY(?)`, pq.Array(ids)),
|
||||
sq.Or{
|
||||
sq.Expr(`tr.owner_id = ?`, auser.UserID),
|
||||
sq.Expr(`tr.default_user_perms > 0`),
|
||||
sq.Expr(`tr.visibility != 'PRIVATE'`),
|
||||
sq.And{
|
||||
sq.Expr(`ua.user_id = ?`, auser.UserID),
|
||||
sq.Expr(`ua.permissions > 0`),
|
||||
|
@ -291,12 +291,12 @@ func fetchTrackersByOwnerName(ctx context.Context) func(tuples [][2]string) ([]*
|
|||
ua.permissions,
|
||||
CASE WHEN tr.owner_id = ?
|
||||
THEN ?
|
||||
ELSE tr.default_user_perms
|
||||
ELSE tr.default_access
|
||||
END)`,
|
||||
model.ACCESS_ALL, auser.UserID).
|
||||
Where(sq.Or{
|
||||
sq.Expr(`tr.owner_id = ?`, auser.UserID),
|
||||
sq.Expr(`tr.default_user_perms > 0`),
|
||||
sq.Expr(`tr.visibility != 'PRIVATE'`),
|
||||
sq.And{
|
||||
sq.Expr(`ua.user_id = ?`, auser.UserID),
|
||||
sq.Expr(`ua.permissions > 0`),
|
||||
|
@ -356,7 +356,7 @@ func fetchTicketsByID(ctx context.Context) func(ids []int) ([]*model.Ticket, []e
|
|||
sq.Expr(`ti.id = ANY(?)`, pq.Array(ids)),
|
||||
sq.Or{
|
||||
sq.Expr(`tr.owner_id = ?`, auser.UserID),
|
||||
sq.Expr(`tr.default_user_perms > 0`),
|
||||
sq.Expr(`tr.visibility != 'PRIVATE'`),
|
||||
sq.And{
|
||||
sq.Expr(`ua.user_id = ?`, auser.UserID),
|
||||
sq.Expr(`ua.permissions > 0`),
|
||||
|
@ -568,7 +568,7 @@ func fetchLabelsByID(ctx context.Context) func(ids []int) ([]*model.Label, []err
|
|||
sq.Expr(`l.id = ANY(?)`, pq.Array(ids)),
|
||||
sq.Or{
|
||||
sq.Expr(`tr.owner_id = ?`, auser.UserID),
|
||||
sq.Expr(`tr.default_user_perms > 0`),
|
||||
sq.Expr(`tr.visibility != 'PRIVATE'`),
|
||||
sq.And{
|
||||
sq.Expr(`ua.user_id = ?`, auser.UserID),
|
||||
sq.Expr(`ua.permissions > 0`),
|
||||
|
|
|
@ -19,7 +19,7 @@ def user_trackers_GET(username):
|
|||
trackers = Tracker.query.filter(Tracker.owner_id == user.id)
|
||||
if current_token.user_id != user.id:
|
||||
# TODO: proper ACLs
|
||||
trackers = trackers.filter(Tracker.default_user_perms > 0)
|
||||
trackers = trackers.filter(Tracker.default_access > 0)
|
||||
return paginated_response(Tracker.id, trackers)
|
||||
|
||||
@trackers.route("/api/trackers", methods=["POST"])
|
||||
|
|
Loading…
Reference in New Issue