Skip to content

Commit

Permalink
Fix RBAC check for missing role in JWT validation test
Browse files Browse the repository at this point in the history
- Ensure JWT_SECRET is properly loaded from the environment during testing.
- Use the correct JWT_SECRET for both token generation and validation in .
- Add dotenv loading to ensure consistency with environment variables.
  • Loading branch information
Mehrn0ush committed Oct 5, 2024
1 parent f1c463f commit 950a8c6
Show file tree
Hide file tree
Showing 22 changed files with 305 additions and 341 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
REDIS_URL=redis://127.0.0.1:6379
JWT_PRIVATE_KEY=/path/to/your/private_key.pem
JWT_SIGNING_ALGORITHM="Dilithium"
JWT_SECRET="yoursecret"
tls_key_exchange_algorithm="Kyber"
CLIENT_CERT_PATH=./path/to/client_cert.pem
CLIENT_KEY_PATH=./path/to/client_key.pem
Expand Down
2 changes: 0 additions & 2 deletions src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use async_trait::async_trait;
pub mod mock;
pub mod rbac;


#[derive(Debug, Clone)]
pub struct User {
pub id: String,
Expand All @@ -29,4 +28,3 @@ pub trait SessionManager: Send + Sync {
pub trait UserAuthenticator: Send + Sync {
async fn authenticate(&self, username: &str, password: &str) -> Result<User, AuthError>;
}

123 changes: 80 additions & 43 deletions src/auth/rbac.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm, TokenData};
use crate::auth::mock::{MockUserAuthenticator, MockSessionManager};
use std::collections::HashSet;
use crate::auth::mock::{MockSessionManager, MockUserAuthenticator};
use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::env;


/// Struct representing the JWT claims.
/// Adjust the fields based on your actual JWT structure.
#[derive(Debug, Deserialize)]
pub struct Claims {
pub sub: String, // Subject (user identifier)
pub exp: i64, // Expiration time as UNIX timestamp
pub roles: Vec<String>, // Roles assigned to the user
pub sub: String, // Subject (user identifier)
pub exp: i64, // Expiration time as UNIX timestamp
pub roles: Vec<String>, // Roles assigned to the user
}

/// Enum representing possible RBAC errors.
Expand All @@ -27,35 +26,13 @@ pub enum RbacError {
/// Result type alias for RBAC operations.
pub type RbacResult<T> = Result<T, RbacError>;

/// Performs a Role-Based Access Control (RBAC) check.
///
/// # Arguments
///
/// * `token` - The bearer token to validate.
/// * `required_role` - The role required to perform the action.
///
/// # Returns
///
/// * `Ok(())` if the token is valid and contains the required role.
/// * `Err(RbacError)` otherwise.
///
/// # Example
///
/// ```
/// use crate::authentication::rbac::{rbac_check, RbacError};
///
/// let token = "valid_jwt_token_here";
/// match rbac_check(token, "admin") {
/// Ok(_) => println!("Access granted"),
/// Err(e) => println!("Access denied: {:?}", e),
/// }
/// ```
pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> {
// Retrieve the JWT secret from environment variables
let secret = env::var("JWT_SECRET").map_err(|_| RbacError::MissingJwtSecret)?;
println!("Using JWT_SECRET: {}", secret); // Log the secret

// Define the validation parameters
let mut validation = Validation::new(Algorithm::HS256);
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;

// Create a HashSet of required claims
Expand All @@ -68,11 +45,14 @@ pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> {
token,
&DecodingKey::from_secret(secret.as_ref()),
&validation,
).map_err(|err| match *err.kind() {
)
.map_err(|err| match *err.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => RbacError::ExpiredToken,
_ => RbacError::InvalidToken,
})?;

println!("Decoded token claims: {:?}", token_data.claims); // Log claims

// Check if the required role is present
if token_data.claims.roles.contains(&required_role.to_string()) {
Ok(())
Expand All @@ -81,7 +61,6 @@ pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> {
}
}


/// Helper function to extract roles from a token.
/// This can be used if you need to access roles beyond RBAC checks.
pub fn extract_roles(token: &str) -> RbacResult<Vec<String>> {
Expand All @@ -102,7 +81,8 @@ pub fn extract_roles(token: &str) -> RbacResult<Vec<String>> {
token,
&DecodingKey::from_secret(secret.as_ref()),
&validation,
).map_err(|err| match *err.kind() {
)
.map_err(|err| match *err.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => RbacError::ExpiredToken,
// Map additional error kinds to InvalidToken
jsonwebtoken::errors::ErrorKind::InvalidToken
Expand All @@ -118,11 +98,10 @@ pub fn extract_roles(token: &str) -> RbacResult<Vec<String>> {
Ok(token_data.claims.roles)
}


#[cfg(test)]
mod tests {
use super::*;
use jsonwebtoken::{encode, Header, EncodingKey};
use jsonwebtoken::{encode, EncodingKey, Header};
use std::env;

#[derive(Debug, Serialize)]
Expand All @@ -138,23 +117,20 @@ mod tests {
encode(&header, &claims, &EncodingKey::from_secret(secret.as_ref())).unwrap()
}



#[test]
fn test_rbac_check_invalid_token() {
// Set the JWT_SECRET environment variable for testing
env::set_var("JWT_SECRET", "test_secret");

let invalid_token = "invalid.token.value";

let result = rbac_check(invalid_token, "admin");
assert!(matches!(result, Err(RbacError::InvalidToken)));

// Clean up
env::remove_var("JWT_SECRET");
}


#[test]
fn test_extract_roles_invalid_token() {
// Set the JWT_SECRET environment variable for testing
Expand All @@ -168,4 +144,65 @@ mod tests {
// Clean up
env::remove_var("JWT_SECRET");
}
}

#[test]
fn test_rbac_check_expired_token() {
dotenv::dotenv().ok();

// Ensure JWT_SECRET is set in the environment
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env");

// Set the expiration time to a timestamp in the past
let past_timestamp = (chrono::Utc::now() - chrono::Duration::seconds(3600)).timestamp();

// Create claims for the test token
let claims = TestClaims {
sub: "user123".to_string(),
exp: past_timestamp, // Expired token
roles: vec!["admin".to_string()],
};

// Generate a test token using the JWT_SECRET
let token = generate_test_token(claims, &jwt_secret);

// Perform the RBAC check
let result = rbac_check(&token, "admin");

// Assert that the token is marked as expired
assert!(
matches!(result, Err(RbacError::ExpiredToken)),
"Expected ExpiredToken, but got: {:?}",
result
);
}
#[test]
fn test_rbac_check_missing_role() {
dotenv::dotenv().ok(); // Load environment variables from .env

// Ensure JWT_SECRET is set in the environment
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env");

// Define the claims with the role "user" and a valid future expiration time
let claims = TestClaims {
sub: "user123".to_string(),
exp: 9999999999, // Future expiration time
roles: vec!["user".to_string()], // Only 'user' role
};

// Generate the test JWT token using the JWT_SECRET
let token = generate_test_token(claims, &jwt_secret);

// Run the RBAC check for the "admin" role
let result = rbac_check(&token, "admin");

// Debugging output to track the result
println!("RBAC check result: {:?}", result);

// Ensure that the result matches the expected InsufficientRole error
assert!(
matches!(result, Err(RbacError::InsufficientRole)),
"Expected InsufficientRole, but got: {:?}",
result
);
}
}
3 changes: 1 addition & 2 deletions src/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ pub enum AuthError {
InvalidCredentials,
SessionNotFound,
InternalError,
OAuthErrorResponse
// Add other error variants as needed
OAuthErrorResponse, // Add other error variants as needed
}

/*
Expand Down
6 changes: 4 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ pub struct OAuthConfig {
impl OAuthConfig {
pub fn from_env() -> Self {
let google_client_id = env::var("GOOGLE_CLIENT_ID").expect("GOOGLE_CLIENT_ID must be set");
let google_client_secret = env::var("GOOGLE_CLIENT_SECRET").expect("GOOGLE_CLIENT_SECRET must be set");
let google_redirect_uri = env::var("GOOGLE_REDIRECT_URI").expect("GOOGLE_REDIRECT_URI must be set");
let google_client_secret =
env::var("GOOGLE_CLIENT_SECRET").expect("GOOGLE_CLIENT_SECRET must be set");
let google_redirect_uri =
env::var("GOOGLE_REDIRECT_URI").expect("GOOGLE_REDIRECT_URI must be set");

OAuthConfig {
google_client_id,
Expand Down
4 changes: 2 additions & 2 deletions src/core/client_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use crate::error::OAuthError;
use crate::jwt::generate_jwt;
use crate::jwt::SigningAlgorithm;
use crate::storage::{ClientData, StorageBackend};
use serde::{Deserialize, Serialize};
use rustls_pemfile::private_key;
use std::time::{Duration, SystemTime};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::{Duration, SystemTime};

/// Validates client credentials by checking against storage (e.g., Redis, SQL).
///
Expand Down
18 changes: 11 additions & 7 deletions src/endpoints/client_credentials.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use crate::error::{OAuthError, OAuthErrorResponse };
use crate::core::client_credentials::{issue_token, validate_client_credentials, TokenResponse};
use crate::core::types::TokenRequest;
use crate::error::{OAuthError, OAuthErrorResponse};
use crate::storage::{ClientData, StorageBackend};
use actix_web::{web, HttpResponse};
use serde::Deserialize;
use std::sync::Arc;



#[derive(Deserialize)]
pub struct ClientCredentialsRequest {
pub grant_type: String,
Expand All @@ -16,7 +14,6 @@ pub struct ClientCredentialsRequest {
pub scope: Option<String>,
}


/// Handles the client credentials grant type for OAuth2.
///
/// Validates client credentials and issues an access token if valid.
Expand All @@ -34,7 +31,10 @@ pub async fn handle_client_credentials(
storage: web::Data<Arc<dyn StorageBackend>>, // Inject storage backend
) -> HttpResponse {
// Log the incoming request (avoid logging sensitive information in production)
log::info!("Handling client credentials for client_id: {}", req.client_id);
log::info!(
"Handling client credentials for client_id: {}",
req.client_id
);

// Validate the request parameters
if req.grant_type != "client_credentials" {
Expand All @@ -57,7 +57,11 @@ pub async fn handle_client_credentials(
.collect();

// Call core functions to validate client and issue token
match validate_client_credentials(&req.client_id, &req.client_secret, storage.as_ref().as_ref()) {
match validate_client_credentials(
&req.client_id,
&req.client_secret,
storage.as_ref().as_ref(),
) {
Ok(client) => match issue_token(&client, &scopes) {
Ok(token_response) => {
log::info!("Issued token for client_id: {}", req.client_id);
Expand All @@ -75,4 +79,4 @@ pub async fn handle_client_credentials(
HttpResponse::Unauthorized().json(error_response)
}
}
}
}
17 changes: 7 additions & 10 deletions src/endpoints/delete.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@


use crate::auth::rbac::rbac_check; // Adjust the path based on your project structure
use crate::core::token::TokenStore;
use crate::endpoints::register::Client;
use crate::auth::rbac::rbac_check; // Adjust the path based on your project structure
use actix_web::{web, HttpResponse, Responder, HttpRequest};
use crate::endpoints::update::ClientStore;
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use serde::{Deserialize, Serialize};
use std::sync::RwLock;
use crate::endpoints::update::ClientStore; // Ensure the correct import path
use std::sync::RwLock; // Ensure the correct import path

/// Response structure for successful client deletion.
#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -58,11 +56,11 @@ pub async fn delete_client_handler<T: TokenStore>(
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, App};
use crate::core::token::InMemoryTokenStore;
use std::sync::RwLock;
use crate::endpoints::register::{ClientStore, Client};
use crate::endpoints::register::{Client, ClientStore};
use actix_web::{test, App};
use serde_json::json;
use std::sync::RwLock;

/// Helper function to create a sample client for testing.
fn create_sample_client(client_id: &str) -> Client {
Expand All @@ -76,5 +74,4 @@ mod tests {
tbid: None,
}
}

}
Loading

0 comments on commit 950a8c6

Please sign in to comment.