Add FileDiffACLsHook to allow people to hook up repository ACLs.
Review Request #11857 — Created Oct. 18, 2021 and submitted
Information | |
---|---|
david | |
Review Board | |
release-4.0.x | |
|
|
Reviewers | |
reviewboard | |
One common request we've had over the years is to mirror repository
access control into Review Board. This is complicated by a few factors.
Review Board has always used a single account to access the remote
repository, and it caches aggressively to provide reasonable interactive
performance, so we can't just rely on the repository access directly. In
addition, it's not uncommon for repository users/usernames to not match
up with Review Board users/usernames (especially when repositories are
hosted on SaaS services).In order to make this possible, we're adding a new extension hook,
FileDiffACLsHook
. This hook is invoked when a user attempts to access
a review request, and is run for eachDiffSet
/FileDiff
. The result
of this is cached perDiffSet
to keep reasonable performance overall.
People can then create an extension implementing this hook to do
whatever access control they want, whether that's querying the
repository or referencing external config files.
- Implemented a sample extension that queried
p4 protect
information
to do access control for Perforce repositories. This implementation is
visible in the documentation for the hook. - Ran unit tests.
Summary | |
---|---|
Description | From | Last Updated |
---|---|---|
"hosting" services. |
|
|
Each of these will need to be full reference paths, or Sphinx will fail to map and will probably emit … |
|
|
False or True? |
|
|
Can we require **kwargs (or document it at least) to make future expansion possible without breaking things? |
|
|
In 4.0 (maybe late 3.0.x?), we added scmtool_id, which is more stable and will be needed for registries. This should … |
|
|
Two blank lines. |
|
|
Can you place this alphabetically in the hook list? |
|
|
As above, let's add **kwargs to any hook methods. |
|
|
The pattern we use for similar hooks is to return a boolean if we have an explicit result, or None … |
|
|
Shouldn't this be checking results? |
|
|
Blank line between these. |
|
|
Leftover debugging? |
|
|
I saw the comment about letting the hook just take a diffset (I think that's maybe a worthwhile change — … |
|
|
This is a little difficult to read. How about returning the result as a variable, and then checking the variable? |
|
|
E712 comparison to False should be 'if cond is False:' or 'if not cond:' |
![]() |
|
Blank line between these. |
|
|
Should that be extension: "%s" or extension "%s"? I see that the ReviewRequestApprovalHook version has the former, but I wonder … |
|
|
Just to check (and we should explicitly document this everywhere): Do all hooks need to say either True or None … |
|
|
Can we have unit tests that test through the hook mechanism? I feel we'd want to ensure that, for N … |
|
|
E501 line too long (80 > 79 characters) |
![]() |
|
F841 local variable 'diffset' is assigned to but never used |
![]() |
|
Private functions should go after public functions. |
|
|
Private functions should go after public functions. |
|
|
This function is missing an explicit return at the end. That'd result in a None, but docs say to expect … |
|
|
I think the only thing left I'd want to see is a change to this paragraph to hammer in the … |
|
-
-
reviewboard/extensions/hooks.py (Diff revision 1) As written, the
is_accessible
method will be called once for each filediff in each diffset, per the code in review_request.py:for filediff in diffset.files.all(): ic(filediff) for hook in FileDiffACLHook.hooks: try: if not hook.is_accessible(diffset, filediff, user):
I think it would be more flexible, though, if the hook were called once per diffset instead.
For a Perforce example, suppose a company organizes its ACLs based on the first 3 levels in the depotFile path:
//<depot>/<project>/<branch>/...
That is, within a given depot, project, and branch, if a user has access to one file, they have access to all files.
And suppose that a review has a fileset containing 100 filediffs:
//depot/foo/main/f00.c //depot/foo/main/f01.c ... //depot/foo/main/f98.c //depot/foo/main/f99.c
In this case, the hook function would only need to test a single depot path, //depot/foo/main/..., with "p4 protects" instead of testing all 100 paths to get the result.
And in cases where there is no existing ACL pattern that can be used to optimize the tests, the hook method can still iterate over the filediffs in the diffset.
-
-
-
docs/manual/extending/extensions/hooks/filediff-acl-hook.rst (Diff revision 1) Each of these will need to be full reference paths, or Sphinx will fail to map and will probably emit warnings.
-
-
docs/manual/extending/extensions/hooks/filediff-acl-hook.rst (Diff revision 1) Can we require
**kwargs
(or document it at least) to make future expansion possible without breaking things? -
docs/manual/extending/extensions/hooks/filediff-acl-hook.rst (Diff revision 1) In 4.0 (maybe late 3.0.x?), we added
scmtool_id
, which is more stable and will be needed for registries. This should check that and compare against'perforce'
. -
-
docs/manual/extending/index.rst (Diff revision 1) Can you place this alphabetically in the hook list?
-
-
reviewboard/extensions/hooks.py (Diff revision 1) The pattern we use for similar hooks is to return a boolean if we have an explicit result, or
None
if we don't. Can we stick to that here? Make the "I don't know, someone else check" result just beNone
? -
reviewboard/extensions/tests/test_sandbox_hooks.py (Diff revision 1) Shouldn't this be checking results?
-
-
-
reviewboard/reviews/models/review_request.py (Diff revision 1) I saw the comment about letting the hook just take a diffset (I think that's maybe a worthwhile change — will discuss in that comment), so this comment depends on the outcome of that a bit, but...
We should avoid inline functions here. The function gets redefined on every call to the parent function. I think we should instead just move this out into the class.
-
reviewboard/reviews/models/review_request.py (Diff revision 1) This is a little difficult to read. How about returning the result as a variable, and then checking the variable?
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 2 (+452 -6) |
Checks run (1 failed, 1 succeeded)
flake8
-
reviewboard/reviews/models/review_request.py (Diff revision 2) E712 comparison to False should be 'if cond is False:' or 'if not cond:'
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 3 (+452 -6) |
Checks run (2 succeeded)
-
The design looks sound to me. Just a few comments.
-
-
reviewboard/reviews/models/review_request.py (Diff revision 3) Should that be
extension: "%s"
orextension "%s"
?I see that the
ReviewRequestApprovalHook
version has the former, but I wonder if that was a typo back then. -
reviewboard/reviews/models/review_request.py (Diff revision 3) Just to check (and we should explicitly document this everywhere): Do all hooks need to say either
True
orNone
for the result to beTrue
, or do we want to allow anyTrue
to win?ReviewRequestApprovalHook
chains results, so that a hook can do effectivelyif prev_approved and ...
. We don't have that design for this hook (should we?), so there isn't a precedent here.I don't honestly know what the right design is, so this is really just a question and a request to make it crystal-clear in docs/comments, specifically describing what happens with multiple hooks (important to note that hooks can be called in any order).
-
reviewboard/reviews/tests/test_review_request.py (Diff revision 3) Can we have unit tests that test through the hook mechanism? I feel we'd want to ensure that, for N hooks (maybe 2, 3) that we get the right results for a combination of
True
,False
, andNone
results.
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 4 (+606 -8) |
Checks run (1 failed, 1 succeeded)
flake8
-
reviewboard/extensions/tests/test_filediffacl_hook.py (Diff revision 4) E501 line too long (80 > 79 characters)
-
reviewboard/extensions/tests/test_filediffacl_hook.py (Diff revision 4) F841 local variable 'diffset' is assigned to but never used
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 5 (+606 -8) |
Checks run (2 succeeded)
Change Summary:
Use SpyOpReturn instead of call_fake
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 6 (+596 -8) |
Checks run (2 succeeded)
-
All the logic and docs look good to me. Only a couple small things (function ordering, missing
return
) and I'm happy. -
reviewboard/extensions/tests/test_filediffacl_hook.py (Diff revision 6) Private functions should go after public functions.
-
reviewboard/reviews/models/review_request.py (Diff revision 6) Private functions should go after public functions.
-
reviewboard/reviews/models/review_request.py (Diff revision 6) This function is missing an explicit
return
at the end. That'd result in aNone
, but docs say to expectTrue
orFalse
.
Commits: |
|
|||||||||
---|---|---|---|---|---|---|---|---|---|---|
Diff: |
Revision 7 (+600 -8) |
Checks run (2 succeeded)
-
-
docs/manual/extending/extensions/hooks/filediff-acl-hook.rst (Diff revision 7) I think the only thing left I'd want to see is a change to this paragraph to hammer in the expectations of the implementation. Right now, the requirements are all in one big paragraph, and I just know people are going to mentally skip something.
Can we make the "If the user does not have ..." text onward each its own bullet point, like:
The hook is expected to return: * ``True`` if the user does have access to all the files. * ``False`` if the user does not have access to one or more files. * ``None`` if the hook did not check the diff (for example, it might only check a single SCM or hosting service type, and ignore any others) If any hook returns ``False``, the user will not have access to the diff.