Skip to content

Commit 1a1d462

Browse files
authored
Merge pull request #531 from Kobzol/rollup-auth
Require review permissions for creating rollups
2 parents 4147860 + 8c41908 commit 1a1d462

File tree

4 files changed

+61
-10
lines changed

4 files changed

+61
-10
lines changed

src/github/oauth.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use crate::github::GithubRepoName;
21
use crate::github::api::DEFAULT_REQUEST_TIMEOUT;
32
use crate::github::api::client::GithubRepositoryClient;
3+
use crate::github::{GithubRepoName, GithubUser};
44
use anyhow::Context;
55
use octocrab::OctocrabBuilder;
66
use octocrab::service::middleware::retry::RetryConfig;
@@ -76,17 +76,17 @@ impl OAuthClient {
7676
.context("Cannot get user authenticated with OAuth")?;
7777

7878
let client_repo = GithubRepoName::new(&user.login, repo.name());
79-
let client = GithubRepositoryClient::new(user.html_url, user_client, client_repo);
79+
let client = GithubRepositoryClient::new(user.html_url.clone(), user_client, client_repo);
8080
Ok(UserGitHubClient {
81-
username: user.login,
81+
user: user.into(),
8282
client,
8383
})
8484
}
8585
}
8686

8787
/// GitHub client authenticated to work with a user's fork of a repository managed by bors.
8888
pub struct UserGitHubClient {
89-
pub username: String,
89+
pub user: GithubUser,
9090
pub client: GithubRepositoryClient,
9191
}
9292

src/github/rollup.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::bors::make_text_ignored_by_bors;
44
use crate::github::api::client::GithubRepositoryClient;
55
use crate::github::api::operations::{ForcePush, MergeError};
66
use crate::github::oauth::{OAuthClient, UserGitHubClient};
7+
use crate::permissions::PermissionType;
78
use crate::server::ServerStateRef;
89
use anyhow::Context;
910
use axum::extract::{Query, State};
@@ -47,6 +48,7 @@ pub enum RollupError {
4748
PullRequestNotFound { pr: PullRequestNumber },
4849
NoPullRequestsSelected,
4950
TooManyPullRequests,
51+
NotAuthenticated,
5052
}
5153

5254
impl IntoResponse for RollupError {
@@ -77,6 +79,11 @@ impl IntoResponse for RollupError {
7779
StatusCode::BAD_REQUEST,
7880
format!("Rolling up too many pull requests, at most {ROLLUP_PR_LIMIT} is allowed"),
7981
),
82+
RollupError::NotAuthenticated => (
83+
StatusCode::FORBIDDEN,
84+
"You are not allowed to create rollups. Review permissions are required."
85+
.to_string(),
86+
),
8087
}
8188
.into_response()
8289
}
@@ -116,6 +123,16 @@ pub async fn oauth_callback_handler(
116123
.get_authenticated_client(repo_name.clone(), &callback.code)
117124
.await?;
118125

126+
// The rollup author is expected to r+ the rollup, so they must have review permissions to
127+
// create it in the first place.
128+
if !repo_state
129+
.permissions
130+
.load()
131+
.has_permission(user_client.user.id, PermissionType::Review)
132+
{
133+
return Err(RollupError::NotAuthenticated);
134+
}
135+
119136
let span = tracing::info_span!(
120137
"create_rollup",
121138
repo = %repo_name,
@@ -165,7 +182,7 @@ async fn create_rollup(
165182
mut pr_nums,
166183
} = rollup_state;
167184

168-
let username = user_client.username;
185+
let username = user_client.user.username;
169186

170187
tracing::info!("User {username} is creating a rollup with PRs: {pr_nums:?}");
171188

@@ -360,6 +377,7 @@ async fn create_rollup(
360377
mod tests {
361378
use crate::github::rollup::OAuthRollupState;
362379
use crate::github::{GithubRepoName, PullRequestNumber};
380+
use crate::permissions::PermissionType;
363381
use crate::tests::{
364382
ApiRequest, ApiResponse, BorsTester, Comment, GitHub, MergeBehavior, PullRequest, Repo,
365383
User, default_repo_name, run_test,
@@ -368,7 +386,15 @@ mod tests {
368386

369387
#[sqlx::test]
370388
async fn rollup_missing_fork(pool: sqlx::PgPool) {
371-
run_test(pool, async |ctx: &mut BorsTester| {
389+
let mut gh = GitHub::default();
390+
gh.add_user(rollup_user());
391+
gh.get_repo(())
392+
.lock()
393+
.permissions
394+
.users
395+
.insert(rollup_user(), vec![PermissionType::Review]);
396+
397+
run_test((pool, gh), async |ctx: &mut BorsTester| {
372398
let pr1 = ctx.open_pr((), |_| {}).await?;
373399
let pr2 = ctx.open_pr((), |_| {}).await?;
374400

@@ -435,6 +461,24 @@ mod tests {
435461
.await;
436462
}
437463

464+
#[sqlx::test]
465+
async fn rollup_missing_review_permissions(pool: sqlx::PgPool) {
466+
let mut gh = GitHub::default();
467+
gh.add_user(rollup_user());
468+
run_test((pool, gh), async |ctx: &mut BorsTester| {
469+
let pr1 = ctx.open_pr((), |_| {}).await?;
470+
471+
make_rollup(ctx, &[&pr1])
472+
.await?
473+
.assert_status(StatusCode::FORBIDDEN)
474+
.assert_body(
475+
"You are not allowed to create rollups. Review permissions are required.",
476+
);
477+
Ok(())
478+
})
479+
.await;
480+
}
481+
438482
#[sqlx::test]
439483
async fn rollup_merge_conflict(pool: sqlx::PgPool) {
440484
let gh = run_test((pool, rollup_state()), async |ctx: &mut BorsTester| {
@@ -640,6 +684,12 @@ mod tests {
640684
let mut gh = GitHub::default();
641685
let rolluper = rollup_user();
642686
gh.add_user(rolluper.clone());
687+
gh.get_repo(())
688+
.lock()
689+
.permissions
690+
.users
691+
.insert(rolluper.clone(), vec![PermissionType::Review]);
692+
643693
// Create fork
644694
let mut repo = Repo::new(rolluper, fork_repo().name());
645695
repo.fork = true;

src/tests/mock/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,16 +354,17 @@ EjQJOzV2OIk4waurl+BsbOHP7C0Zhp7rpyWx4fUCgYEAp/4UceUfbJZGa8CcWZ8F
354354
34PVnCZP7HB3k2eBSpDp4vk=
355355
-----END PRIVATE KEY-----"###;
356356

357-
fn oauth_user_from_request(request: &Request) -> User {
357+
fn oauth_user_from_request(github: Arc<Mutex<GitHub>>, request: &Request) -> User {
358358
let auth: &HeaderValue = request
359359
.headers
360360
.get(AUTHORIZATION)
361361
.expect("Authorization header not found");
362362
let (_bearer, token) = auth.to_str().unwrap().split_once(" ").unwrap();
363363
let (user, rest) = token.split_once("-").unwrap();
364364
assert_eq!(rest, "access-token");
365-
// TODO: load this from GitHub state
366-
User::new(10000, user)
365+
366+
let gh = github.lock();
367+
gh.get_user(user).expect("User not found").clone()
367368
}
368369

369370
/// Create a mock that dynamically responds to its requests using the given function `f`.

src/tests/mock/oauth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub async fn mock_oauth(github: Arc<Mutex<GitHub>>, mock_server: &MockServer) {
4242
Mock::given(method("GET"))
4343
.and(path("/user"))
4444
.respond_with(move |req: &Request| {
45-
let user = oauth_user_from_request(req);
45+
let user = oauth_user_from_request(github.clone(), req);
4646
ResponseTemplate::new(200).set_body_json(GitHubUser::from(user))
4747
})
4848
.mount(mock_server)

0 commit comments

Comments
 (0)