-
Notifications
You must be signed in to change notification settings - Fork 0
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
Bitwarden access manager #10
Conversation
* Member * Group * Collection * GroupMember * MemberCollectionAccess * GroupCollectionAccess
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.
I reviewed the docstring 😅 Will continue the review next week!
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.
I finally reviewed the full thing now. Overall it looks really nice! I left many comments, but most of them are fairly superficial Python things.
I mostly looked superficially at the implementation now that I did not try to make sense of the diffing logic on a high level, but that is very similar to what we already have for GitHub, so if the data structures are good for Bitwarden, it should work. I’ll do a more high-level review later then, we can focus on the Python things first.
Unrelated to the content of the PR, but you ran into both mypy
and black
and I realize I didn’t document this at all, it would be nice to mention these in the readme or in a CONTRIBUTING.md
.
bitwarden_access_manager.py
Outdated
email: str | ||
type: str | ||
access_all: Optional[bool] | ||
# groups: Tuple[str, ...] |
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.
(Note to self, we need to make a decision on this before merging.)
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.
Following the docstring, the group should go in the member, right? Then this should be uncommented, no?
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.
Following the docstring, the group should go in the member, right? Then this should be uncommented, no?
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.
Yes the group should go there in order to keep track of member assigned group in the toml file.
As we go with this approach a member not part of a group will be reported in the user diff and we don't need a specific group_diff
to check if a member is whether part of a group of not.
I implemented the change in b0e8a2f
0bb41b4
to
e3a0b84
Compare
bitwarden_access_manager.py
Outdated
f"member_id = {self.id}", | ||
f"member_name = {self.name}", | ||
f"email = {self.email}", | ||
f"type = {str(self.type)}", |
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.
This is still unresolved.
0a98f16
to
1168dc0
Compare
What is unresolved here ? |
This: https://github.com/ChorusOne/github-access-manager/pull/10/files#r1128160013. If you go to the “files changed” tab you can see the full context, from the “conversation” tab sometimes things are missing. |
Thanks, I will check with the full context next time ! |
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.
I think it’s almost good! I have a few more superficial comments about the Python, but the structure of the program looks good.
bitwarden_access_manager.py
Outdated
email: str | ||
type: str | ||
access_all: Optional[bool] | ||
# groups: Tuple[str, ...] |
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.
Following the docstring, the group should go in the member, right? Then this should be uncommented, no?
bitwarden_access_manager.py
Outdated
email: str | ||
type: str | ||
access_all: Optional[bool] | ||
# groups: Tuple[str, ...] |
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.
Following the docstring, the group should go in the member, right? Then this should be uncommented, no?
def get_group_members(self, id: str, name: str) -> Iterable[GroupMember]: | ||
members = json.load(self._http_get(f"/public/groups/{id}/member-ids")) | ||
|
||
for member in members: |
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.
Now we will request every member once for every group that they are part of. It’s not a huge problem, but we could reduce the number of API calls by keeping a dict of member id to member, and only requesting the member if we haven’t requested it before.
But this PR is already open for a long time, let’s not do that right now, we can do it as a follow-up.
When a member is not part of a group, it will be reflected in Member diff. Therefore group diff is not necessary and will result in duplicated information. group_diff is removed.
6fddc72
to
cda04f0
Compare
df16e5a
to
cc2c081
Compare
66ed801
to
d097be8
Compare
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.
This looks good to me! I have a few more small Python comments, but at this point it’s only nitpicks, I think the structure of the program looks good, and the access checks all look correct too!
I’ll try and run this on our organization and report back if I run into any issues.
bitwarden_access_manager.py
Outdated
groups = self.groups or () | ||
if len(groups) > 0: | ||
groups_str = ", ".join(f'"{g}"' for g in sorted(groups)) | ||
result = result + "groups = [ " + groups_str + " ]" | ||
else: | ||
result = result + "groups = []" |
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.
groups = self.groups or () | |
if len(groups) > 0: | |
groups_str = ", ".join(f'"{g}"' for g in sorted(groups)) | |
result = result + "groups = [ " + groups_str + " ]" | |
else: | |
result = result + "groups = []" | |
result = result + "groups = [" + ", ".join(json.dumps(g) for g in sorted(groups)) + "]" |
groups
is no longer Optional
, so we don’t need the or ()
. Also, I don’t see a reason to have the spaces after [
and before ]
if the list is non-empty, if we omit those, then the case distinction goes away.
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.
Why do we need to convert g
into a json string with json.dumps
?
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.
If we just surround g
by quotes, then if the string g
contains a "
or a \
, it will produce invalid toml. If we use json.dumps
to format a json string literal, then it will escape "
and \
to \"
and \\
. This works because toml has the same escaping rules as json.
Ah small thing, can you |
|
||
@staticmethod | ||
def new(client_id: str, client_secret: str) -> BitwardenClient: | ||
response = requests.post( |
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.
I noticed now that I try to run this ... this is using the third-party library requests
. We don’t need it to make requests, we already import http.client
from the standard library, and we even construct the client already. You can see here how we do this in the GitHub version:
Lines 609 to 624 in 6fc4782
self.connection.request( | |
method="GET", | |
url=url, | |
headers={ | |
"Authorization": f"token {self.github_token}", | |
"Accept": "application/vnd.github.v3+json", | |
"User-Agent": "Github Access Manager", | |
}, | |
) | |
# TODO: Respect these response headers | |
# X-RateLimit-Limit: 5000 | |
# X-RateLimit-Remaining: 4994 | |
# X-RateLimit-Reset: 1653495296 | |
# X-RateLimit-Used: 6 | |
# X-RateLimit-Resource: core | |
return self.connection.getresponse() |
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.
I can push a commit to fix this, I’m looking into it anyway.
data={ | ||
"grant_type": "client_credentials", | ||
"scope": "api.organization", | ||
"Accept": "application/json", |
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.
Does the Accept
really go here? This is an http header, shouldn’t it go in headers
?
* The access_all key for members and groups is optional, default is false. | ||
* The member_access key for a collection only list members with direct access | ||
to collection. It omits direct access for members with the role | ||
owners or admins because they have implicit access to all collections. |
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.
It turns out, this is not true, I thought it was because I could access everything in our organization, but it was because access_all
was true for me (which I discovered using this tool :D). So could we remove this special case?
Description
Add script to retrieve Bitwarden configuration using API and to compare with a configuration file.
The script reuse the logic of
github_access_manager
script and some parts can be share between the two script. This will be perform in another PR.Note for the reviewer
access_all
is set totrue
only when a member has the role typeowner
type
refers to the memberrole
: 0 = user, 1 = owner, 2 = admin, 3 = manager, 4 = custom (when writing this, I realize that I forgot to add a logic to make it readable in the toml config. I will add it later)access_all
is set totrue
only whenGrant access to all current and future collections
box is tick.read_only
attribute is set totrue
when the permissionCan view
orCan view, except password
are set. This is set tofalse
when the permissionCan edit
orCan edit, except password
are set. The API doesn't do the distinctionCan ...
andCan ..., except password
permissions.name
, we need to setexternal ID
in the UI to retrieve his nameExample output
Issue chorus-infrastructure #3941