use crate::{DetectionState, DetectionStrategy};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("ID token variable not found: {0}")]
Missing(String),
}
pub(crate) struct GitLabCI;
impl GitLabCI {
fn normalized_audience(audience: &str) -> String {
audience
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_uppercase()
} else {
'_'
}
})
.collect()
}
}
impl DetectionStrategy for GitLabCI {
type Error = Error;
fn new(_state: &DetectionState) -> Option<Self> {
std::env::var("GITLAB_CI")
.ok()
.filter(|v| v == "true")
.map(|_| GitLabCI)
}
async fn detect(&self, audience: &str) -> Result<crate::IdToken, Self::Error> {
let normalized_audience = Self::normalized_audience(audience);
let var_name = format!("{normalized_audience}_ID_TOKEN");
let token = std::env::var(&var_name).map_err(|_| Error::Missing(var_name))?;
Ok(crate::IdToken(token.into()))
}
}
#[cfg(test)]
mod tests {
use crate::{DetectionStrategy as _, gitlab::Error, tests::EnvScope};
use super::GitLabCI;
#[test]
fn test_normalized_audience() {
let cases = [
("sigstore", "SIGSTORE"),
("http://test.audience", "HTTP___TEST_AUDIENCE"),
("my-audience_123", "MY_AUDIENCE_123"),
("Audience With Spaces!", "AUDIENCE_WITH_SPACES_"),
("ðŸ˜", "_"),
("ðŸ˜ðŸ˜ðŸ˜", "___"),
];
for (input, expected) in cases {
assert_eq!(GitLabCI::normalized_audience(input), expected);
}
}
#[tokio::test]
async fn test_detected() {
let mut scope = EnvScope::new();
scope.setenv("GITLAB_CI", "true");
assert!(GitLabCI::new(&Default::default()).is_some())
}
#[tokio::test]
async fn test_not_detected() {
let mut scope = EnvScope::new();
scope.unsetenv("GITLAB_CI");
assert!(GitLabCI::new(&Default::default()).is_none());
}
#[tokio::test]
async fn test_not_detected_wrong_value() {
for value in &["", "false", "TRUE", "1", "yes"] {
let mut scope = EnvScope::new();
scope.setenv("GITLAB_CI", value);
assert!(GitLabCI::new(&Default::default()).is_none());
}
}
#[tokio::test]
async fn test_invalid_missing() {
let mut scope = EnvScope::new();
scope.setenv("GITLAB_CI", "true");
scope.setenv("WRONG_ID_TOKEN", "sometoken");
let detector = GitLabCI::new(&Default::default()).expect("should detect GitLab CI");
assert!(matches!(
detector.detect("bupkis").await,
Err(Error::Missing(_))
));
}
#[tokio::test]
async fn test_ok() {
let mut scope = EnvScope::new();
scope.setenv("GITLAB_CI", "true");
scope.setenv("BUPKIS_ID_TOKEN", "sometoken");
let detector = GitLabCI::new(&Default::default()).expect("should detect GitLab CI");
let token = detector.detect("bupkis").await.expect("should fetch token");
assert_eq!(token.reveal(), "sometoken");
}
}