summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--Makefile4
-rw-r--r--etc/authzd/policy0.cedar56
-rw-r--r--src/authorization/authorizer.rs2
-rw-r--r--src/authorization/cedar_authorizer.rs207
-rw-r--r--src/authorization/check_service.rs2
-rw-r--r--src/authorization/server.rs12
-rw-r--r--src/main.rs3
-rw-r--r--tests/authorization/cedar_authorizer_test.rs60
-rw-r--r--tests/authorization/check_service_test.rs127
-rw-r--r--tests/support/factory_bot.rs10
11 files changed, 267 insertions, 217 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6ae0a526..4968aa7b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -44,5 +44,6 @@ lint:
needs: []
before_script:
- rustup component add clippy rustfmt
+ - cargo install --locked cedar-policy-cli
script:
- make lint
diff --git a/Makefile b/Makefile
index 6eb1ec31..701b2422 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ setup:
mise install
mise exec go -- go install github.com/xlgmokha/minit@latest
mise exec rust -- rustup component add clippy rustfmt
+ mise exec rust -- cargo install --locked cedar-policy-cli
# Cargo targets
build:
@@ -29,9 +30,12 @@ clean:
fmt:
@cargo fmt
+ @cedar format --policies etc/authzd/policy0.cedar --write
lint:
@cargo clippy
+ @cedar check-parse --policies etc/authzd/policy0.cedar
+ @cedar format --policies etc/authzd/policy0.cedar --check
doc:
@cargo doc --open
diff --git a/etc/authzd/policy0.cedar b/etc/authzd/policy0.cedar
index 034e81b5..9410eced 100644
--- a/etc/authzd/policy0.cedar
+++ b/etc/authzd/policy0.cedar
@@ -1,20 +1,42 @@
-permit(principal, action == Action::"check", resource)
-when {
- context has bearer_token &&
- context.bearer_token == "valid-token"
+permit (
+ principal,
+ action == Action::"check",
+ resource
+)
+when { context has bearer_token && context.bearer_token == "valid-token" };
+
+permit (principal, action, resource)
+when
+{
+ context has path &&
+ context has method &&
+ context.method == "GET" &&
+ (context.path like "*.css" ||
+ context.path like "*.js" ||
+ context.path like "*.ico" ||
+ context.path like "*.png" ||
+ context.path like "*.jpg" ||
+ context.path like "*.jpeg" ||
+ context.path like "*.gif" ||
+ context.path like "*.bmp" ||
+ context.path like "*.html")
};
-permit(principal, action == Action::"check", resource)
-when {
- context has path && (
- context.path like "*.css" ||
- context.path like "*.js" ||
- context.path like "*.ico" ||
- context.path like "*.png" ||
- context.path like "*.jpg" ||
- context.path like "*.jpeg" ||
- context.path like "*.gif" ||
- context.path like "*.bmp" ||
- context.path like "*.html"
- )
+permit (principal, action, resource)
+when
+{
+ context has host &&
+ context has method &&
+ context has path &&
+ ((context.host == "sparkle.runway.gitlab.net" ||
+ context.host == "sparkle.staging.runway.gitlab.net" ||
+ context.host like "localhost:*") &&
+ ((context.method == "GET" &&
+ (context.path == "/" ||
+ context.path == "/callback" ||
+ context.path == "/dashboard/nav" ||
+ context.path == "/health" ||
+ context.path == "/signout" ||
+ context.path == "/sparkles")) ||
+ (context.method == "POST" && (context.path == "/sparkles/restore"))))
};
diff --git a/src/authorization/authorizer.rs b/src/authorization/authorizer.rs
index 14a7df27..62733585 100644
--- a/src/authorization/authorizer.rs
+++ b/src/authorization/authorizer.rs
@@ -1,5 +1,5 @@
use envoy_types::ext_authz::v3::pb::CheckRequest;
-pub trait Authorizer: std::fmt::Debug {
+pub trait Authorizer: std::fmt::Debug + std::marker::Sync + std::marker::Send + 'static {
fn authorize(&self, request: CheckRequest) -> bool;
}
diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs
index 432102ef..64287414 100644
--- a/src/authorization/cedar_authorizer.rs
+++ b/src/authorization/cedar_authorizer.rs
@@ -1,45 +1,48 @@
use super::authorizer::Authorizer;
-use cedar_policy::{
- Authorizer as CedarAuth, Context, Entities, EntityId, EntityTypeName, EntityUid, PolicySet,
- Request as CedarRequest, RestrictedExpression,
-};
-use envoy_types::ext_authz::v3::pb::CheckRequest;
-use std::collections::HashMap;
use std::fs;
use std::str::FromStr;
#[derive(Debug)]
pub struct CedarAuthorizer {
- policies: PolicySet,
- authorizer: CedarAuth,
+ authorizer: cedar_policy::Authorizer,
+ entities: cedar_policy::Entities,
+ policies: cedar_policy::PolicySet,
}
impl CedarAuthorizer {
- pub fn new(policies: cedar_policy::PolicySet) -> CedarAuthorizer {
+ pub fn new(
+ policies: cedar_policy::PolicySet,
+ entities: cedar_policy::Entities,
+ ) -> CedarAuthorizer {
CedarAuthorizer {
policies,
- authorizer: CedarAuth::new(),
+ entities,
+ authorizer: cedar_policy::Authorizer::new(),
}
}
- pub fn new_from(path: &std::path::Path) -> CedarAuthorizer {
- Self::new(Self::load_from(path).unwrap_or_else(|_| PolicySet::default()))
+ pub fn new_from(path: &std::path::Path, entities: cedar_policy::Entities) -> CedarAuthorizer {
+ Self::new(
+ Self::load_from(path).unwrap_or_else(|_| cedar_policy::PolicySet::default()),
+ entities,
+ )
}
- fn load_from(path: &std::path::Path) -> Result<PolicySet, Box<dyn std::error::Error>> {
+ fn load_from(
+ path: &std::path::Path,
+ ) -> Result<cedar_policy::PolicySet, Box<dyn std::error::Error>> {
if !path.exists() || !path.is_dir() {
- return Ok(PolicySet::default());
+ return Ok(cedar_policy::PolicySet::default());
}
- let mut policies = PolicySet::new();
-
+ let mut policies = cedar_policy::PolicySet::new();
for entry in fs::read_dir(path)? {
let file_path = entry?.path();
if let Some(extension) = file_path.extension() {
if extension == "cedar" {
let content = fs::read_to_string(&file_path)?;
- let file_policies = PolicySet::from_str(&content)?;
+ let file_policies = cedar_policy::PolicySet::from_str(&content)?;
for policy in file_policies.policies() {
policies.add(policy.clone())?;
@@ -50,16 +53,96 @@ impl CedarAuthorizer {
Ok(policies)
}
+
+ fn map_from(
+ &self,
+ http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> Result<cedar_policy::Request, Box<dyn std::error::Error>> {
+ let principal = self.principal_from(&http_request)?;
+ let permission = self.permission_from(&http_request)?;
+ let resource = self.resource_from(&http_request)?;
+ let context = self.context_from(http_request)?;
+
+ Ok(cedar_policy::Request::new(
+ principal, permission, resource, context, None,
+ )?)
+ }
+
+ fn principal_from(
+ &self,
+ _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
+ Ok(cedar_policy::EntityUid::from_type_name_and_id(
+ cedar_policy::EntityTypeName::from_str("User")?,
+ cedar_policy::EntityId::from_str("client")?,
+ ))
+ }
+
+ fn permission_from(
+ &self,
+ _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
+ Ok(cedar_policy::EntityUid::from_type_name_and_id(
+ cedar_policy::EntityTypeName::from_str("Action")?,
+ cedar_policy::EntityId::from_str("check")?,
+ ))
+ }
+
+ fn resource_from(
+ &self,
+ _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
+ Ok(cedar_policy::EntityUid::from_type_name_and_id(
+ cedar_policy::EntityTypeName::from_str("Resource")?,
+ cedar_policy::EntityId::from_str("resource")?,
+ ))
+ }
+
+ fn context_from(
+ &self,
+ http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> Result<cedar_policy::Context, Box<dyn std::error::Error>> {
+ let mut items = std::collections::HashMap::new();
+
+ items.insert("bearer_token".to_string(), self.token_from(&http_request));
+ items.insert("host".to_string(), self.safe_string(&http_request.host));
+ items.insert("method".to_string(), self.safe_string(&http_request.method));
+ items.insert("path".to_string(), self.safe_string(&http_request.path));
+
+ Ok(cedar_policy::Context::from_pairs(
+ items.into_iter().collect::<Vec<_>>(),
+ )?)
+ }
+
+ fn token_from(
+ &self,
+ http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
+ ) -> cedar_policy::RestrictedExpression {
+ let bearer_token = &http_request
+ .headers
+ .get("authorization")
+ .and_then(|auth| auth.strip_prefix("Bearer "))
+ .unwrap_or("");
+
+ self.safe_string(bearer_token)
+ }
+
+ fn safe_string(&self, item: &str) -> cedar_policy::RestrictedExpression {
+ cedar_policy::RestrictedExpression::new_string(item.to_string())
+ }
}
impl Default for CedarAuthorizer {
fn default() -> Self {
- Self::new_from(std::path::Path::new("/etc/authzd"))
+ Self::new_from(
+ std::path::Path::new("/etc/authzd"),
+ cedar_policy::Entities::empty(),
+ )
}
}
impl Authorizer for CedarAuthorizer {
- fn authorize(&self, request: CheckRequest) -> bool {
+ fn authorize(&self, request: envoy_types::ext_authz::v3::pb::CheckRequest) -> bool {
let http_request = match request
.attributes
.as_ref()
@@ -70,37 +153,26 @@ impl Authorizer for CedarAuthorizer {
None => return false,
};
- tracing::info!(
- method = %http_request.method,
- host = %http_request.host,
- path = %http_request.path,
- scheme = %http_request.scheme,
- protocol = %http_request.protocol,
- "Processing HTTP request"
- );
-
- if http_request.host == "sparkle.staging.runway.gitlab.net"
- && http_request.method == "GET"
- && http_request.path == "/"
- {
- return true;
- }
-
- let headers = &http_request.headers;
-
- let bearer_token = headers
- .get("authorization")
- .and_then(|auth| auth.strip_prefix("Bearer "))
- .unwrap_or("");
-
- match self.create_cedar_request(bearer_token, &http_request.path.to_string()) {
+ match self.map_from(http_request.clone()) {
Ok(cedar_request) => {
- let entities = Entities::empty();
let response =
self.authorizer
- .is_authorized(&cedar_request, &self.policies, &entities);
+ .is_authorized(&cedar_request, &self.policies, &self.entities);
+
+ let decision = response.decision();
- matches!(response.decision(), cedar_policy::Decision::Allow)
+ tracing::info!(
+ method = %http_request.method,
+ host = %http_request.host,
+ path = %http_request.path,
+ scheme = %http_request.scheme,
+ protocol = %http_request.protocol,
+ decision = ?decision,
+ diagnostics = ?response.diagnostics(),
+ "Processing HTTP request"
+ );
+
+ matches!(decision, cedar_policy::Decision::Allow)
}
Err(e) => {
tracing::error!(
@@ -113,46 +185,3 @@ impl Authorizer for CedarAuthorizer {
}
}
}
-
-impl CedarAuthorizer {
- fn create_cedar_request(
- &self,
- bearer_token: &str,
- path: &str,
- ) -> Result<CedarRequest, Box<dyn std::error::Error>> {
- // Create principal entity
- let principal_id = EntityId::from_str("client")?;
- let principal_type = EntityTypeName::from_str("User")?;
- let principal = EntityUid::from_type_name_and_id(principal_type, principal_id);
-
- // Create action entity
- let action_id = EntityId::from_str("check")?;
- let action_type = EntityTypeName::from_str("Action")?;
- let action = EntityUid::from_type_name_and_id(action_type, action_id);
-
- // Create resource entity
- let resource_id = EntityId::from_str("resource")?;
- let resource_type = EntityTypeName::from_str("Resource")?;
- let resource = EntityUid::from_type_name_and_id(resource_type, resource_id);
-
- // Create context with bearer token and path
- let mut context_map = HashMap::new();
- if !bearer_token.is_empty() {
- context_map.insert(
- "bearer_token".to_string(),
- RestrictedExpression::from_str(&format!("\"{bearer_token}\""))?,
- );
- }
- if !path.is_empty() {
- context_map.insert(
- "path".to_string(),
- RestrictedExpression::from_str(&format!("\"{path}\""))?,
- );
- }
-
- let context = Context::from_pairs(context_map.into_iter().collect::<Vec<_>>())?;
-
- CedarRequest::new(principal, action, resource, context, None)
- .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
- }
-}
diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs
index 57f7b5d5..f8c7577f 100644
--- a/src/authorization/check_service.rs
+++ b/src/authorization/check_service.rs
@@ -23,10 +23,8 @@ impl envoy_types::ext_authz::v3::pb::Authorization for CheckService {
request: Request<CheckRequest>,
) -> Result<Response<CheckResponse>, Status> {
if self.authorizer.authorize(request.into_inner()) {
- log::info!("OK");
Ok(Response::new(CheckResponse::with_status(Status::ok("OK"))))
} else {
- log::info!("Unauthorized");
Ok(Response::new(CheckResponse::with_status(
Status::unauthenticated("Unauthorized"),
)))
diff --git a/src/authorization/server.rs b/src/authorization/server.rs
index 23b7720e..759a550d 100644
--- a/src/authorization/server.rs
+++ b/src/authorization/server.rs
@@ -8,13 +8,13 @@ pub struct Server {
}
impl Server {
- pub fn new() -> Result<Server, Box<dyn std::error::Error>> {
+ pub fn new<T: super::Authorizer>(authorizer: T) -> Result<Server, Box<dyn std::error::Error>> {
let (health_reporter, health_service) = tonic_health::server::health_reporter();
std::mem::drop(
health_reporter.set_service_status("", tonic_health::ServingStatus::Serving),
);
let authorization_service =
- AuthorizationServer::new(CheckService::new(Arc::new(CedarAuthorizer::default())));
+ AuthorizationServer::new(CheckService::new(Arc::new(authorizer)));
let reflection_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
@@ -40,9 +40,9 @@ impl Server {
tracing::info!(
method = %req.method(),
path = %req.uri().path(),
- content_type = ?req.headers().get("content-type").and_then(|v| v.to_str().ok()),
- user_agent = ?req.headers().get("user-agent").and_then(|v| v.to_str().ok()),
- x_request_id = ?req.headers().get("x-request-id").and_then(|v| v.to_str().ok()),
+ content_type = req.headers().get("content-type").map_or("unknown", |v| v.to_str().unwrap_or("unknown")),
+ user_agent = req.headers().get("user-agent").map_or("unknown", |v| v.to_str().unwrap_or("unknown")),
+ x_request_id = req.headers().get("x-request-id").map_or("none", |v| v.to_str().unwrap_or("none")),
"gRPC request"
);
@@ -64,6 +64,6 @@ impl Server {
impl Default for Server {
fn default() -> Self {
- Self::new().unwrap()
+ Self::new(CedarAuthorizer::default()).unwrap()
}
}
diff --git a/src/main.rs b/src/main.rs
index 511d3d04..add0d88d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,7 +17,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.parse()?;
tracing::info!(address = %addr, "Starting authorization server");
- let server = authzd::authorization::Server::new()?;
+ let cedar = authzd::authorization::CedarAuthorizer::default();
+ let server = authzd::authorization::Server::new(cedar)?;
server.serve(addr).await?;
Ok(())
diff --git a/tests/authorization/cedar_authorizer_test.rs b/tests/authorization/cedar_authorizer_test.rs
index 79f83c00..490a0107 100644
--- a/tests/authorization/cedar_authorizer_test.rs
+++ b/tests/authorization/cedar_authorizer_test.rs
@@ -5,46 +5,44 @@ mod tests {
use envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest;
use std::collections::HashMap;
+ fn subject() -> authzd::CedarAuthorizer {
+ build_cedar_authorizer()
+ }
+
#[test]
fn test_cedar_authorizer_allows_valid_token() {
- let request = build_request(|item: &mut HttpRequest| {
- item.headers = build_with(|item: &mut HashMap<String, String>| {
- item.insert(
- String::from("authorization"),
- String::from("Bearer valid-token"),
- );
- });
- });
-
- assert!(build_cedar_authorizer().authorize(request));
+ assert!(subject().authorize(build_request(|item: &mut HttpRequest| {
+ item.headers = build_headers(vec![(
+ "authorization".to_string(),
+ "Bearer valid-token".to_string(),
+ )]);
+ })));
}
#[test]
fn test_cedar_authorizer_denies_invalid_token() {
- let request = build_request(|item: &mut HttpRequest| {
- item.headers = build_with(|item: &mut HashMap<String, String>| {
- item.insert(
- String::from("authorization"),
- String::from("Bearer invalid-token"),
- );
- });
- });
-
- assert!(!build_cedar_authorizer().authorize(request));
+ assert!(
+ !subject().authorize(build_request(|item: &mut HttpRequest| {
+ item.headers = build_headers(vec![(
+ "authorization".to_string(),
+ "Bearer invalid-token".to_string(),
+ )]);
+ }))
+ );
}
#[test]
fn test_cedar_authorizer_denies_missing_header() {
- let request = build_request(|item: &mut HttpRequest| {
- item.headers = HashMap::new();
- });
-
- assert!(!build_cedar_authorizer().authorize(request));
+ assert!(
+ !subject().authorize(build_request(|item: &mut HttpRequest| {
+ item.headers = HashMap::new();
+ }))
+ );
}
#[test]
fn test_cedar_authorizer_allows_static_assets() {
- let request = build_request(|item: &mut HttpRequest| {
+ assert!(subject().authorize(build_request(|item: &mut HttpRequest| {
let method = String::from("GET");
let host = String::from("sparkle.staging.runway.gitlab.net");
let path = "/public/style.css";
@@ -57,14 +55,12 @@ mod tests {
(String::from(":method"), method),
(String::from(":authority"), host),
]);
- });
-
- assert!(build_cedar_authorizer().authorize(request));
+ })));
}
#[test]
fn test_cedar_authorizer_allows_js_assets() {
- let request = build_request(|item: &mut HttpRequest| {
+ assert!(subject().authorize(build_request(|item: &mut HttpRequest| {
let method = String::from("GET");
let host = String::from("sparkle.staging.runway.gitlab.net");
let path = "/app.js";
@@ -77,8 +73,6 @@ mod tests {
(String::from(":method"), method),
(String::from(":authority"), host),
]);
- });
-
- assert!(build_cedar_authorizer().authorize(request));
+ })));
}
}
diff --git a/tests/authorization/check_service_test.rs b/tests/authorization/check_service_test.rs
index a4b8f2ee..c5c824fc 100644
--- a/tests/authorization/check_service_test.rs
+++ b/tests/authorization/check_service_test.rs
@@ -14,6 +14,7 @@ mod tests {
#[tokio::test]
async fn test_check_allows_valid_bearer_token() {
let request = tonic::Request::new(build_request(|item: &mut HttpRequest| {
+ item.path = String::from("/");
item.headers = build_headers(vec![(
"authorization".to_string(),
format!("Bearer {}", String::from("valid-token")),
@@ -27,7 +28,7 @@ mod tests {
assert!(check_response.status.is_some());
let status = check_response.status.unwrap();
- assert_eq!(status.code, tonic::Code::Ok as i32);
+ assert_eq!(tonic::Code::from_i32(status.code), tonic::Code::Ok);
}
#[tokio::test]
@@ -43,7 +44,10 @@ mod tests {
assert!(check_response.status.is_some());
let status = check_response.status.unwrap();
- assert_eq!(status.code, tonic::Code::Unauthenticated as i32);
+ assert_eq!(
+ tonic::Code::from_i32(status.code),
+ tonic::Code::Unauthenticated
+ );
}
#[tokio::test]
@@ -79,7 +83,7 @@ mod tests {
assert!(check_response.status.is_some());
let status = check_response.status.unwrap();
- assert_eq!(status.code, tonic::Code::Ok as i32);
+ assert_eq!(tonic::Code::from_i32(status.code), tonic::Code::Ok);
}
}
@@ -94,19 +98,22 @@ mod tests {
assert!(check_response.status.is_some());
let status = check_response.status.unwrap();
- assert_eq!(status.code, tonic::Code::Unauthenticated as i32);
+ assert_eq!(
+ tonic::Code::from_i32(status.code),
+ tonic::Code::Unauthenticated
+ );
}
#[tokio::test]
async fn test_table() {
let test_cases = vec![
- ("Bearer valid-token", true),
- ("Bearer invalid-token", false),
- ("Basic valid-token", false),
- ("", false),
+ ("Bearer valid-token", tonic::Code::Ok),
+ ("Bearer invalid-token", tonic::Code::Unauthenticated),
+ ("Basic valid-token", tonic::Code::Unauthenticated),
+ ("", tonic::Code::Unauthenticated),
];
- for (auth_value, should_succeed) in test_cases {
+ for (auth_value, expected_status_code) in test_cases {
let request = tonic::Request::new(build_request(|item: &mut HttpRequest| {
item.headers =
build_headers(vec![("authorization".to_string(), auth_value.to_string())]);
@@ -118,70 +125,64 @@ mod tests {
let check_response = response.unwrap().into_inner();
let status = check_response.status.unwrap();
- if should_succeed {
- assert_eq!(status.code, tonic::Code::Ok as i32);
- } else {
- assert_eq!(status.code, tonic::Code::Unauthenticated as i32);
- }
+ assert_eq!(tonic::Code::from_i32(status.code), expected_status_code);
}
}
#[tokio::test]
async fn test_public_sparkle_endpoints() {
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/application.js"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/callback"}},
// {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/dashboard", Headers: loggedInHeaders}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/dashboard/nav"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/favicon.ico"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/favicon.png"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/favicon.png"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/health"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/htmx.js"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/index.html"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/logo.png"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/pico.min.css"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/signout"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/sparkles"}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "GET", Path: "/vue.global.js"}},
// {status: tonic::Code::Ok, http: &HTTPRequest{Method: "POST", Path: "/sparkles", Headers: loggedInHeaders}},
- // {status: tonic::Code::Ok, http: &HTTPRequest{Method: "POST", Path: "/sparkles/restore"}},
- // {status: tonic::Code::PermissionDenied, http: &HTTPRequest{Method: "GET", Path: "/dashboard"}},
// {status: tonic::Code::PermissionDenied, http: &HTTPRequest{Method: "GET", Path: "/dashboard", Headers: invalidHeaders}},
- // {status: tonic::Code::PermissionDenied, http: &HTTPRequest{Method: "POST", Path: "/sparkles"}},
- //
- // http:
- // method: \"GET\",
- // headers: {
- // \":method\": \"GET\",
- // \":authority\": \"localhost:10000\",
- // \":path\": \"/sparkles\",
- // },
- // path: \"/sparkles\",
- // host: \"localhost:10000\",
-
- let request = tonic::Request::new(build_request(|item: &mut HttpRequest| {
- let path = String::from("/");
- let method = String::from("GET");
- let host = String::from("sparkle.staging.runway.gitlab.net");
-
- item.method = method.clone();
- item.path = path.clone();
- item.host = host.clone();
- item.headers = build_headers(vec![
- (String::from(":path"), path),
- (String::from(":method"), method),
- (String::from(":authority"), host),
- ]);
- }));
- let response = subject().check(request).await;
- assert!(response.is_ok());
+ let hosts = vec![
+ "localhost:10000",
+ "sparkle.runway.gitlab.net",
+ "sparkle.staging.runway.gitlab.net",
+ ];
- let check_response = response.unwrap().into_inner();
- assert!(check_response.status.is_some());
+ let routes = vec![
+ ("GET", "/", tonic::Code::Ok),
+ ("GET", "/application.js", tonic::Code::Ok),
+ ("GET", "/callback", tonic::Code::Ok),
+ ("GET", "/dashboard/nav", tonic::Code::Ok),
+ ("GET", "/favicon.ico", tonic::Code::Ok),
+ ("GET", "/favicon.png", tonic::Code::Ok),
+ ("GET", "/health", tonic::Code::Ok),
+ ("GET", "/htmx.js", tonic::Code::Ok),
+ ("GET", "/index.html", tonic::Code::Ok),
+ ("GET", "/logo.png", tonic::Code::Ok),
+ ("GET", "/pico.min.css", tonic::Code::Ok),
+ ("GET", "/signout", tonic::Code::Ok),
+ ("GET", "/sparkles", tonic::Code::Ok),
+ ("GET", "/vue.global.js", tonic::Code::Ok),
+ ("POST", "/sparkles/restore", tonic::Code::Ok),
+ ("GET", "/dashboard", tonic::Code::Unauthenticated),
+ ("POST", "/sparkles", tonic::Code::Unauthenticated),
+ ];
- let status = check_response.status.unwrap();
- assert_eq!(status.code, tonic::Code::Ok as i32);
+ for host in hosts {
+ for (method, path, expected_status_code) in &routes {
+ let request = tonic::Request::new(build_request(|item: &mut HttpRequest| {
+ item.method = method.to_string();
+ item.path = path.to_string();
+ item.host = host.to_string();
+ item.headers = build_headers(vec![
+ (String::from(":path"), path.to_string()),
+ (String::from(":method"), method.to_string()),
+ (String::from(":authority"), host.to_string()),
+ ]);
+ }));
+
+ let response = subject().check(request).await;
+ assert!(response.is_ok());
+
+ let check_response = response.unwrap().into_inner();
+ assert!(check_response.status.is_some());
+
+ let status = check_response.status.unwrap();
+ assert_eq!(tonic::Code::from_i32(status.code), *expected_status_code);
+ }
+ }
}
}
diff --git a/tests/support/factory_bot.rs b/tests/support/factory_bot.rs
index 15c6f1f3..007f0cb7 100644
--- a/tests/support/factory_bot.rs
+++ b/tests/support/factory_bot.rs
@@ -7,7 +7,7 @@ use tonic::transport::Channel;
#[allow(dead_code)]
pub fn build<T: Default>() -> T {
- return please::build();
+ please::build()
}
pub fn build_with<T, F>(initializer: F) -> T
@@ -15,7 +15,7 @@ where
T: Default,
F: std::ops::FnOnce(&mut T),
{
- return please::build_with(initializer);
+ please::build_with(initializer)
}
pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest {
@@ -29,17 +29,17 @@ pub fn build_request(f: impl std::ops::FnOnce(&mut HttpRequest)) -> CheckRequest
}
pub fn build_headers(headers: Vec<(String, String)>) -> HashMap<String, String> {
- return build_with(|item: &mut HashMap<String, String>| {
+ build_with(|item: &mut HashMap<String, String>| {
for (key, value) in headers {
item.insert(key, value);
}
- });
+ })
}
pub fn build_cedar_authorizer() -> authzd::CedarAuthorizer {
let realpath = std::fs::canonicalize("./etc/authzd").unwrap();
let path = realpath.as_path();
- authzd::CedarAuthorizer::new_from(path)
+ authzd::CedarAuthorizer::new_from(path, cedar_policy::Entities::empty())
}
pub async fn build_channel(addr: SocketAddr) -> Channel {