Skip to content

Commit

Permalink
Allow builds to be private
Browse files Browse the repository at this point in the history
Allow build repos to be set as public or private. If no app ID is set,
the default is public as before, but if an app ID *is* set, the default
is private (but in either case can be overridden). flat-manager-client gains
`--public_download`/`--no_public_download` flags to control this
behavior.

A private build requires a build token in order to download from it. A new scope
is added, "download", which allows listing and downloading builds.
  • Loading branch information
jameswestman authored and barthalion committed Sep 23, 2022
1 parent 27e9ea2 commit bcc96a3
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 7 deletions.
4 changes: 4 additions & 0 deletions flat-manager-client
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ async def create_command(session, args):
}
if args.app_id is not None:
json["app-id"] = args.app_id
if args.public_download is not None:
json["public-download"] = args.public_download
resp = await session.post(build_url, headers={'Authorization': 'Bearer ' + args.token}, json=json)
async with resp:
if resp.status != 200:
Expand Down Expand Up @@ -634,6 +636,8 @@ if __name__ == '__main__':
create_parser.add_argument('manager_url', help='remote repo manager url')
create_parser.add_argument('repo', help='repo name')
create_parser.add_argument('app_id', nargs='?', help='app ID')
create_parser.add_argument('--public_download', action='store_true', default=None, help='allow public read access to the build repo')
create_parser.add_argument('--no_public_download', action='store_false', dest='public_download', default=None, help='allow public read access to the build repo')
create_parser.set_defaults(func=create_command)

push_parser = subparsers.add_parser('push', help='Push to repo manager')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE builds DROP COLUMN public_download;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE builds ADD public_download BOOLEAN DEFAULT TRUE NOT NULL;
12 changes: 11 additions & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ async fn get_job_async(
pub struct CreateBuildArgs {
repo: String,
app_id: Option<String>,
public_download: Option<bool>,
}

pub fn create_build(
Expand All @@ -245,10 +246,17 @@ async fn create_build_async(

let repoconfig = config.get_repoconfig(&args.repo).map(|rc| rc.clone())?; // Ensure the repo exists

// If public_download is not specified, it defaults to true if there is no app ID (old style builds) and false
// if there is one.
let public_download = args
.public_download
.unwrap_or_else(|| args.app_id.is_none());

let build = db
.new_build(NewBuild {
repo: args.repo.clone(),
app_id: args.app_id.clone(),
public_download,
})
.await?;
let build_repo_path = config.build_repo_base.join(build.id.to_string());
Expand Down Expand Up @@ -284,7 +292,9 @@ async fn builds_async(
db: Data<Db>,
req: HttpRequest,
) -> Result<HttpResponse, ApiError> {
req.has_token_claims("build", ClaimsScope::Build)?;
req.has_token_claims("build", ClaimsScope::Build)
// also allow downloaders to list builds
.or_else(|_| req.has_token_claims("build", ClaimsScope::Download))?;

let builds = if let Some(app_id) = query.app_id.clone() {
req.has_token_prefix(&app_id)?;
Expand Down
42 changes: 37 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use actix_web::http::header::{HeaderValue, CACHE_CONTROL};
use actix_web::web::Data;
use actix_web::Responder;
use actix_web::{self, http, middleware, web, App, HttpRequest, HttpResponse, HttpServer};
use futures3::TryFutureExt;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ffi::OsStr;
Expand Down Expand Up @@ -153,6 +154,8 @@ pub enum ClaimsScope {
Publish,
// Permission to upload deltas for a repo. Should not be given to untrusted parties.
Generate,
// Permission to list builds and to download a build repo.
Download,
#[serde(other)]
Unknown,
}
Expand Down Expand Up @@ -379,15 +382,44 @@ pub fn load_config<P: AsRef<Path>>(path: P) -> io::Result<Config> {
Ok(config_data)
}

#[derive(Deserialize)]
pub struct BuildRepoParams {
id: i32,
tail: String,
}

fn handle_build_repo(
config: Data<Config>,
params: actix_web::web::Path<BuildRepoParams>,
db: Data<Db>,
req: HttpRequest,
) -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
Box::pin(handle_build_repo_async(config, params, db, req)).compat()
}

async fn handle_build_repo_async(
config: Data<Config>,
params: actix_web::web::Path<BuildRepoParams>,
db: Data<Db>,
req: HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
let tail = req.match_info().query("tail");
let id = req.match_info().query("id");
let build = db.lookup_build(params.id).await?;
if !build.public_download {
req.has_token_repo(&build.repo)?;
req.has_token_claims(&format!("build/{}", build.id), ClaimsScope::Download)?;
if let Some(app_id) = build.app_id {
req.has_token_prefix(&app_id)
/* Hide the app ID of the build, since we can't access it */
.map_err(|_| {
ApiError::NotEnoughPermissions(
"Build's app ID not matching prefix in token".to_string(),
)
})?;
}
}

let relpath = canonicalize_path(tail.trim_start_matches('/'))?;
let realid = canonicalize_path(id)?;
let relpath = canonicalize_path(params.tail.trim_start_matches('/'))?;
let realid = canonicalize_path(&params.id.to_string())?;
let path = Path::new(&config.build_repo_base)
.join(&realid)
.join(&relpath);
Expand All @@ -398,7 +430,7 @@ fn handle_build_repo(
NamedFile::open(path)
.or_else(|_e| {
let fallback_path = Path::new(&config.build_repo_base)
.join(&id)
.join(&params.id.to_string())
.join("parent")
.join(&relpath);
if fallback_path.is_dir() {
Expand Down
3 changes: 2 additions & 1 deletion src/bin/gentoken.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn main() {
ap.refer(&mut scope).add_option(
&["--scope"],
List,
"Add scope (default if none: [build, upload, publish, jobs]",
"Add scope (default if none: [build, upload, download, publish, jobs]",
);
ap.refer(&mut prefixes).add_option(
&["--prefix"],
Expand Down Expand Up @@ -88,6 +88,7 @@ fn main() {
scope = vec![
"build".to_string(),
"upload".to_string(),
"download".to_string(),
"publish".to_string(),
"jobs".to_string(),
];
Expand Down
2 changes: 2 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::{mem, time};
pub struct NewBuild {
pub repo: String,
pub app_id: Option<String>,
pub public_download: bool,
}

#[derive(Identifiable, Serialize, Queryable, Debug, Eq, PartialEq)]
Expand All @@ -29,6 +30,7 @@ pub struct Build {
pub repo: String,
pub extra_ids: Vec<String>,
pub app_id: Option<String>,
pub public_download: bool,
}

#[derive(Deserialize, Debug, Eq, PartialEq)]
Expand Down
1 change: 1 addition & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ table! {
repo -> Text,
extra_ids -> Array<Text>,
app_id -> Nullable<Text>,
public_download -> Bool,
}
}

Expand Down

0 comments on commit bcc96a3

Please sign in to comment.