Getting Information on Project Collaborators
Overview
The Benchling API provides various broad endpoints for retrieving lists of different types of objects, such as users, apps, projects, and permissions access policies. This guide demonstrates how to leverage these endpoints in combination to fulfill the following example use cases:
- Getting information about collaborators on all projects on a tenant
- Getting all collaborators on a specific project
- Getting a specific partyβs access policy on a specific project
- Getting a specific userβs access policies for all projects on which they are shared
1) Getting all collaborators on a specific project
- For a given project id (eg.
src_TnTw2xzy
), make a request toapi/v2-alpha/projects/<project_id>/collaborations
. This will return something like the following:
{
"collaborations": [
{
"accessPolicy": {
"id": "pol_12345",
"name": "READ"
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
{
"access": "NOT_GRANTED",
"description": "Projects and folders - Create folders"
},
...
]
},
"collaborator": {
"type": "USER",
"handle": "albert.einstein",
"id": "ent_rCgpcKrj",
"name": "Albert Einstein"
}
},
{
"accessPolicy": {
"id": "pol_23456",
"name": "WRITE",
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
...
]
},
"collaborator": {
"type": "TEAM",
"role": "ADMIN",
"handle": "benchlingteam",
"id": "team_oSrQDUmh",
"name": "Benchling Team"
}
},
{
"accessPolicy": {
"id": "pol_23456",
"name": "READ",
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
...
]
},
"collaborator": {
"type": "APP",
"handle": "",
"id": "app_ROpiaHWZUrrZFUxy",
"name": "Integration App"
}
}
]
}
Going forward, let us define the above request by
get_project_collaborations(project_id): api/v2-alpha/projects/<project_id>/collaborations
This endpoint provides the same amount of information as the project collaborators UI (pictured below).
- We can further break down group collaborators into individual users by leveraging the
/users
endpoint. For example, to get all users that are admins of Benchling Team and haveWRITE
access in the above example, we can make a call to/api/v2-beta/users?adminOf=team_oSrQDUmh
. This returns a list of users:
{
"users": [
{
"handle": "lpasteur",
"id": "ent_a8asdp",
"name": "Louis Pasteur"
}
]
}
We define the following requests:
get_team_admins(team_id): /api/v2-beta/users?adminOf=team_id
get_team_members(team_id): /api/v2-beta/users?memberOf=team_id
get_org_admins(org_id): /api/v2-beta/users?adminOf=org_id
get_org_members(org_id): /api/v2-beta/users?memberOf=org_id
Combining the results of these subqueries with the results of the previous request, we can create a full list of individual users and their access levels to each project on the tenant. In Python, this could look like the following:
results = []
projects = get_all_projects()
for project in projects:
collaborations = get_project_collaborations(project.id)
for collaboration in collaborations:
collaborator = collaboration.collaborator
role, type = collaboration.collaborator.role, collaboration.collaborator.type
# if collaboration is for a specific user, add directly to results
if type == "USER":
results.append({"project": project, "user": collaborator, "policy": collaboration.policy})
# otherwise, get users corresponding to role
elif type == "ORGANIZATION" and role == "ADMIN":
org_admins = get_org_admins(collaborator.id)
for org_admin in org_admins:
results.append({"project": project, "user": org_admin, "policy": collaboration.policy})
elif type == "ORGANIZATION" and role == "MEMBER":
# similarly iterate through org_members
elif collaborator.type == "TEAM" and role = "ADMIN":
# iterate through team_admins
elif collaborator.type == "TEAM" and role = "MEMBER":
# iterate through team_members
Collaborators with Multiple Access Policies
In this and all the following cases, our API might return more than one access policy per user on a project, if the user gets different policies through memberships of different groups. Refer to Understanding Combined Access Policies to learn how to interpret multiple policies.
2) Getting information about collaborators on all projects on a tenant
- Make a request (or multiple requests, until
nextToken
is empty) toapi/v2/projects
to get a list of all projects on the tenant accessible to the API caller. This request, which we'll define asget_all_projects()
, will return a paginated list with the following shape:
{
"projects": [
{
"archiveRecord": null,
"id": "src_TnTw2xzy",
"name": "Martian DNA Project",
"owner": {
"handle": "albert.einstein",
"id": "ent_rCgpcKrj",
"name": "Albert Einstein"
}
},
],
"nextToken": ""
}
For each project, we can then reuse the logic from the previous use case to get information on its collaborators.
3) Getting a specific partyβs access policy on a specific project
Similarly, to get a specific partyβs access level on a specific project, we can reuse the logic from Use Case #1 to get collaborator info on the project. If our party of interest is a team or organization, we can simply go through the projectβs collaborators and check for that group. If our party is a user, we can delve into the admins and members of each collaborating group and check to see if our user is present.
4) Getting a specific userβs access policy for all projects they can access
Next, weβll walk through the process for getting a userβs policies on all accessible projects, including projects shared with teams or organizations of which they are members.
This could also be done by filtering the complete results from Use Case #2 for the desired user. However, we will use this opportunity to demonstrate the /teams and /organizations endpoints, which work in the opposite direction.
Listing Endpoint Visibility
All listing endpoints, including
api/v2/projects
, return only objects that are viewable by the API caller. In this use case, only projects accessible to both the given user and the API caller are returned.
- We first make requests to get all teams and orgs of which the user is a member or admin, using the HasMembers and HasAdmins filters. We can define functions to make the following calls:
get_member_teams(id): api/v2-beta/teams?hasMembers=id
get_admin_teams(id): api/v2-beta/teams?hasAdmins=id
get_member_orgs(id): api/v2-beta/organizations?hasMembers=id
get_admin_orgs(id): api/v2-beta/organizations?hasAdmins=id
- Next, we follow steps 1 and 2 from the previous section to retrieve all collaborations on all projects on the tenant, which once again gives us a list with the following shape:
{
"collaborations": [
{
"accessPolicy": {
"id": "datapol_12345",
"name": "READ",
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
...
]
},
"collaborator": {
"type": "USER",
"handle": "albert.einstein",
"id": "ent_rCgpcKrj",
"name": "Albert Einstein"
}
},
{
"accessPolicy": {
"id": "datapol_23456",
"name": "WRITE",
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
...
]
},
"collaborator": {
"type": "TEAM",
"role": "ADMIN",
"handle": "benchlingteam",
"id": "team_oSrQDUmh",
"name": "Benchling Team"
}
]
}
- For each project, we can now iterate through its collaborations, determine if any of those collaborations include our user or one of their groups, and get a list of corresponding policies the user has on the project. In Python, this might look something like the following:
member_team_ids = [team.id for team in get_member_teams("ent_a8asdp")]
# similarly define admin_team_ids, member_org_ids, admin_org_ids
results = []
for project in get_all_projects():
for collaboration in get_project_collaborations(project.id):
role, type = collaboration.collaborator.role, collaboration.collaborator.type
if role == "ADMIN" and type == "TEAM":
if collaborator.id in admin_team_ids:
results.append({"project": project, "policy": collaboration.accessPolicy})
if role == "MEMBER" and type == "TEAM":
# append from member_team_ids
if role == "ADMIN" and type == "ORG":
# append from admin_org_ids
if role == "MEMBER" and type == "ORG":
# append from member_org_ids
Understanding Combined Access Policies
In this section, we explain how to determine a userβs combined permissions across multiple access policies.
Assume weβve followed the steps for Use Case 3 above, giving us the following list of policies for a single user on a single project:
policies = [
{
"id": "datapol_12345",
"name": "LIBRARIAN"
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
{
"access": "GRANTED_TO_AUTHOR",
"description": "Projects and folders - Create folders"
},
{
"access": "NOT_GRANTED",
"description": "Projects and folders - Add other items"
},
{
"access": "NOT_GRANTED",
"description": "Projects and folders - Edit folder properties"
},
...
]
},
{
"id": "datapol_23456",
"name": "SCIENTIST",
"policyStatements": [
{
"access": "GRANTED",
"description": "Projects and folders - View"
},
{
"access": "NOT_GRANTED",
"description": "Projects and folders - Create folders"
},
{
"access": "GRANTED",
"description": "Projects and folders - Add other items"
},
{
"access": "NOT_GRANTED",
"description": "Projects and folders - Edit folder properties"
},
...
]
]
The right way to interpret the combined permissions for a user can be found by going through each the policy statements of each of the userβs access policies and taking the most permissive value of access, where GRANTED
is the most permissive, GRANTED_TO_AUTHOR
is the next most permissive, and NOT_GRANTED
is the least permissive. Thus, in the above example, our userβs effective permissions are:
- Projects and folders - View: Granted (by both Librarian and Scientist)
- Projects and folders - Create folders: Granted to Author (by Librarian, which supersedes Scientist)
- Projects and folders - Add other items: Granted (by Scientist, which supersedes Librarian)
- Projects and folders - Edit folder properties: Not Granted (by either policy)
The Python code for this could look as follows:
from collections import defaultdict
for policy in policies:
# create dict mapping each policy statement description to a set of
# access levels granted to the user across policies
permission_levels_by_description = defaultdict(set)
for statement in policy.policy_statements:
permission_levels_by_description[statement.description].add(statement.access)
# get new effective policy statements dict with most permissive access
# level for each policy statement
effective_policy_statements = []
for description, access_levels in permission_levels_by_description.items():
if "GRANTED" in access_levels:
access = "GRANTED"
elif "GRANTED_TO_AUTHOR" in access_levels:
access = "GRANTED_TO_AUTHOR"
else:
access = "NOT_GRANTED"
effective_policy_statements.append(
{
"description": description,
"access": access
}
)
Updated about 3 years ago