-
Notifications
You must be signed in to change notification settings - Fork 899
feat: Add initial AuthzQuerier implementation #5919
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
7d0fad4
efe7f93
923219a
f97ca2a
ad6ff52
0e3b9ff
feb7689
083bcf2
161842d
11983ab
ab9c049
04e32bc
21d0f97
76a490e
fa399d6
cb9a2c5
9aa7835
8f6265b
bfa91c1
13710c6
467646d
32c8af1
11ef507
b08fc44
69a6346
4967fe6
6a7b053
fc992cd
d599753
0ce75c6
357b05d
6bb2e1c
8a8ce06
300f6dc
9f7d276
d37379d
6cc14b4
2107b74
53f7a5d
44ca906
73655ab
0d6f6a0
32a9e12
85ff5f1
e152d5f
ef1deb5
4848481
b583a1e
75747f5
add77c6
4357a3c
9dbc6bf
c285f6f
58261fe
a4a2994
874e9da
d878e71
10ac765
e353c4d
db647ba
f45a170
b4beb38
d9d23b6
8780e4e
e6d5c2f
672b2e0
5a0e5a2
016c56d
e086e51
390a284
53fcf79
0add01a
6191561
e8ab762
837f66a
88d422f
a32b4f3
d3affdc
338e300
f5c4040
a7899cf
0da03c6
4415b6b
fb8973c
6763fbf
d1b948d
13a4fab
607e428
592a62b
102af8a
d2b1f41
b6afc2a
d1cfa73
b7cd5a5
f34c61b
e53d709
cb4d92f
13a8445
e1ce04e
7fde8fb
7ba3482
cf763cb
64e80fb
432a261
8134d1b
29e7c46
a37fead
2e435cf
792cbb6
1336e28
0923780
92f89ec
acae52b
764b0a0
0cee453
d1e3214
e799713
cbb4502
83a31cb
9010ad7
912c97a
a3f67bb
7d31209
2c906e5
5e92648
04cce68
712c0f4
8f92a77
3df9848
90a9d87
8b39d7e
a621743
2c002bd
cbd5cb4
6fed479
5928c37
46b8366
21a6f6a
d6810de
889b650
72ed503
94ff5ef
38a90de
c962897
62e3fa0
a0725b9
a4c4489
567cfa4
91910af
dce10b5
58b71f9
833bbc2
fcfdb4e
8858fd3
64e0f8c
9d6ab90
1821dcb
7c9f686
eda4e0a
073aa2c
4fe26e9
13f1c9f
ba172ea
509ebdc
802272b
57cde94
4daa878
4608462
2767264
fc3ae4b
bf653b6
ca68db2
f1f05cc
eb38c0d
b96bb21
0a061be
8295eb3
c2bc20e
3bd3e89
052c531
6aa55ac
72d0a4e
4c68562
fdfdd73
c902715
f5dbd3e
97ad3df
b369c99
03d42d3
69d1aa3
3861a43
9e7ff9a
9dc357e
ad6ad36
0985060
d4e1124
4e6b43f
22e1057
c5346ad
7a14b64
b89b430
21532a6
6a7970f
399241a
924ef9c
2cf0fb2
d1bb7cf
ef97e4b
951d74f
2cf1cad
a9f2581
832d91a
0ddee07
cc76887
002f354
039e1e2
b509b8f
524394f
f666e13
1a97843
4b292e2
bebe638
f99c778
84bc12f
c5e69fa
a93c2d5
f7023a4
035609b
bbe4f18
f0bbaaf
51a2dae
00955e0
2289f4d
106d58b
2724dfd
2c34f6d
c54afc5
d282e9c
7334046
3dbbc71
cd6096f
eb2497a
d2c7a1f
99fa810
1dfa287
57ab200
306c591
f39cee0
2ed5588
c09b077
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,9 @@ package authzquery | |
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
|
||
"cdr.dev/slog" | ||
|
||
"golang.org/x/xerrors" | ||
|
||
|
@@ -17,20 +20,51 @@ var ( | |
// NoActorError wraps ErrNoRows for the api to return a 404. This is the correct | ||
// response when the user is not authorized. | ||
NoActorError = xerrors.Errorf("no authorization actor in context: %w", sql.ErrNoRows) | ||
// TODO: Log this error every time it occurs. | ||
NotAuthorizedError = xerrors.Errorf("unauthorized: %w", sql.ErrNoRows) | ||
) | ||
|
||
// NotAuthorizedError is a sentinal error that unwraps to sql.ErrNoRows. | ||
// This allows the internal error to be read by the caller if needed. Otherwise | ||
// it will be handled as a 404. | ||
type NotAuthorizedError struct { | ||
Err error | ||
} | ||
|
||
func (e NotAuthorizedError) Error() string { | ||
return fmt.Sprintf("unauthorized: %s", e.Err.Error()) | ||
} | ||
|
||
// Unwrap will always unwrap to a sql.ErrNoRows so the API returns a 404. | ||
// So 'errors.Is(err, sql.ErrNoRows)' will always be true. | ||
func (e NotAuthorizedError) Unwrap() error { | ||
return sql.ErrNoRows | ||
} | ||
|
||
func LogNotAuthorizedError(ctx context.Context, logger slog.Logger, err error) error { | ||
// Only log the errors if it is an UnauthorizedError error. | ||
internalError := new(rbac.UnauthorizedError) | ||
if err != nil && xerrors.As(err, internalError) { | ||
logger.Debug(ctx, "unauthorized", | ||
slog.F("internal", internalError.Internal()), | ||
slog.F("input", internalError.Input()), | ||
slog.Error(err), | ||
) | ||
} | ||
return NotAuthorizedError{ | ||
Err: err, | ||
} | ||
} | ||
|
||
func authorizedInsert[ArgumentType any, | ||
Insert func(ctx context.Context, arg ArgumentType) error]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
object rbac.Objecter, | ||
insertFunc Insert) Insert { | ||
|
||
return func(ctx context.Context, arg ArgumentType) error { | ||
_, err := authorizedInsertWithReturn(authorizer, action, object, func(ctx context.Context, arg ArgumentType) (rbac.Objecter, error) { | ||
_, err := authorizedInsertWithReturn(logger, authorizer, action, object, func(ctx context.Context, arg ArgumentType) (rbac.Objecter, error) { | ||
return rbac.Object{}, insertFunc(ctx, arg) | ||
})(ctx, arg) | ||
return err | ||
|
@@ -40,6 +74,7 @@ func authorizedInsert[ArgumentType any, | |
func authorizedInsertWithReturn[ObjectType any, ArgumentType any, | ||
Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error)]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
object rbac.Objecter, | ||
|
@@ -55,7 +90,7 @@ func authorizedInsertWithReturn[ObjectType any, ArgumentType any, | |
// Authorize the action | ||
err = authorizer.Authorize(ctx, act, action, object.RBACObject()) | ||
if err != nil { | ||
return empty, NotAuthorizedError | ||
return empty, LogNotAuthorizedError(ctx, logger, err) | ||
} | ||
|
||
// Insert the database object | ||
|
@@ -67,11 +102,12 @@ func authorizedDelete[ObjectType rbac.Objecter, ArgumentType any, | |
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), | ||
Delete func(ctx context.Context, arg ArgumentType) error]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
fetchFunc Fetch, | ||
deleteFunc Delete) Delete { | ||
|
||
return authorizedFetchAndExec(authorizer, | ||
return authorizedFetchAndExec(logger, authorizer, | ||
rbac.ActionDelete, fetchFunc, deleteFunc) | ||
} | ||
|
||
|
@@ -80,23 +116,25 @@ func authorizedUpdateWithReturn[ObjectType rbac.Objecter, | |
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), | ||
UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error)]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
fetchFunc Fetch, | ||
updateQuery UpdateQuery) UpdateQuery { | ||
|
||
return authorizedFetchAndQuery(authorizer, rbac.ActionUpdate, fetchFunc, updateQuery) | ||
return authorizedFetchAndQuery(logger, authorizer, rbac.ActionUpdate, fetchFunc, updateQuery) | ||
} | ||
|
||
func authorizedUpdate[ObjectType rbac.Objecter, | ||
ArgumentType any, | ||
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), | ||
Exec func(ctx context.Context, arg ArgumentType) error]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
fetchFunc Fetch, | ||
updateExec Exec) Exec { | ||
|
||
return authorizedFetchAndExec(authorizer, rbac.ActionUpdate, fetchFunc, updateExec) | ||
return authorizedFetchAndExec(logger, authorizer, rbac.ActionUpdate, fetchFunc, updateExec) | ||
} | ||
|
||
// authorizedFetchAndExecWithConverter uses authorizedFetchAndQueryWithConverter but | ||
|
@@ -107,12 +145,13 @@ func authorizedFetchAndExec[ObjectType rbac.Objecter, | |
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), | ||
Exec func(ctx context.Context, arg ArgumentType) error]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
fetchFunc Fetch, | ||
execFunc Exec) Exec { | ||
|
||
f := authorizedFetchAndQuery(authorizer, action, fetchFunc, func(ctx context.Context, arg ArgumentType) (empty ObjectType, err error) { | ||
f := authorizedFetchAndQuery(logger, authorizer, action, fetchFunc, func(ctx context.Context, arg ArgumentType) (empty ObjectType, err error) { | ||
return empty, execFunc(ctx, arg) | ||
}) | ||
return func(ctx context.Context, arg ArgumentType) error { | ||
|
@@ -125,6 +164,7 @@ func authorizedFetchAndQuery[ObjectType rbac.Objecter, ArgumentType any, | |
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error), | ||
Query func(ctx context.Context, arg ArgumentType) (ObjectType, error)]( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still don't understand the difference between a fetch and a query. They have the same signature. The next method is called "fetch" and says, "fetch is a generic function that wraps a database query" suggesting that maybe they are interchangeable? This file is extremely abstract, so it is extremely important that we pick a consistent set of terms, define them tightly, and stick to the definitions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference is a // fetchAndQuery is a generic function that wraps a database fetch and query.
// A query has potential side effects in the database (update, delete, etc).
// The fetch is used to know which rbac object the action should be asserted on
// **before** the query runs. The returns from the fetch are only used to
// assert rbac. The final return of this function comes from the Query function. |
||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
fetchFunc Fetch, | ||
|
@@ -146,7 +186,7 @@ func authorizedFetchAndQuery[ObjectType rbac.Objecter, ArgumentType any, | |
// Authorize the action | ||
err = authorizer.Authorize(ctx, act, action, object.RBACObject()) | ||
if err != nil { | ||
return empty, NotAuthorizedError | ||
return empty, LogNotAuthorizedError(ctx, logger, err) | ||
} | ||
|
||
return queryFunc(ctx, arg) | ||
|
@@ -156,10 +196,11 @@ func authorizedFetchAndQuery[ObjectType rbac.Objecter, ArgumentType any, | |
func authorizedFetch[ObjectType rbac.Objecter, ArgumentType any, | ||
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error)]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
fetchFunc Fetch) Fetch { | ||
|
||
return authorizedQuery(authorizer, rbac.ActionRead, fetchFunc) | ||
return authorizedQuery(logger, authorizer, rbac.ActionRead, fetchFunc) | ||
} | ||
|
||
// authorizedQuery is a generic function that wraps a database | ||
|
@@ -175,6 +216,7 @@ func authorizedFetch[ObjectType rbac.Objecter, ArgumentType any, | |
func authorizedQuery[ArgumentType any, ObjectType rbac.Objecter, | ||
DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error)]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
Emyrk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
f DatabaseFunc) DatabaseFunc { | ||
|
@@ -195,7 +237,7 @@ func authorizedQuery[ArgumentType any, ObjectType rbac.Objecter, | |
// Authorize the action | ||
err = authorizer.Authorize(ctx, act, action, object.RBACObject()) | ||
if err != nil { | ||
return empty, NotAuthorizedError | ||
return empty, LogNotAuthorizedError(ctx, logger, err) | ||
} | ||
|
||
return object, nil | ||
|
@@ -236,6 +278,7 @@ func authorizedFetchSet[ArgumentType any, ObjectType rbac.Objecter, | |
// are predicated on the RBAC permissions of the related Template object. | ||
func authorizedQueryWithRelated[ObjectType any, ArgumentType any, Related rbac.Objecter]( | ||
// Arguments | ||
logger slog.Logger, | ||
authorizer rbac.Authorizer, | ||
action rbac.Action, | ||
relatedFunc func(ObjectType, ArgumentType) (Related, error), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: these lines should be broken at something reasonable like 100 chars
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof, my IDE line is set to 120. We do go over this often too though.