use crate::parse::{Token, WordSet};
use crate::verdict::{SafetyLevel, Verdict};
use crate::policy::{self, FlagPolicy, FlagStyle};
static GLAB_READ_ONLY_SUBCOMMANDS: WordSet = WordSet::new(&[
"ci", "cluster", "deploy-key", "gpg-key", "incident", "issue",
"iteration", "label", "milestone", "mr", "release", "repo",
"schedule", "snippet", "ssh-key", "stack", "variable",
]);
static GLAB_READ_ONLY_ACTIONS: WordSet =
WordSet::new(&["diff", "issues", "list", "status", "view"]);
static GLAB_ALWAYS_SAFE: WordSet =
WordSet::new(&["--version", "-v", "check-update", "version"]);
static GLAB_AUTH_SAFE: WordSet =
WordSet::new(&["status"]);
static GLAB_LIST_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&[
"--all", "--closed", "--draft", "--help", "--merged",
"-A", "-M", "-a", "-c", "-d", "-g", "-h", "-q",
]),
valued: WordSet::flags(&[
"--assignee", "--author", "--group", "--label",
"--milestone", "--not-label", "--order", "--output",
"--page", "--per-page", "--repo", "--reviewer",
"--search", "--sort", "--source-branch", "--state",
"--target-branch",
"-F", "-P", "-R", "-S", "-a", "-g", "-l", "-m", "-o", "-p", "-r", "-s", "-t",
]),
bare: true,
max_positional: None,
flag_style: FlagStyle::Strict,
numeric_dash: false,
};
static GLAB_VIEW_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&[
"--comments", "--help", "--resolved", "--system-logs",
"--unresolved", "--web",
"-c", "-h", "-p", "-s", "-w",
]),
valued: WordSet::flags(&[
"--output", "--page", "--per-page", "--repo",
"-F", "-P", "-R", "-p",
]),
bare: false,
max_positional: None,
flag_style: FlagStyle::Strict,
numeric_dash: false,
};
static GLAB_DIFF_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&[
"--help", "--raw",
"-h",
]),
valued: WordSet::flags(&[
"--color", "--repo",
"-R",
]),
bare: false,
max_positional: None,
flag_style: FlagStyle::Strict,
numeric_dash: false,
};
static GLAB_SIMPLE_POLICY: FlagPolicy = FlagPolicy {
standalone: WordSet::flags(&[
"--help",
"-h", "-q",
]),
valued: WordSet::flags(&[
"--output", "--page", "--per-page", "--repo",
"-F", "-P", "-R", "-p",
]),
bare: true,
max_positional: None,
flag_style: FlagStyle::Strict,
numeric_dash: false,
};
fn glab_action_policy(action: &str) -> &'static FlagPolicy {
match action {
"list" | "issues" => &GLAB_LIST_POLICY,
"view" => &GLAB_VIEW_POLICY,
"diff" => &GLAB_DIFF_POLICY,
"status" => &GLAB_SIMPLE_POLICY,
_ => &GLAB_SIMPLE_POLICY,
}
}
pub fn is_safe_glab(tokens: &[Token]) -> Verdict {
if tokens.len() < 2 {
return Verdict::Denied;
}
let subcmd = &tokens[1];
if GLAB_READ_ONLY_SUBCOMMANDS.contains(subcmd) {
if tokens.len() < 3 || !GLAB_READ_ONLY_ACTIONS.contains(&tokens[2]) {
return Verdict::Denied;
}
let policy = glab_action_policy(tokens[2].as_str());
return if policy::check(&tokens[2..], policy) { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied };
}
if GLAB_ALWAYS_SAFE.contains(subcmd) {
return if tokens.len() == 2 { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied };
}
if subcmd == "auth" {
if tokens.len() < 3 || !GLAB_AUTH_SAFE.contains(&tokens[2]) {
return Verdict::Denied;
}
return if policy::check(&tokens[2..], &GLAB_SIMPLE_POLICY) { Verdict::Allowed(SafetyLevel::Inert) } else { Verdict::Denied };
}
if subcmd == "api" {
return super::gh::is_safe_gh_api(tokens);
}
Verdict::Denied
}
pub(in crate::handlers::forges) fn dispatch(cmd: &str, tokens: &[Token]) -> Option<Verdict> {
match cmd {
"glab" => Some(is_safe_glab(tokens)),
_ => None,
}
}
pub fn command_docs() -> Vec<crate::docs::CommandDoc> {
use crate::docs::{CommandDoc, DocBuilder, wordset_items};
vec![
CommandDoc::handler("glab",
"https://glab.readthedocs.io/en/latest/",
DocBuilder::new()
.section(format!("Subcommands {} are allowed with actions: {}.",
wordset_items(&GLAB_READ_ONLY_SUBCOMMANDS),
wordset_items(&GLAB_READ_ONLY_ACTIONS)))
.section(format!("Always safe: {}.",
wordset_items(&GLAB_ALWAYS_SAFE)))
.section("auth status, api (GET only).")
.section("")
.build()),
]
}
#[cfg(test)]
pub(super) const REGISTRY: &[crate::handlers::CommandEntry] = &[
crate::handlers::CommandEntry::Paths { cmd: "glab", bare_ok: false, paths: &[
"glab mr list",
"glab mr view 123",
"glab mr diff 123",
"glab issue list",
"glab issue view 456",
"glab ci list",
"glab ci status",
"glab release list",
"glab label list",
"glab milestone list",
"glab snippet view 1",
"glab variable list",
"glab repo list",
"glab repo view owner/repo",
"glab cluster list",
"glab deploy-key list",
"glab gpg-key list",
"glab incident list",
"glab iteration list",
"glab schedule list",
"glab ssh-key list",
"glab stack list",
"glab auth status",
"glab api projects/1/merge_requests",
]},
];
#[cfg(test)]
mod tests {
use crate::is_safe_command;
fn check(cmd: &str) -> bool {
is_safe_command(cmd)
}
safe! {
glab_mr_list: "glab mr list",
glab_mr_list_state: "glab mr list --state opened",
glab_mr_list_author: "glab mr list --author user",
glab_mr_list_label: "glab mr list --label bug",
glab_mr_list_output: "glab mr list --output json",
glab_mr_view: "glab mr view 123",
glab_mr_view_web: "glab mr view 123 --web",
glab_mr_view_comments: "glab mr view 123 --comments",
glab_mr_diff: "glab mr diff 123",
glab_mr_diff_color: "glab mr diff 123 --color always",
glab_mr_diff_raw: "glab mr diff 123 --raw",
glab_issue_list: "glab issue list",
glab_issue_list_state: "glab issue list --state opened",
glab_issue_view: "glab issue view 456",
glab_ci_status: "glab ci status",
glab_ci_list: "glab ci list",
glab_release_list: "glab release list",
glab_label_list: "glab label list",
glab_milestone_list: "glab milestone list",
glab_snippet_view: "glab snippet view 1",
glab_variable_list: "glab variable list",
glab_auth_status: "glab auth status",
glab_version: "glab --version",
glab_version_subcommand: "glab version",
glab_check_update: "glab check-update",
glab_api_get_implicit: "glab api projects/1/merge_requests",
glab_api_explicit_get: "glab api projects/1/issues -X GET",
}
denied! {
glab_api_post_denied: "glab api projects/1/issues -X POST",
glab_api_field_denied: "glab api projects/1/issues -f title=x",
glab_version_with_extra_denied: "glab version --extra",
glab_check_update_with_extra_denied: "glab check-update --extra",
}
}