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
646 changes: 347 additions & 299 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ version = "0.1.0"
edition = "2024"
license = "Apache-2.0"
authors = ["Artem Goncharov (gtema)"]
rust-version = "1.85" # MSRV
rust-version = "1.90" # MSRV
repository = "https://github.com/gtema/keystone"

[package.metadata.clippy]
Expand All @@ -26,7 +26,7 @@ harness = false
[dependencies]
async-trait = { version = "0.1" }
axum = { version = "0.8", features = ["macros"] }
base64 = "0.22"
base64 = { version = "0.22" }
bcrypt = { version = "0.17", features = ["alloc"] }
bytes = { version = "1.10" }
chrono = { version = "0.4" }
Expand All @@ -38,10 +38,11 @@ dyn-clone = { version = "1.0" }
eyre = { version = "0.6" }
fernet = { version = "0.2", default-features = false, features = ["rustcrypto"] }
futures-util = { version = "0.3" }
itertools = { version = "0.14" }
mockall_double = { version = "0.3" }
opa-wasm = { version = "^0.1", optional = true }
openidconnect = { version = "4.0" }
regex = { version = "1.11"}
regex = { version = "1.12" }
reqwest = { version = "0.12", features = ["json", "http2", "gzip", "deflate"] }
rmp = { version = "0.8" }
schemars = { version = "1.0" }
Expand Down Expand Up @@ -71,7 +72,7 @@ criterion = { version = "0.7", features = ["async_tokio"] }
http-body-util = "0.1"
hyper = { version = "1.7", features = ["http1"] }
hyper-util = { version = "0.1", features = ["tokio", "http1"] }
keycloak = { version = "26.3" }
keycloak = { version = "26.4" }
mockall = { version = "0.13" }
reqwest = { version = "0.12", features = ["json", "multipart"] }
sea-orm = { version = "1.1", features = ["mock"]}
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
################
##### Builder
FROM rust:1.89.0-slim-bookworm AS builder
FROM rust:1.90.0-slim-bookworm AS builder

#RUN rustup target add x86_64-unknown-linux-gnu &&\
RUN apt update &&\
Expand Down
7 changes: 3 additions & 4 deletions benches/fernet_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tempfile::tempdir;

use openstack_keystone::config::Config;
use openstack_keystone::token::fernet::FernetTokenProvider;
use openstack_keystone::token::types::TokenBackend;
//use openstack_keystone::token::types::TokenBackend;

fn decode(backend: &FernetTokenProvider, token: &str) {
backend.decrypt(token).unwrap();
Expand All @@ -24,10 +24,9 @@ fn bench_decrypt_token(c: &mut Criterion) {
.set_override("database.connection", "dummy")
.unwrap();
let mut config: Config = Config::try_from(builder).expect("can build a valid config");
let mut backend = FernetTokenProvider::default();

config.fernet_tokens.key_repository = tmp_dir.keep();
backend.set_config(config);

let mut backend = FernetTokenProvider::new(config.clone());
backend.load_keys().unwrap();

let token = "gAAAAABns2ixy75K_KfoosWLrNNqG6KW8nm3Xzv0_2dOx8ODWH7B8i2g8CncGLO6XBEH_TYLg83P6XoKQ5bU8An8Kqgw9WX3bvmEQXphnwPM6aRAOQUSdVhTlUm_8otDG9BS2rc70Q7pfy57S3_yBgimy-174aKdP8LPusvdHZsQPEJO9pfeXWw";
Expand Down
48 changes: 24 additions & 24 deletions src/api/v3/auth/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,31 @@ async fn authenticate_request(
.await?,
);
}
} else if method == "token" {
if let Some(token) = &req.auth.identity.token {
let mut authz = state
} else if method == "token"
&& let Some(token) = &req.auth.identity.token
{
let mut authz = state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?;
// Resolve the user
authz.user = Some(
state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?;
// Resolve the user
authz.user = Some(
state
.provider
.get_identity_provider()
.get_user(&state.db, &authz.user_id)
.await
.map(|x| {
x.ok_or_else(|| KeystoneApiError::NotFound {
resource: "user".into(),
identifier: authz.user_id.clone(),
})
})??,
);
authenticated_info = Some(authz);

{}
}
.get_identity_provider()
.get_user(&state.db, &authz.user_id)
.await
.map(|x| {
x.ok_or_else(|| KeystoneApiError::NotFound {
resource: "user".into(),
identifier: authz.user_id.clone(),
})
})??,
);
authenticated_info = Some(authz);

{}
}
}
authenticated_info
Expand Down
48 changes: 24 additions & 24 deletions src/api/v4/auth/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,31 @@ async fn authenticate_request(
.await?,
);
}
} else if method == "token" {
if let Some(token) = &req.auth.identity.token {
let mut authz = state
} else if method == "token"
&& let Some(token) = &req.auth.identity.token
{
let mut authz = state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?;
// Resolve the user
authz.user = Some(
state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?;
// Resolve the user
authz.user = Some(
state
.provider
.get_identity_provider()
.get_user(&state.db, &authz.user_id)
.await
.map(|x| {
x.ok_or_else(|| KeystoneApiError::NotFound {
resource: "user".into(),
identifier: authz.user_id.clone(),
})
})??,
);
authenticated_info = Some(authz);

{}
}
.get_identity_provider()
.get_user(&state.db, &authz.user_id)
.await
.map(|x| {
x.ok_or_else(|| KeystoneApiError::NotFound {
resource: "user".into(),
identifier: authz.user_id.clone(),
})
})??,
);
authenticated_info = Some(authz);

{}
}
}
authenticated_info
Expand Down
76 changes: 38 additions & 38 deletions src/api/v4/federation/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ pub(super) fn validate_bound_claims(
claims: &IdTokenClaims<AllOtherClaims, CoreGenderClaim>,
claims_as_json: &Value,
) -> Result<(), OidcError> {
if let Some(bound_subject) = &mapping.bound_subject {
if bound_subject != claims.subject().as_str() {
return Err(OidcError::BoundSubjectMismatch {
expected: bound_subject.to_string(),
found: claims.subject().as_str().into(),
});
}
if let Some(bound_subject) = &mapping.bound_subject
&& bound_subject != claims.subject().as_str()
{
return Err(OidcError::BoundSubjectMismatch {
expected: bound_subject.to_string(),
found: claims.subject().as_str().into(),
});
}
if let Some(bound_audiences) = &mapping.bound_audiences {
let mut bound_audiences_match: bool = false;
Expand All @@ -106,23 +106,23 @@ pub(super) fn validate_bound_claims(
});
}
}
if let Some(bound_claims) = &mapping.bound_claims {
if let Some(required_claims) = bound_claims.as_object() {
for (claim, value) in required_claims.iter() {
if !claims_as_json
.get(claim)
.map(|x| x == value)
.is_some_and(|val| val)
{
return Err(OidcError::BoundClaimsMismatch {
claim: claim.to_string(),
expected: value.to_string(),
found: claims_as_json
.get(claim)
.map(|x| x.to_string())
.unwrap_or_default(),
});
}
if let Some(bound_claims) = &mapping.bound_claims
&& let Some(required_claims) = bound_claims.as_object()
{
for (claim, value) in required_claims.iter() {
if !claims_as_json
.get(claim)
.map(|x| x == value)
.is_some_and(|val| val)
{
return Err(OidcError::BoundClaimsMismatch {
claim: claim.to_string(),
expected: value.to_string(),
found: claims_as_json
.get(claim)
.map(|x| x.to_string())
.unwrap_or_default(),
});
}
}
}
Expand Down Expand Up @@ -192,20 +192,20 @@ pub(super) async fn map_user_data(
.ok_or(OidcError::UserDomainUnbound)?,
);

if let Some(groups_claim) = &mapping.groups_claim {
if let Some(group_names_data) = &claims_as_json.get(groups_claim) {
builder.group_names(
group_names_data
.as_array()
.map(|names| {
names
.iter()
.map(|group| group.as_str().map(|v| v.to_string()))
.collect::<Option<Vec<_>>>()
})
.ok_or(OidcError::GroupsClaimNotArrayOfStrings)?,
);
}
if let Some(groups_claim) = &mapping.groups_claim
&& let Some(group_names_data) = &claims_as_json.get(groups_claim)
{
builder.group_names(
group_names_data
.as_array()
.map(|names| {
names
.iter()
.map(|group| group.as_str().map(|v| v.to_string()))
.collect::<Option<Vec<_>>>()
})
.ok_or(OidcError::GroupsClaimNotArrayOfStrings)?,
);
}

Ok(builder.build()?)
Expand Down
7 changes: 3 additions & 4 deletions src/api/v4/federation/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,14 @@ pub async fn callback(
let claims = id_token
.claims(&client.id_token_verifier(), &Nonce::new(auth_state.nonce))
.map_err(OidcError::from)?;
if let Some(bound_issuer) = &idp.bound_issuer {
if Url::parse(bound_issuer)
if let Some(bound_issuer) = &idp.bound_issuer
&& Url::parse(bound_issuer)
.map_err(OidcError::from)
.wrap_err_with(|| {
format!("while parsing the mapping bound_issuer url: {bound_issuer}")
})?
== *claims.issuer().url()
{}
}
{}

let claims_as_json = serde_json::to_value(claims)?;
debug!("Claims data {claims_as_json}");
Expand Down
18 changes: 9 additions & 9 deletions src/assignment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,15 @@ impl AssignmentApi for AssignmentProvider {
if let Some(uid) = &params.user_id {
actors.push(uid.into());
}
if let Some(true) = &params.effective {
if let Some(uid) = &params.user_id {
let users = provider
.get_identity_provider()
.list_groups_of_user(db, uid)
.await?;
actors.extend(users.into_iter().map(|x| x.id));
};
}
if let Some(true) = &params.effective
&& let Some(uid) = &params.user_id
{
let users = provider
.get_identity_provider()
.list_groups_of_user(db, uid)
.await?;
actors.extend(users.into_iter().map(|x| x.id));
};
if let Some(val) = &params.project_id {
targets.push(RoleAssignmentTarget {
target_id: val.clone(),
Expand Down
16 changes: 8 additions & 8 deletions src/federation/backends/sql/identity_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ impl TryFrom<db_federated_identity_provider::Model> for IdentityProvider {
if let Some(val) = &value.oidc_response_mode {
builder.oidc_response_mode(val);
}
if let Some(val) = &value.oidc_response_types {
if !val.is_empty() {
builder.oidc_response_types(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.oidc_response_types
&& !val.is_empty()
{
builder.oidc_response_types(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.jwks_url {
builder.jwks_url(val);
}
if let Some(val) = &value.jwt_validation_pubkeys {
if !val.is_empty() {
builder.jwt_validation_pubkeys(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.jwt_validation_pubkeys
&& !val.is_empty()
{
builder.jwt_validation_pubkeys(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.bound_issuer {
builder.bound_issuer(val);
Expand Down
24 changes: 12 additions & 12 deletions src/federation/backends/sql/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ impl TryFrom<db_federated_mapping::Model> for Mapping {
db_mapping_type::Oidc => MappingType::Oidc,
db_mapping_type::Jwt => MappingType::Jwt,
});
if let Some(val) = &value.allowed_redirect_uris {
if !val.is_empty() {
builder.allowed_redirect_uris(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.allowed_redirect_uris
&& !val.is_empty()
{
builder.allowed_redirect_uris(Vec::from_iter(val.split(",").map(Into::into)));
}
builder.user_id_claim(value.user_id_claim.clone());
builder.user_name_claim(value.user_name_claim.clone());
Expand All @@ -76,21 +76,21 @@ impl TryFrom<db_federated_mapping::Model> for Mapping {
if let Some(val) = &value.groups_claim {
builder.groups_claim(val);
}
if let Some(val) = &value.bound_audiences {
if !val.is_empty() {
builder.bound_audiences(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.bound_audiences
&& !val.is_empty()
{
builder.bound_audiences(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.bound_subject {
builder.bound_subject(val);
}
if let Some(val) = &value.bound_claims {
builder.bound_claims(val.clone());
}
if let Some(val) = &value.oidc_scopes {
if !val.is_empty() {
builder.oidc_scopes(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.oidc_scopes
&& !val.is_empty()
{
builder.oidc_scopes(Vec::from_iter(val.split(",").map(Into::into)));
}
if let Some(val) = &value.token_project_id {
builder.token_project_id(val.clone());
Expand Down
Loading
Loading