Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion josh-proxy/src/bin/josh-proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ async fn do_filter(

let mut headref = headref;

josh::filter_refs(&transaction, filter, &from_to)?;
josh::filter_refs(&transaction, filter, &from_to, josh::filter::empty())?;
if headref == "HEAD" {
headref = heads_map
.read()?
Expand Down
130 changes: 77 additions & 53 deletions src/bin/josh-filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,41 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
.arg(
clap::Arg::with_name("check-permission")
.long("check-permission")
.short("c")
.short("c"),
)
.arg(clap::Arg::with_name("missing-permission").long("missing-permission"))
.arg(
clap::Arg::with_name("whitelist")
.long("whitelist")
.short("w")
.takes_value(true),
)
.arg(
clap::Arg::with_name("blacklist")
.long("blacklist")
.short("b")
.takes_value(true),
)
.arg(
clap::Arg::with_name("users")
.long("users")
.takes_value(true),
)
.arg(
clap::Arg::with_name("groups")
.long("groups")
.takes_value(true),
)
.arg(
clap::Arg::with_name("user")
.long("user")
.short("u")
.takes_value(true),
)
.arg(
clap::Arg::with_name("repo")
.long("repo")
.short("r")
.takes_value(true),
)
.arg(clap::Arg::with_name("version").long("version").short("v"))
Expand Down Expand Up @@ -183,6 +217,7 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
josh::filter::parse(&i)?,
input_ref,
"refs/JOSH_TMP",
josh::filter::empty(),
)?;
}
}
Expand All @@ -193,12 +228,6 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
let target = update_target;

let reverse = args.is_present("reverse");
let check_permissions = args.is_present("check-permission");

if check_permissions {
filterobj = josh::filter::chain(josh::filter::parse(":PATHS")?, filterobj);
filterobj = josh::filter::chain(filterobj, josh::filter::parse(":FOLD")?);
}

let t = if reverse {
"refs/JOSH_TMP".to_owned()
Expand All @@ -213,21 +242,49 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
.unwrap()
.to_string();

josh::filter_ref(&transaction, filterobj, &src, &t)?;

let mut all_paths = vec![];

let check_permissions = args.is_present("check-permission");
let mut permissions_filter = josh::filter::empty();
if check_permissions {
let result_tree = repo.find_reference(&t)?.peel_to_tree()?;
let whitelist;
let blacklist;
if args.is_present("users")
&& args.is_present("groups")
&& args.is_present("user")
&& args.is_present("repo")
{
let users = args.value_of("users").unwrap();
let groups = args.value_of("groups").unwrap();
let user = args.value_of("user").unwrap();
let repo = args.value_of("repo").unwrap();

let acl = josh::get_acl(users, groups, user, repo)?;
whitelist = acl.0;
blacklist = acl.1;
} else {
whitelist = match args.value_of("whitelist") {
Some(s) => josh::filter::parse(s)?,
_ => josh::filter::nop(),
};
blacklist = match args.value_of("blacklist") {
Some(s) => josh::filter::parse(s)?,
_ => josh::filter::empty(),
};
}
permissions_filter = josh::filter::make_permissions_filter(filterobj, whitelist, blacklist)
}

result_tree.walk(git2::TreeWalkMode::PreOrder, |_, entry| {
let name = entry.name().unwrap();
if name.starts_with("JOSH_ORIG_PATH_") {
let pathname = josh::from_ns(&name.replacen("JOSH_ORIG_PATH_", "", 1));
all_paths.push(pathname);
}
git2::TreeWalkResult::Ok
})?;
let missing_permissions = args.is_present("missing-permission");
if missing_permissions {
filterobj = permissions_filter;
permissions_filter = josh::filter::empty();
}

let updated_refs = josh::filter_ref(&transaction, filterobj, &src, &t, permissions_filter)?;
if args.value_of("update") != Some("FILTERED_HEAD") && updated_refs == 0 {
println!(
"Warning: reference {} wasn't updated",
args.value_of("update").unwrap()
);
}

#[cfg(feature = "search")]
Expand Down Expand Up @@ -264,39 +321,6 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
/* println!("\n Search took {:?}", duration); */
}

let mut dedup = vec![];

for w in all_paths.as_slice().windows(2) {
if let [a, b, ..] = w {
if !b.starts_with(a) {
dedup.push(a.to_owned());
}
}
}

let dedup = all_paths;

let options = glob::MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
};

if let Some(cp) = args.value_of("check-permission") {
let pattern = glob::Pattern::new(cp)?;

let mut allowed = !dedup.is_empty();
for d in dedup.iter() {
let d = std::path::PathBuf::from(d);
let m = pattern.matches_path_with(&d, options);
if !m {
allowed = false;
println!("missing permission for: {:?}", &d);
}
}
println!("Allowed = {:?}", allowed);
}

if reverse {
let new = repo.revparse_single(target).unwrap().id();
let old = repo.revparse_single("JOSH_TMP").unwrap().id();
Expand Down
19 changes: 19 additions & 0 deletions src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ impl std::fmt::Debug for Filter {
return to_op(*self).fmt(f);
}
}

pub fn nop() -> Filter {
to_filter(Op::Nop)
}

pub fn empty() -> Filter {
to_filter(Op::Empty)
}

fn to_filter(op: Op) -> Filter {
let s = format!("{:?}", op);
let f = Filter(
Expand Down Expand Up @@ -767,6 +772,20 @@ fn compute_warnings2<'a>(
warnings
}

pub fn make_permissions_filter(filter: Filter, whitelist: Filter, blacklist: Filter) -> Filter {
rs_tracing::trace_scoped!("make_permissions_filter");

let filter = chain(to_filter(Op::Paths), filter);
let filter = chain(filter, to_filter(Op::Invert));
let filter = chain(
filter,
compose(blacklist, to_filter(Op::Subtract(nop(), whitelist))),
);
let filter = opt::optimize(filter);

return filter;
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion src/housekeeping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ pub fn refresh_known_filters(
upstream_repo,
);

updated_count += filter_refs(&t, filter::parse(filter_spec)?, &refs)?;
updated_count += filter_refs(&t, filter::parse(filter_spec)?, &refs, filter::empty())?;
}
info!("updated {} refs for {:?}", updated_count, upstream_repo);
}
Expand Down
89 changes: 88 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ extern crate pest_derive;
#[macro_use]
extern crate serde_json;

use std::collections::HashMap;
use tracing;

pub mod cache;
pub mod filter;
pub mod graphql;
Expand Down Expand Up @@ -166,12 +169,34 @@ pub fn filter_ref(
filterobj: filter::Filter,
from_refsname: &str,
to_refname: &str,
permissions: filter::Filter,
) -> JoshResult<usize> {
let mut updated_count = 0;
if let Ok(reference) = transaction.repo().revparse_single(from_refsname) {
let original_commit = reference.peel_to_commit()?;
let oid = original_commit.id();

let perms_commit = if let Some(s) = transaction.get_ref(permissions, oid) {
s
} else {
tracing::trace!("apply_to_commit (permissions)");

filter::apply_to_commit(permissions, &original_commit, &transaction)?
};

if perms_commit != git2::Oid::zero() {
let perms_commit = transaction.repo().find_commit(perms_commit)?;
if !perms_commit.tree()?.is_empty() || perms_commit.parents().len() > 0 {
tracing::event!(
tracing::Level::WARN,
msg = "filter_refs: missing permissions for ref",
warn = true,
reference = from_refsname,
);
return Err(josh_error("missing permissions for ref"));
}
}

let filter_commit = if let Some(s) = transaction.get_ref(filterobj, oid) {
s
} else {
Expand Down Expand Up @@ -226,6 +251,7 @@ pub fn filter_refs(
transaction: &cache::Transaction,
filterobj: filter::Filter,
refs: &[(String, String)],
permissions: filter::Filter,
) -> JoshResult<usize> {
rs_tracing::trace_scoped!("filter_refs", "spec": filter::spec(filterobj));
let s = tracing::Span::current();
Expand All @@ -235,7 +261,7 @@ pub fn filter_refs(

let mut updated_count = 0;
for (k, v) in refs {
updated_count += ok_or!(filter_ref(transaction, filterobj, k, v), {
updated_count += ok_or!(filter_ref(&transaction, filterobj, &k, &v, permissions), {
tracing::event!(
tracing::Level::WARN,
msg = "filter_refs: Can't filter reference",
Expand Down Expand Up @@ -275,3 +301,64 @@ pub fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
}
ret
}

type Users = HashMap<String, User>;

#[derive(Debug, serde::Deserialize)]
struct User {
pub groups: toml::value::Array,
}

type Groups = HashMap<String, HashMap<String, Group>>;
#[derive(Debug, serde::Deserialize)]
struct Group {
pub whitelist: String,
pub blacklist: String,
}

pub fn get_acl(
users: &str,
groups: &str,
user: &str,
repo: &str,
) -> JoshResult<(filter::Filter, filter::Filter)> {
let users =
std::fs::read_to_string(users).map_err(|_| josh_error("failed to read users file"))?;
let users: Users = serde_yaml::from_str(&users)
.map_err(|err| josh_error(format!("failed to parse users file: {}", err).as_str()))?;
let groups =
std::fs::read_to_string(groups).map_err(|_| josh_error("failed to read groups file"))?;
let groups: Groups = serde_yaml::from_str(&groups)
.map_err(|err| josh_error(format!("failed to parse groups file: {}", err).as_str()))?;

return users
.get(user)
.and_then(|u| {
let mut whitelist = filter::empty();
let mut blacklist = filter::empty();
for g in &u.groups {
let lists = groups.get(repo).and_then(|repo| {
repo.get(g.as_str()?).and_then(|group| {
let w = filter::parse(&group.whitelist);
let b = filter::parse(&group.blacklist);
Some((w, b))
})
})?;
if let Err(e) = lists.0 {
return Some(Err(JoshError(format!("Error parsing whitelist: {}", e))));
}
if let Err(e) = lists.1 {
return Some(Err(JoshError(format!("Error parsing blacklist: {}", e))));
}
if let Ok(w) = lists.0 {
whitelist = filter::compose(whitelist, w);
}
if let Ok(b) = lists.1 {
blacklist = filter::compose(blacklist, b);
}
}
println!("w: {:?}, b: {:?}", whitelist, blacklist);
Some(Ok((whitelist, blacklist)))
})
.unwrap_or(Ok((filter::empty(), filter::nop())));
}
1 change: 1 addition & 0 deletions tests/filter/empty_head.t
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
$ git commit -m "add file5" 1> /dev/null

$ josh-filter -s :/sub2 master --update refs/josh/filter/master
Warning: reference refs/josh/filter/master wasn't updated
[2] :/sub1
[2] :/sub2
$ git log --graph --pretty=%s josh/filter/master
Expand Down
2 changes: 2 additions & 0 deletions tests/filter/infofile.t
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* add file1

$ josh-filter -s c=:/sub1 master --update refs/josh/filter/master
Warning: reference refs/josh/filter/master wasn't updated
[2] :/sub1
[2] :prefix=c
$ git log --graph --pretty=%s josh/filter/master
Expand All @@ -48,6 +49,7 @@
$ git commit -m "add file5" 1> /dev/null

$ josh-filter -s c=:/sub2 master --update refs/josh/filter/master
Warning: reference refs/josh/filter/master wasn't updated
[2] :/sub1
[2] :/sub2
[3] :prefix=c
Expand Down
Loading