// This software is available under the Apache-2.0 license.
// See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
//
// Copyright (c) 2024, Gluu, Inc.
//! Integration tests for SSA JWT validation functionality
use crate::log::StdOutLoggerMode;
use crate::{
AuthorizationConfig, BootstrapConfig, Cedarling, DataStoreConfig, JwtConfig, LockServiceConfig,
LockTransport, LogConfig, LogLevel, LogTypeConfig, PolicyStoreConfig, PolicyStoreSource,
};
use serde_json::json;
use std::collections::HashSet;
use std::time::Duration;
static POLICY_STORE_RAW: &str = include_str!("../../../test_files/policy-store_ok.yaml");
// Valid SSA JWT for testing
const VALID_SSA_JWT: &str = "eyJraWQiOiJzc2FfMmRmMGNkZDUtNTU2Yi00ZDRlLTkzNjItNjc2Mjk1NjEzMzMxX3NpZ19yczI1NiIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzb2Z0d2FyZV9pZCI6ImNlZGFybGluZyIsImdyYW50X3R5cGVzIjpbImNsaWVudF9jcmVkZW50aWFscyJdLCJvcmdfaWQiOiJteV9vcmciLCJpc3MiOiJodHRwczovL2RlbW9leGFtcGxlLmphbnMuaW8iLCJsaWZldGltZSI6MTU3Njc5OTk5OSwic29mdHdhcmVfcm9sZXMiOlsiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svbG9nLndyaXRlIiwiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svaGVhbHRoLndyaXRlIiwiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svdGVsZW1ldHJ5LndyaXRlIl0sImV4cCI6MzMyMzA4NjI3NSwiaWF0IjoxNzQ2Mjg2Mjc2LCJqdGkiOiJkM2EwODI1Yi1kZjFhLTQ3ZTYtYmQ5MC0yMTk2NTkyYTVlNGQifQ.KxMQyxDZ3zsZDHj5OjZdAWi3J8fuYdxMbl2NC3fzS0e308Zd_t8CvtFX0F3edYAwvy3mdnva_MxKkxgSsXGniv2UfiFj7p8gKaqybYB4ngb1mX1BZCJZ27M0K5g9H3pa4g3csKp_UHjmV2LBHePkr3RA343E9ezDtR-4WwxQwJ6Lq_dmdXvtMW5iQLE9SsDT0f6rPNs2jt1mx-_PjT3mpOo2NG7mMB1TPX_runu53PqnJ844QoZNa1yjjIhJUGxLzE-DH8t6pjwiatnd1kDS7jDhfAn41l-t29IraIpYKTmPEGNxKd6EkIr-j7Si54HPIJZoZXY8E-UEnLHwDNo7hQ";
#[tokio::test]
async fn test_cedarling_with_valid_ssa() {
// This test verifies that Cedarling can start successfully with a valid SSA JWT
let lock_config = LockServiceConfig {
log_level: LogLevel::TRACE,
config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
.parse()
.unwrap(),
dynamic_config: false,
ssa_jwt: Some(VALID_SSA_JWT.to_string()),
log_interval: Some(Duration::from_secs(3)),
health_interval: None,
telemetry_interval: None,
listen_sse: false,
accept_invalid_certs: true,
transport: LockTransport::Rest,
..Default::default()
};
let result = Cedarling::new(&BootstrapConfig {
application_name: "test_app".to_string(),
log_config: LogConfig {
log_type: LogTypeConfig::StdOut(StdOutLoggerMode::Immediate),
log_level: LogLevel::INFO,
},
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
..Default::default()
},
jwt_config: JwtConfig {
jwks: None,
jwt_sig_validation: false,
jwt_status_validation: false,
signature_algorithms_supported: HashSet::new(),
..Default::default()
}
.allow_all_algorithms(),
authorization_config: AuthorizationConfig {
decision_log_default_jwt_id: "jti".to_string(),
strict_schema_validation: true,
},
lock_config: Some(lock_config),
max_default_entities: None,
max_base64_size: None,
data_store_config: DataStoreConfig::default(),
http_client_config: crate::HttpClientConfig::default(),
})
.await;
// Note: This test will fail in a real environment because it can't connect to the actual lock server
// In a real integration test, you would need to mock the lock server endpoints
// For now, we just verify that the configuration is accepted
assert!(result.is_ok() || result.is_err()); // Either success or network error is acceptable
}
#[tokio::test]
async fn test_cedarling_without_ssa() {
// This test verifies that Cedarling can start successfully without an SSA JWT
let lock_config = LockServiceConfig {
log_level: LogLevel::TRACE,
config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
.parse()
.unwrap(),
dynamic_config: false,
ssa_jwt: None,
log_interval: Some(Duration::from_secs(3)),
health_interval: None,
telemetry_interval: None,
listen_sse: false,
accept_invalid_certs: true,
transport: LockTransport::Rest,
..Default::default()
};
let result = Cedarling::new(&BootstrapConfig {
application_name: "test_app".to_string(),
log_config: LogConfig {
log_type: LogTypeConfig::StdOut(StdOutLoggerMode::Immediate),
log_level: LogLevel::INFO,
},
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
..Default::default()
},
jwt_config: JwtConfig {
jwks: None,
jwt_sig_validation: false,
jwt_status_validation: false,
signature_algorithms_supported: HashSet::new(),
..Default::default()
}
.allow_all_algorithms(),
authorization_config: AuthorizationConfig {
decision_log_default_jwt_id: "jti".to_string(),
strict_schema_validation: true,
},
lock_config: Some(lock_config),
max_default_entities: None,
max_base64_size: None,
data_store_config: DataStoreConfig::default(),
http_client_config: crate::HttpClientConfig::default(),
})
.await;
// Note: This test will fail in a real environment because it can't connect to the actual lock server
// In a real integration test, you would need to mock the lock server endpoints
// For now, we just verify that the configuration is accepted
assert!(result.is_ok() || result.is_err()); // Either success or network error is acceptable
}
#[tokio::test]
async fn test_ssa_validation_structure() {
// Test SSA JWT structure validation
use crate::jwt::{DecodedJwt, DecodedJwtClaims, DecodedJwtHeader};
use crate::lock::ssa_validation::{
SsaValidationConfig, SsaValidationError, validate_ssa_structure_with_config,
};
use jsonwebtoken::Algorithm;
// Test valid SSA structure
let valid_claims = json!({
"software_id": "test_software",
"grant_types": ["client_credentials"],
"org_id": "test_org",
"iss": "https://test.issuer.com",
"software_roles": ["cedarling"],
"exp": 1_735_689_600,
"iat": 1_735_603_200,
"jti": "test-jti-123"
});
let valid_decoded_jwt = DecodedJwt {
header: DecodedJwtHeader {
typ: Some("JWT".to_string()),
alg: Algorithm::RS256,
cty: None,
kid: Some("test-kid".to_string()),
},
claims: DecodedJwtClaims {
inner: valid_claims,
},
};
let config = SsaValidationConfig::default();
let result = validate_ssa_structure_with_config(&valid_decoded_jwt, &config);
assert!(result.is_ok());
// Test missing required claims
let invalid_claims = json!({
"software_id": "test_software",
"grant_types": ["client_credentials"],
// Missing org_id, iss, software_roles, exp, iat, jti
});
let invalid_decoded_jwt = DecodedJwt {
header: DecodedJwtHeader {
typ: Some("JWT".to_string()),
alg: Algorithm::RS256,
cty: None,
kid: Some("test-kid".to_string()),
},
claims: DecodedJwtClaims {
inner: invalid_claims,
},
};
let result = validate_ssa_structure_with_config(&invalid_decoded_jwt, &config);
assert!(matches!(
result,
Err(SsaValidationError::MissingRequiredClaims(_))
));
// Test invalid grant_types (not an array)
let invalid_grant_types_claims = json!({
"software_id": "test_software",
"grant_types": "client_credentials", // Should be array
"org_id": "test_org",
"iss": "https://test.issuer.com",
"software_roles": ["cedarling"],
"exp": 1_735_689_600,
"iat": 1_735_603_200,
"jti": "test-jti-123"
});
let invalid_grant_types_jwt = DecodedJwt {
header: DecodedJwtHeader {
typ: Some("JWT".to_string()),
alg: Algorithm::RS256,
cty: None,
kid: Some("test-kid".to_string()),
},
claims: DecodedJwtClaims {
inner: invalid_grant_types_claims,
},
};
let result = validate_ssa_structure_with_config(&invalid_grant_types_jwt, &config);
assert!(matches!(result, Err(SsaValidationError::InvalidGrantTypes)));
}
#[tokio::test]
async fn test_ssa_jwt_decode() {
// Test SSA JWT decoding
use crate::jwt::decode_jwt;
// Test decoding a valid JWT structure
let valid_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
let result = decode_jwt(valid_jwt);
assert!(result.is_ok());
// Test decoding an invalid JWT structure
let invalid_jwt = "invalid.jwt.token";
let result = decode_jwt(invalid_jwt);
assert!(result.is_err());
}
#[tokio::test]
async fn test_ssa_configuration_validation() {
// Test that the SSA configuration is properly validated
let config = LockServiceConfig {
log_level: LogLevel::TRACE,
config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
.parse()
.unwrap(),
dynamic_config: false,
ssa_jwt: Some(VALID_SSA_JWT.to_string()),
log_interval: Some(Duration::from_secs(3)),
health_interval: None,
telemetry_interval: None,
listen_sse: false,
accept_invalid_certs: true,
transport: LockTransport::Rest,
..Default::default()
};
// Verify that the SSA JWT is properly set
assert!(config.ssa_jwt.is_some());
assert_eq!(config.ssa_jwt.as_ref().unwrap(), VALID_SSA_JWT);
// Test configuration without SSA
let config_without_ssa = LockServiceConfig {
log_level: LogLevel::TRACE,
config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
.parse()
.unwrap(),
dynamic_config: false,
ssa_jwt: None,
log_interval: Some(Duration::from_secs(3)),
health_interval: None,
telemetry_interval: None,
listen_sse: false,
accept_invalid_certs: true,
transport: LockTransport::Rest,
..Default::default()
};
// Verify that the SSA JWT is not set
assert!(config_without_ssa.ssa_jwt.is_none());
}