-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
76 changed files
with
1,631 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "prbot-frontend" | ||
version = "0.0.0" | ||
authors = ["Denis BOURGE <Srynetix@users.noreply.github.com>"] | ||
edition = "2021" | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
//! Admin module. | ||
use actix_web::{web, HttpResponse, Result}; | ||
use actix_web_httpauth::extractors::bearer::BearerAuth; | ||
use percent_encoding::{percent_decode, percent_decode_str}; | ||
use prbot_models::{ExternalAccount, ExternalAccountRight, MergeRule, PullRequest, PullRequestRule, Repository}; | ||
use serde::Serialize; | ||
|
||
use crate::server::AppContext; | ||
|
||
pub mod validator; | ||
|
||
#[derive(Serialize)] | ||
struct ExtendedRepository { | ||
repository: Repository, | ||
pull_requests: Vec<PullRequest>, | ||
merge_rules: Vec<MergeRule>, | ||
pull_request_rules: Vec<PullRequestRule> | ||
} | ||
|
||
#[derive(Serialize)] | ||
struct ExtendedExternalAccount { | ||
external_account: ExternalAccount, | ||
rights: Vec<ExternalAccountRight> | ||
} | ||
|
||
|
||
#[tracing::instrument(skip_all)] | ||
pub(crate) async fn repositories_list( | ||
ctx: web::Data<AppContext>, | ||
_auth: BearerAuth, | ||
) -> Result<HttpResponse> { | ||
let mut output = vec![]; | ||
let repositories = ctx.db_service.repositories_all().await.unwrap(); | ||
|
||
for repository in repositories.into_iter() { | ||
let pull_requests = ctx.db_service.pull_requests_list(&repository.owner, &repository.name).await.unwrap(); | ||
let merge_rules = ctx.db_service.merge_rules_list(&repository.owner, &repository.name).await.unwrap(); | ||
let pull_request_rules = ctx.db_service.pull_request_rules_list(&repository.owner, &repository.name).await.unwrap(); | ||
|
||
output.push(ExtendedRepository { | ||
repository, | ||
pull_requests, | ||
merge_rules, | ||
pull_request_rules | ||
}) | ||
} | ||
|
||
Ok(HttpResponse::Ok().json(&output)) | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
pub(crate) async fn accounts_list( | ||
ctx: web::Data<AppContext>, | ||
_auth: BearerAuth, | ||
) -> Result<HttpResponse> { | ||
let accounts = ctx.db_service.accounts_all().await.unwrap(); | ||
Ok(HttpResponse::Ok().json(&accounts)) | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
pub(crate) async fn external_accounts_list( | ||
ctx: web::Data<AppContext>, | ||
_auth: BearerAuth, | ||
) -> Result<HttpResponse> { | ||
let mut output = vec![]; | ||
let external_accounts = ctx.db_service.external_accounts_all().await.unwrap(); | ||
for external_account in external_accounts.into_iter() { | ||
let rights = ctx.db_service.external_account_rights_list(&external_account.username).await.unwrap(); | ||
output.push(ExtendedExternalAccount { | ||
external_account, | ||
rights | ||
}); | ||
} | ||
|
||
Ok(HttpResponse::Ok().json(&output)) | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
pub(crate) async fn pull_request_rules_create( | ||
ctx: web::Data<AppContext>, | ||
rule: web::Json<PullRequestRule>, | ||
_auth: BearerAuth, | ||
) -> Result<HttpResponse> { | ||
let output = ctx.db_service.pull_request_rules_create(rule.0).await.unwrap(); | ||
Ok(HttpResponse::Ok().json(&output)) | ||
} | ||
|
||
#[tracing::instrument(skip_all)] | ||
pub(crate) async fn pull_request_rules_delete( | ||
ctx: web::Data<AppContext>, | ||
path: web::Path<(u64, String)>, | ||
_auth: BearerAuth, | ||
) -> Result<HttpResponse> { | ||
let repository_id = &path.0; | ||
let repository = ctx.db_service.repositories_get_from_id_expect(*repository_id).await.unwrap(); | ||
|
||
let rule_name = &path.1; | ||
let rule_name = percent_decode_str(rule_name).decode_utf8_lossy().to_string(); | ||
ctx.db_service.pull_request_rules_delete(&repository.owner, &repository.name, &rule_name).await.unwrap(); | ||
|
||
Ok(HttpResponse::NoContent().finish()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! External API validator. | ||
use std::time::{SystemTime, UNIX_EPOCH}; | ||
|
||
use actix_web::{dev::ServiceRequest, http::StatusCode, web, Error, ResponseError}; | ||
use actix_web_httpauth::extractors::bearer::BearerAuth; | ||
use prbot_config::Config; | ||
use prbot_crypto::{CryptoError, JwtUtils, PrivateRsaKey}; | ||
use prbot_sentry::sentry; | ||
use serde::{Deserialize, Serialize}; | ||
use thiserror::Error; | ||
|
||
use crate::server::AppContext; | ||
|
||
/// External Jwt claims. | ||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct AdminJwtClaims { | ||
/// Issued at time | ||
pub iat: u64, | ||
/// Expiration | ||
pub exp: u64, | ||
} | ||
|
||
/// Validation error. | ||
#[derive(Debug, Error)] | ||
pub enum ValidationError { | ||
#[error("Admin disabled")] | ||
AdminDisabled, | ||
#[error("Token error,\n caused by: {}", source)] | ||
TokenError { source: CryptoError }, | ||
#[error("Token expired")] | ||
TokenExpired | ||
} | ||
|
||
impl ValidationError { | ||
pub fn token_error(token: &str, source: CryptoError) -> Self { | ||
sentry::configure_scope(|scope| { | ||
scope.set_extra("Token", token.into()); | ||
}); | ||
|
||
Self::TokenError { source } | ||
} | ||
} | ||
|
||
impl ResponseError for ValidationError { | ||
fn status_code(&self) -> StatusCode { | ||
StatusCode::BAD_REQUEST | ||
} | ||
} | ||
|
||
/// Admin jwt authentication validator. | ||
pub async fn admin_jwt_auth_validator( | ||
req: ServiceRequest, | ||
credentials: BearerAuth, | ||
) -> Result<ServiceRequest, (Error, ServiceRequest)> { | ||
admin_jwt_auth_validator_inner(req, credentials) | ||
.await | ||
.map_err(|(err, req)| (err.into(), req)) | ||
} | ||
|
||
async fn admin_jwt_auth_validator_inner( | ||
req: ServiceRequest, | ||
credentials: BearerAuth, | ||
) -> Result<ServiceRequest, (ValidationError, ServiceRequest)> { | ||
let ctx = req.app_data::<web::Data<AppContext>>().unwrap(); | ||
if ctx.config.server.admin_private_key.is_empty() { | ||
return Err((ValidationError::AdminDisabled, req)); | ||
} | ||
|
||
// Validate token | ||
let pubkey = | ||
PrivateRsaKey::new(ctx.config.server.admin_private_key.clone()).extract_public_key(); | ||
let tok = credentials.token(); | ||
let claims: AdminJwtClaims = match JwtUtils::verify_jwt(tok, pubkey.as_str()) { | ||
Ok(claims) => claims, | ||
Err(e) => return Err((ValidationError::token_error(tok, e), req)), | ||
}; | ||
|
||
if now_timestamp() >= claims.exp { | ||
return Err((ValidationError::TokenExpired, req)); | ||
} | ||
|
||
Ok(req) | ||
} | ||
|
||
fn now_timestamp() -> u64 { | ||
let start = SystemTime::now(); | ||
let duration = start.duration_since(UNIX_EPOCH).expect("time collapsed"); | ||
|
||
duration.as_secs() | ||
} | ||
|
||
/// Generate admin token. | ||
pub fn generate_admin_token(config: &Config) -> Result<String, CryptoError> { | ||
let now_ts = now_timestamp(); | ||
let claims = AdminJwtClaims { | ||
// Issued at time | ||
iat: now_ts, | ||
// Expiration in 24h | ||
exp: now_ts + (60 * 60 * 24), | ||
}; | ||
|
||
JwtUtils::create_jwt(&config.server.admin_private_key, &claims) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.