Skip to content

Commit 928dc83

Browse files
authored
Merge pull request #15 from rust-lang/team-repo
Integrate with the team repo
2 parents 5b3ea91 + 23edaab commit 928dc83

File tree

4 files changed

+93
-48
lines changed

4 files changed

+93
-48
lines changed

cfg.sample.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ sync_on_start = true
6060
owner = ""
6161
name = ""
6262

63+
# If this repo should be integrated with the permissions defined in
64+
# https://github.com/rust-lang/team uncomment the following line.
65+
# Note that the other ACLs will *also* apply.
66+
#rust_team = true
67+
6368
# Who can approve PRs (r+ rights)? You can put GitHub usernames here.
6469
reviewers = []
6570
# Alternatively, set this allow any github collaborator;

homu/auth.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import requests
2+
3+
4+
RUST_TEAM_BASE = "https://team-api.infra.rust-lang.org/v1/"
5+
6+
7+
def fetch_rust_team(repo_label, level):
8+
repo = repo_label.replace('-', '_')
9+
url = RUST_TEAM_BASE + "permissions/bors." + repo + "." + level + ".json"
10+
try:
11+
resp = requests.get(url)
12+
resp.raise_for_status()
13+
return resp.json()["github_users"]
14+
except requests.exceptions.RequestException as e:
15+
print("error while fetching " + url + ": " + str(e))
16+
return []
17+
18+
19+
def verify_level(username, repo_label, repo_cfg, state, toml_keys,
20+
rust_team_level):
21+
authorized = False
22+
if repo_cfg.get('auth_collaborators', False):
23+
authorized = state.get_repo().is_collaborator(username)
24+
if repo_cfg.get('rust_team', False):
25+
authorized = username in fetch_rust_team(repo_label, rust_team_level)
26+
if not authorized:
27+
authorized = username.lower() == state.delegate.lower()
28+
for toml_key in toml_keys:
29+
if not authorized:
30+
authorized = username in repo_cfg.get(toml_key, [])
31+
return authorized
32+
33+
34+
def verify(username, repo_label, repo_cfg, state, auth, realtime, my_username):
35+
# The import is inside the function to prevent circular imports: main.py
36+
# requires auth.py and auth.py requires main.py
37+
from .main import AuthState
38+
39+
# In some cases (e.g. non-fully-qualified r+) we recursively talk to
40+
# ourself via a hidden markdown comment in the message. This is so that
41+
# when re-synchronizing after shutdown we can parse these comments and
42+
# still know the SHA for the approval.
43+
#
44+
# So comments from self should always be allowed
45+
if username == my_username:
46+
return True
47+
48+
authorized = False
49+
if auth == AuthState.REVIEWER:
50+
authorized = verify_level(
51+
username, repo_label, repo_cfg, state, ['reviewers'], 'review',
52+
)
53+
elif auth == AuthState.TRY:
54+
authorized = verify_level(
55+
username, repo_label, repo_cfg, state, ['reviewers', 'try_users'],
56+
'try',
57+
)
58+
59+
if authorized:
60+
return True
61+
else:
62+
if realtime:
63+
reply = '@{}: :key: Insufficient privileges: '.format(username)
64+
if auth == AuthState.REVIEWER:
65+
if repo_cfg.get('auth_collaborators', False):
66+
reply += 'Collaborator required'
67+
else:
68+
reply += 'Not in reviewers'
69+
elif auth == AuthState.TRY:
70+
reply += 'not in try users'
71+
state.add_comment(reply)
72+
return False

homu/main.py

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import functools
77
from . import comments
88
from . import utils
9+
from .auth import verify as verify_auth
910
from .utils import lazy_debug
1011
import logging
1112
from threading import Thread, Lock, Timer
@@ -408,58 +409,19 @@ class LabelEvent(Enum):
408409
PUSHED = 'pushed'
409410

410411

411-
def verify_auth(username, repo_cfg, state, auth, realtime, my_username):
412-
# In some cases (e.g. non-fully-qualified r+) we recursively talk to
413-
# ourself via a hidden markdown comment in the message. This is so that
414-
# when re-synchronizing after shutdown we can parse these comments and
415-
# still know the SHA for the approval.
416-
#
417-
# So comments from self should always be allowed
418-
if username == my_username:
419-
return True
420-
is_reviewer = False
421-
auth_collaborators = repo_cfg.get('auth_collaborators', False)
422-
if auth_collaborators:
423-
is_reviewer = state.get_repo().is_collaborator(username)
424-
if not is_reviewer:
425-
is_reviewer = username in repo_cfg.get('reviewers', [])
426-
if not is_reviewer:
427-
is_reviewer = username.lower() == state.delegate.lower()
428-
429-
if is_reviewer:
430-
have_auth = AuthState.REVIEWER
431-
elif username in repo_cfg.get('try_users', []):
432-
have_auth = AuthState.TRY
433-
else:
434-
have_auth = AuthState.NONE
435-
if have_auth >= auth:
436-
return True
437-
else:
438-
if realtime:
439-
reply = '@{}: :key: Insufficient privileges: '.format(username)
440-
if auth == AuthState.REVIEWER:
441-
if auth_collaborators:
442-
reply += 'Collaborator required'
443-
else:
444-
reply += 'Not in reviewers'
445-
elif auth == AuthState.TRY:
446-
reply += 'not in try users'
447-
state.add_comment(reply)
448-
return False
449-
450-
451412
PORTAL_TURRET_DIALOG = ["Target acquired", "Activated", "There you are"]
452413
PORTAL_TURRET_IMAGE = "https://cloud.githubusercontent.com/assets/1617736/22222924/c07b2a1c-e16d-11e6-91b3-ac659550585c.png" # noqa
453414

454415

455-
def parse_commands(body, username, repo_cfg, state, my_username, db, states,
456-
*, realtime=False, sha=''):
416+
def parse_commands(body, username, repo_label, repo_cfg, state, my_username,
417+
db, states, *, realtime=False, sha=''):
457418
global global_cfg
458419
state_changed = False
459420

460421
_reviewer_auth_verified = functools.partial(
461422
verify_auth,
462423
username,
424+
repo_label,
463425
repo_cfg,
464426
state,
465427
AuthState.REVIEWER,
@@ -469,6 +431,7 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, states,
469431
_try_auth_verified = functools.partial(
470432
verify_auth,
471433
username,
434+
repo_label,
472435
repo_cfg,
473436
state,
474437
AuthState.TRY,
@@ -590,8 +553,8 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, states,
590553
state.change_labels(LabelEvent.APPROVED)
591554

592555
elif word == 'r-':
593-
if not verify_auth(username, repo_cfg, state, AuthState.REVIEWER,
594-
realtime, my_username):
556+
if not verify_auth(username, repo_label, repo_cfg, state,
557+
AuthState.REVIEWER, realtime, my_username):
595558
continue
596559

597560
state.approved_by = ''
@@ -600,8 +563,8 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, states,
600563
state.change_labels(LabelEvent.REJECTED)
601564

602565
elif word.startswith('p='):
603-
if not verify_auth(username, repo_cfg, state, AuthState.TRY,
604-
realtime, my_username):
566+
if not verify_auth(username, repo_label, repo_cfg, state,
567+
AuthState.TRY, realtime, my_username):
605568
continue
606569
try:
607570
pvalue = int(word[len('p='):])
@@ -619,8 +582,8 @@ def parse_commands(body, username, repo_cfg, state, my_username, db, states,
619582
state.save()
620583

621584
elif word.startswith('delegate='):
622-
if not verify_auth(username, repo_cfg, state, AuthState.REVIEWER,
623-
realtime, my_username):
585+
if not verify_auth(username, repo_label, repo_cfg, state,
586+
AuthState.REVIEWER, realtime, my_username):
624587
continue
625588

626589
state.delegate = word[len('delegate='):]
@@ -1522,6 +1485,7 @@ def synchronize(repo_label, repo_cfg, logger, gh, states, repos, db, mergeable_q
15221485
parse_commands(
15231486
comment.body,
15241487
comment.user.login,
1488+
repo_label,
15251489
repo_cfg,
15261490
state,
15271491
my_username,
@@ -1534,6 +1498,7 @@ def synchronize(repo_label, repo_cfg, logger, gh, states, repos, db, mergeable_q
15341498
parse_commands(
15351499
comment.body,
15361500
comment.user.login,
1501+
repo_label,
15371502
repo_cfg,
15381503
state,
15391504
my_username,

homu/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def github():
343343
if parse_commands(
344344
body,
345345
username,
346+
repo_label,
346347
repo_cfg,
347348
state,
348349
g.my_username,
@@ -389,6 +390,7 @@ def github():
389390
found = parse_commands(
390391
c.body,
391392
c.user.login,
393+
repo_label,
392394
repo_cfg,
393395
state,
394396
g.my_username,
@@ -507,6 +509,7 @@ def fail(err):
507509
if parse_commands(
508510
body,
509511
username,
512+
repo_label,
510513
repo_cfg,
511514
state,
512515
g.my_username,

0 commit comments

Comments
 (0)