Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ tokio-util = { version = "0.7" }
tower = { version = "0.5" }
tower-http = { version = "0.6", features = ["compression-full", "request-id", "sensitive-headers", "trace", "util"] }
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3" }
tracing-subscriber = { version = "0.3", features = [] }
url = { version = "2.5", features = ["serde"] }
utoipa = { version = "5.4", features = ["axum_extras", "chrono"] }
utoipa-axum = { version = "0.2" }
Expand Down
1 change: 1 addition & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
POLICY_ENTRY_POINTS := \
" -e identity/validate_token" +\
" -e identity/identity_provider_list" +\
" -e identity/identity_provider_show" +\
" -e identity/identity_provider_create" +\
Expand Down
24 changes: 24 additions & 0 deletions policy/federation/token/check.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package identity.check_token

import data.identity

# Update mapping.

default allow := false

allow if {
"admin" in input.credentials.roles
}

allow if {
"reader" in input.credentials.roles
"all" in input.credentials.system_scope
}

allow if {
identity.token_subject
}

allow if {
"service" in input.credentials.roles
}
24 changes: 24 additions & 0 deletions policy/federation/token/validate.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package identity.validate_token

import data.identity

# Update mapping.

default allow := false

allow if {
"admin" in input.credentials.roles
}

allow if {
"reader" in input.credentials.roles
"all" in input.credentials.system_scope
}

allow if {
identity.token_subject
}

allow if {
"service" in input.credentials.roles
}
4 changes: 4 additions & 0 deletions policy/identity.rego
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package identity

token_subject if {
input.credentials.user_id == input.target.token.user_id
}

global_idp if {
not input.target.domain_id
}
Expand Down
17 changes: 15 additions & 2 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use axum::{
};
use utoipa::{
Modify, OpenApi,
openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
openapi::security::{
ApiKey, ApiKeyValue, AuthorizationCode, Flow, OAuth2, Scopes, SecurityScheme,
},
};
use utoipa_axum::{router::OpenApiRouter, routes};

Expand Down Expand Up @@ -54,7 +56,18 @@ impl Modify for SecurityAddon {
components.add_security_scheme(
"x-auth",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("x-auth-token"))),
)
);
// TODO: This must be dynamic
components.add_security_scheme(
"oauth2",
SecurityScheme::OAuth2(OAuth2::new([Flow::AuthorizationCode(
AuthorizationCode::new(
"https://localhost/authorization/token",
"https://localhost/token/url",
Scopes::from_iter([("openid", "default scope")]),
),
)])),
);
}
}
}
Expand Down
46 changes: 37 additions & 9 deletions src/api/v3/auth/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ use axum::{
http::StatusCode,
response::IntoResponse,
};
use mockall_double::double;
use serde_json::{json, to_value};
use tracing::error;
use utoipa_axum::{router::OpenApiRouter, routes};

use crate::api::types::Scope;
Expand All @@ -36,12 +39,14 @@ use crate::auth::{AuthenticatedInfo, AuthzInfo};
use crate::catalog::CatalogApi;
use crate::identity::IdentityApi;
use crate::keystone::ServiceState;
#[double]
use crate::policy::Policy;
use crate::token::TokenApi;

mod common;
pub mod types;

pub(super) fn openapi_router() -> OpenApiRouter<ServiceState> {
pub(crate) fn openapi_router() -> OpenApiRouter<ServiceState> {
OpenApiRouter::new().routes(routes!(show, post))
}

Expand Down Expand Up @@ -159,8 +164,6 @@ async fn post(
let authed_info = authenticate_request(&state, &req).await?;
let authz_info = get_authz_info(&state, &req).await?;

println!("Authz info is {:?}", authz_info);

let mut token = state
.provider
.get_token_provider()
Expand Down Expand Up @@ -209,10 +212,11 @@ async fn post(
#[tracing::instrument(
name = "api::token_get",
level = "debug",
skip(state, headers, _user_auth)
skip(state, headers, user_auth, policy)
)]
async fn show(
Auth(_user_auth): Auth,
Auth(user_auth): Auth,
mut policy: Policy,
Query(query): Query<ValidateTokenParameters>,
headers: HeaderMap,
State(state): State<ServiceState>,
Expand All @@ -224,16 +228,28 @@ async fn show(
.map_err(|_| KeystoneApiError::InvalidHeader)?
.to_string();

// Default behavior is to return 404 for expired tokens. It makes sense to log internally the
// error before mapping it.
let mut token = state
.provider
.get_token_provider()
.validate_token(&subject_token, query.allow_expired, None)
.await
.inspect_err(|e| error!("{:?}", e.to_string()))
.map_err(|_| KeystoneApiError::NotFound {
resource: "token".into(),
identifier: String::new(),
})?;

policy
.enforce(
"identity/validate_token",
&user_auth,
to_value(json!({"token": &token}))?,
None,
)
.await?;

token = state
.provider
.get_token_provider()
Expand Down Expand Up @@ -283,7 +299,7 @@ mod tests {
types::{UserPasswordAuthRequest, UserResponse},
};
use crate::keystone::Service;
use crate::policy::MockPolicyFactory;
use crate::policy::{MockPolicy, MockPolicyFactory, PolicyEvaluationResult};
use crate::provider::Provider;
use crate::resource::{
MockResourceProvider,
Expand All @@ -297,6 +313,18 @@ mod tests {

use super::*;

fn get_policy_factory_mock() -> MockPolicyFactory {
let mut policy_factory_mock = MockPolicyFactory::default();
policy_factory_mock.expect_instantiate().returning(|| {
let mut policy_mock = MockPolicy::default();
policy_mock
.expect_enforce()
.returning(|_, _, _, _| Ok(PolicyEvaluationResult::allowed()));
Ok(policy_mock)
});
policy_factory_mock
}

#[tokio::test]
async fn test_authenticate_request_password() {
let config = Config::default();
Expand Down Expand Up @@ -534,7 +562,7 @@ mod tests {
Config::default(),
DatabaseConnection::Disconnected,
provider,
MockPolicyFactory::new(),
get_policy_factory_mock(),
)
.unwrap(),
);
Expand Down Expand Up @@ -647,7 +675,7 @@ mod tests {
Config::default(),
DatabaseConnection::Disconnected,
provider,
MockPolicyFactory::new(),
get_policy_factory_mock(),
)
.unwrap(),
);
Expand Down Expand Up @@ -708,7 +736,7 @@ mod tests {
Config::default(),
DatabaseConnection::Disconnected,
provider,
MockPolicyFactory::new(),
get_policy_factory_mock(),
)
.unwrap(),
);
Expand Down
Loading