mirror of
https://codeberg.org/pfzetto/axum-oidc
synced 2025-12-08 06:05:16 +01:00
removed integration test from examples/basic
Integrations tests will be re-implemented on the main-crate. See #20 and #35.
This commit is contained in:
parent
c3f4b7e514
commit
00136320a9
7 changed files with 94 additions and 390 deletions
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
|
@ -10,8 +10,8 @@ env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_test:
|
test:
|
||||||
name: axum-oidc - latest
|
name: axum-oidc
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -24,13 +24,17 @@ jobs:
|
||||||
- run: cargo build --verbose --release
|
- run: cargo build --verbose --release
|
||||||
- run: cargo test --verbose --release
|
- run: cargo test --verbose --release
|
||||||
|
|
||||||
build_and_test_examples:
|
test_basic_example:
|
||||||
name: axum-oidc - examples
|
name: axum-oidc - basic
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
toolchain:
|
||||||
|
- stable
|
||||||
|
- nightly
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: sudo apt install chromium-browser -y
|
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
|
||||||
- run: rustup update stable && rustup default stable
|
|
||||||
- run: cargo build --verbose --release
|
- run: cargo build --verbose --release
|
||||||
working-directory: ./examples/basic
|
working-directory: ./examples/basic
|
||||||
- run: cargo test --verbose --release
|
- run: cargo test --verbose --release
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,10 @@ name = "basic"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.43", features = ["net", "macros", "rt-multi-thread"] }
|
tokio = { version = "1.43", features = ["net", "macros", "rt-multi-thread"] }
|
||||||
axum = { version = "0.8", features = [ "macros" ]}
|
axum = { version = "0.8", features = [ "macros" ]}
|
||||||
axum-oidc = { path = "./../.." }
|
axum-oidc = { path = "./../.." }
|
||||||
tower = "0.5"
|
tower = "0.5"
|
||||||
tower-sessions = "0.14"
|
tower-sessions = "0.14"
|
||||||
|
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
testcontainers = "0.23"
|
|
||||||
tokio = { version = "1.43", features = ["rt-multi-thread"] }
|
|
||||||
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
|
|
||||||
env_logger = "0.11"
|
|
||||||
log = "0.4"
|
|
||||||
headless_chrome = "1.0"
|
|
||||||
#see https://github.com/rust-headless-chrome/rust-headless-chrome/issues/535
|
|
||||||
auto_generate_cdp = "=0.4.4"
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ It has three endpoints:
|
||||||
## Dependencies
|
## Dependencies
|
||||||
You will need a running OpenID Connect capable issuer like [Keycloak](https://www.keycloak.org/getting-started/getting-started-docker) and a valid client for the issuer.
|
You will need a running OpenID Connect capable issuer like [Keycloak](https://www.keycloak.org/getting-started/getting-started-docker) and a valid client for the issuer.
|
||||||
|
|
||||||
You can take a look at the `tests/`-folder to see how the automated keycloak deployment for the integration tests work.
|
|
||||||
|
|
||||||
## Setup Environment
|
## Setup Environment
|
||||||
Create a `.env`-file that contains the following keys:
|
Create a `.env`-file that contains the following keys:
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
use axum::{
|
|
||||||
error_handling::HandleErrorLayer,
|
|
||||||
http::Uri,
|
|
||||||
response::IntoResponse,
|
|
||||||
routing::{any, get},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use axum_oidc::{
|
|
||||||
error::MiddlewareError, handle_oidc_redirect, ClientId, ClientSecret, EmptyAdditionalClaims,
|
|
||||||
OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer, OidcRpInitiatedLogout,
|
|
||||||
};
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
use tower_sessions::{
|
|
||||||
cookie::{time::Duration, SameSite},
|
|
||||||
Expiry, MemoryStore, SessionManagerLayer,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn run(issuer: String, client_id: String, client_secret: Option<String>) {
|
|
||||||
let session_store = MemoryStore::default();
|
|
||||||
let session_layer = SessionManagerLayer::new(session_store)
|
|
||||||
.with_secure(false)
|
|
||||||
.with_same_site(SameSite::Lax)
|
|
||||||
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
|
||||||
|
|
||||||
let oidc_login_service = ServiceBuilder::new()
|
|
||||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
|
||||||
dbg!(&e);
|
|
||||||
e.into_response()
|
|
||||||
}))
|
|
||||||
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
|
||||||
|
|
||||||
let mut oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
|
||||||
.with_default_http_client()
|
|
||||||
.with_redirect_url(Uri::from_static("http://localhost:8080/oidc"))
|
|
||||||
.with_client_id(ClientId::new(client_id));
|
|
||||||
if let Some(client_secret) = client_secret {
|
|
||||||
oidc_client = oidc_client.with_client_secret(ClientSecret::new(client_secret));
|
|
||||||
}
|
|
||||||
let oidc_client = oidc_client.discover(issuer).await.unwrap().build();
|
|
||||||
|
|
||||||
let oidc_auth_service = ServiceBuilder::new()
|
|
||||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
|
||||||
dbg!(&e);
|
|
||||||
e.into_response()
|
|
||||||
}))
|
|
||||||
.layer(OidcAuthLayer::new(oidc_client));
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/foo", get(authenticated))
|
|
||||||
.route("/logout", get(logout))
|
|
||||||
.layer(oidc_login_service)
|
|
||||||
.route("/bar", get(maybe_authenticated))
|
|
||||||
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
|
||||||
.layer(oidc_auth_service)
|
|
||||||
.layer(session_layer);
|
|
||||||
|
|
||||||
let listener = TcpListener::bind("[::]:8080").await.unwrap();
|
|
||||||
axum::serve(listener, app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
|
||||||
format!("Hello {}", claims.subject().as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
async fn maybe_authenticated(
|
|
||||||
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
if let Ok(claims) = claims {
|
|
||||||
format!(
|
|
||||||
"Hello {}! You are already logged in from another Handler.",
|
|
||||||
claims.subject().as_str()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
"Hello anon!".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
|
||||||
logout.with_post_logout_redirect(Uri::from_static("https://example.com"))
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,90 @@
|
||||||
use basic::run;
|
use axum::{
|
||||||
|
error_handling::HandleErrorLayer,
|
||||||
|
http::Uri,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{any, get},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use axum_oidc::{
|
||||||
|
error::MiddlewareError, handle_oidc_redirect, ClientId, ClientSecret, EmptyAdditionalClaims,
|
||||||
|
OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer, OidcRpInitiatedLogout,
|
||||||
|
};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use tower_sessions::{
|
||||||
|
cookie::{time::Duration, SameSite},
|
||||||
|
Expiry, MemoryStore, SessionManagerLayer,
|
||||||
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
pub async fn run() {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
let issuer = std::env::var("ISSUER").expect("ISSUER env variable");
|
let issuer = std::env::var("ISSUER").expect("ISSUER env variable");
|
||||||
let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID env variable");
|
let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID env variable");
|
||||||
let client_secret = std::env::var("CLIENT_SECRET").ok();
|
let client_secret = std::env::var("CLIENT_SECRET").ok();
|
||||||
run(issuer, client_id, client_secret).await
|
|
||||||
|
let session_store = MemoryStore::default();
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store)
|
||||||
|
.with_secure(false)
|
||||||
|
.with_same_site(SameSite::Lax)
|
||||||
|
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
||||||
|
|
||||||
|
let oidc_login_service = ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||||
|
dbg!(&e);
|
||||||
|
e.into_response()
|
||||||
|
}))
|
||||||
|
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
||||||
|
|
||||||
|
let mut oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
||||||
|
.with_default_http_client()
|
||||||
|
.with_redirect_url(Uri::from_static("http://localhost:8080/oidc"))
|
||||||
|
.with_client_id(ClientId::new(client_id));
|
||||||
|
if let Some(client_secret) = client_secret {
|
||||||
|
oidc_client = oidc_client.with_client_secret(ClientSecret::new(client_secret));
|
||||||
|
}
|
||||||
|
let oidc_client = oidc_client.discover(issuer).await.unwrap().build();
|
||||||
|
|
||||||
|
let oidc_auth_service = ServiceBuilder::new()
|
||||||
|
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||||
|
dbg!(&e);
|
||||||
|
e.into_response()
|
||||||
|
}))
|
||||||
|
.layer(OidcAuthLayer::new(oidc_client));
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/foo", get(authenticated))
|
||||||
|
.route("/logout", get(logout))
|
||||||
|
.layer(oidc_login_service)
|
||||||
|
.route("/bar", get(maybe_authenticated))
|
||||||
|
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
||||||
|
.layer(oidc_auth_service)
|
||||||
|
.layer(session_layer);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind("[::]:8080").await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
||||||
|
format!("Hello {}", claims.subject().as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
async fn maybe_authenticated(
|
||||||
|
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if let Ok(claims) = claims {
|
||||||
|
format!(
|
||||||
|
"Hello {}! You are already logged in from another Handler.",
|
||||||
|
claims.subject().as_str()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"Hello anon!".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
||||||
|
logout.with_post_logout_redirect(Uri::from_static("https://example.com"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
mod keycloak;
|
|
||||||
|
|
||||||
use headless_chrome::Browser;
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
use crate::keycloak::{Client, Keycloak, Realm, User};
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn first() {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let alice = User {
|
|
||||||
username: "alice".to_string(),
|
|
||||||
email: "alice@example.com".to_string(),
|
|
||||||
firstname: "alice".to_string(),
|
|
||||||
lastname: "doe".to_string(),
|
|
||||||
password: "alice".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let basic_client = Client {
|
|
||||||
client_id: "axum-oidc-example-basic".to_string(),
|
|
||||||
client_secret: Some("123456".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let keycloak = Keycloak::start(vec![Realm {
|
|
||||||
name: "test".to_string(),
|
|
||||||
users: vec![alice.clone()],
|
|
||||||
clients: vec![basic_client.clone()],
|
|
||||||
}])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
info!("starting basic example app");
|
|
||||||
|
|
||||||
let app_url = "http://localhost:8080/";
|
|
||||||
let app_handle = tokio::spawn(basic::run(
|
|
||||||
format!("{}/realms/test", keycloak.url()),
|
|
||||||
basic_client.client_id.to_string(),
|
|
||||||
basic_client.client_secret.clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
info!("starting browser");
|
|
||||||
|
|
||||||
let browser = Browser::default().unwrap();
|
|
||||||
let tab = browser.new_tab().unwrap();
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}bar", app_url)).unwrap();
|
|
||||||
let body = tab
|
|
||||||
.wait_for_xpath(r#"/html/body/pre"#)
|
|
||||||
.unwrap()
|
|
||||||
.get_inner_text()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(body, "Hello anon!");
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}foo", app_url)).unwrap();
|
|
||||||
let username = tab.wait_for_xpath(r#"//*[@id="username"]"#).unwrap();
|
|
||||||
username.type_into(&alice.username).unwrap();
|
|
||||||
let password = tab.wait_for_xpath(r#"//*[@id="password"]"#).unwrap();
|
|
||||||
password.type_into(&alice.password).unwrap();
|
|
||||||
let submit = tab.wait_for_xpath(r#"//*[@id="kc-login"]"#).unwrap();
|
|
||||||
submit.click().unwrap();
|
|
||||||
|
|
||||||
let body = tab
|
|
||||||
.wait_for_xpath(r#"/html/body/pre"#)
|
|
||||||
.unwrap()
|
|
||||||
.get_inner_text()
|
|
||||||
.unwrap();
|
|
||||||
assert!(body.starts_with("Hello ") && body.contains('-'));
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}bar", app_url)).unwrap();
|
|
||||||
let body = tab
|
|
||||||
.wait_for_xpath(r#"/html/body/pre"#)
|
|
||||||
.unwrap()
|
|
||||||
.get_inner_text()
|
|
||||||
.unwrap();
|
|
||||||
assert!(body.contains("! You are already logged in from another Handler."));
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}logout", app_url)).unwrap();
|
|
||||||
tab.wait_until_navigated().unwrap();
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}bar", app_url)).unwrap();
|
|
||||||
let body = tab
|
|
||||||
.wait_for_xpath(r#"/html/body/pre"#)
|
|
||||||
.unwrap()
|
|
||||||
.get_inner_text()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(body, "Hello anon!");
|
|
||||||
|
|
||||||
tab.navigate_to(&format!("{}foo", app_url)).unwrap();
|
|
||||||
tab.wait_until_navigated().unwrap();
|
|
||||||
tab.find_element_by_xpath(r#"//*[@id="username"]"#).unwrap();
|
|
||||||
|
|
||||||
tab.close(true).unwrap();
|
|
||||||
app_handle.abort();
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
use log::info;
|
|
||||||
use std::time::Duration;
|
|
||||||
use testcontainers::runners::AsyncRunner;
|
|
||||||
use testcontainers::ContainerAsync;
|
|
||||||
|
|
||||||
use testcontainers::core::ExecCommand;
|
|
||||||
use testcontainers::{core::WaitFor, Image, ImageExt};
|
|
||||||
|
|
||||||
struct KeycloakImage;
|
|
||||||
|
|
||||||
impl Image for KeycloakImage {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"quay.io/keycloak/keycloak"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tag(&self) -> &str {
|
|
||||||
"latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ready_conditions(&self) -> Vec<WaitFor> {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Keycloak {
|
|
||||||
container: ContainerAsync<KeycloakImage>,
|
|
||||||
realms: Vec<Realm>,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Realm {
|
|
||||||
pub name: String,
|
|
||||||
pub clients: Vec<Client>,
|
|
||||||
pub users: Vec<User>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Client {
|
|
||||||
pub client_id: String,
|
|
||||||
pub client_secret: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct User {
|
|
||||||
pub username: String,
|
|
||||||
pub email: String,
|
|
||||||
pub firstname: String,
|
|
||||||
pub lastname: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keycloak {
|
|
||||||
pub async fn start(realms: Vec<Realm>) -> Keycloak {
|
|
||||||
info!("starting keycloak");
|
|
||||||
|
|
||||||
let keycloak_image = KeycloakImage
|
|
||||||
.with_cmd(["start-dev".to_string()])
|
|
||||||
.with_env_var("KEYCLOAK_ADMIN", "admin")
|
|
||||||
.with_env_var("KEYCLOAK_ADMIN_PASSWORD", "admin");
|
|
||||||
let container = keycloak_image.start().await.unwrap();
|
|
||||||
|
|
||||||
let keycloak = Self {
|
|
||||||
url: format!(
|
|
||||||
"http://127.0.0.1:{}",
|
|
||||||
container.get_host_port_ipv4(8080).await.unwrap()
|
|
||||||
),
|
|
||||||
container,
|
|
||||||
realms,
|
|
||||||
};
|
|
||||||
|
|
||||||
let issuer = format!(
|
|
||||||
"http://127.0.0.1:{}/realms/{}",
|
|
||||||
keycloak.container.get_host_port_ipv4(8080).await.unwrap(),
|
|
||||||
"test"
|
|
||||||
);
|
|
||||||
|
|
||||||
while reqwest::get(&issuer).await.is_err() {
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
keycloak.execute("/opt/keycloak/bin/kcadm.sh config credentials --server http://127.0.0.1:8080 --realm master --user admin --password admin".to_string()).await;
|
|
||||||
|
|
||||||
for realm in keycloak.realms.iter() {
|
|
||||||
keycloak.create_realm(&realm.name).await;
|
|
||||||
for client in realm.clients.iter() {
|
|
||||||
keycloak
|
|
||||||
.create_client(
|
|
||||||
&client.client_id,
|
|
||||||
client.client_secret.as_deref(),
|
|
||||||
&realm.name,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
for user in realm.users.iter() {
|
|
||||||
keycloak
|
|
||||||
.create_user(
|
|
||||||
&user.username,
|
|
||||||
&user.email,
|
|
||||||
&user.firstname,
|
|
||||||
&user.lastname,
|
|
||||||
&user.password,
|
|
||||||
&realm.name,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keycloak
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn url(&self) -> &str {
|
|
||||||
&self.url
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_realm(&self, name: &str) {
|
|
||||||
self.execute(format!(
|
|
||||||
"/opt/keycloak/bin/kcadm.sh create realms -s realm={} -s enabled=true",
|
|
||||||
name
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_client(&self, client_id: &str, client_secret: Option<&str>, realm: &str) {
|
|
||||||
if let Some(client_secret) = client_secret {
|
|
||||||
self.execute(format!(
|
|
||||||
r#"/opt/keycloak/bin/kcadm.sh create clients -r {} -f - << EOF
|
|
||||||
{{
|
|
||||||
"clientId": "{}",
|
|
||||||
"secret": "{}",
|
|
||||||
"redirectUris": ["*"]
|
|
||||||
}}
|
|
||||||
EOF
|
|
||||||
"#,
|
|
||||||
realm, client_id, client_secret
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
} else {
|
|
||||||
self.execute(format!(
|
|
||||||
r#"/opt/keycloak/bin/kcadm.sh create clients -r {} -f - << EOF
|
|
||||||
{{
|
|
||||||
"clientId": "{}",
|
|
||||||
"redirectUris": ["*"]
|
|
||||||
}}
|
|
||||||
EOF
|
|
||||||
"#,
|
|
||||||
realm, client_id
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_user(
|
|
||||||
&self,
|
|
||||||
username: &str,
|
|
||||||
email: &str,
|
|
||||||
firstname: &str,
|
|
||||||
lastname: &str,
|
|
||||||
password: &str,
|
|
||||||
realm: &str,
|
|
||||||
) {
|
|
||||||
let id = self.execute(
|
|
||||||
format!(
|
|
||||||
"/opt/keycloak/bin/kcadm.sh create users -r {} -s username={} -s enabled=true -s emailVerified=true -s email={} -s firstName={} -s lastName={}",
|
|
||||||
realm, username, email, firstname, lastname
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
self.execute(format!(
|
|
||||||
"/opt/keycloak/bin/kcadm.sh set-password -r {} --username {} --new-password {}",
|
|
||||||
realm, username, password
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute(&self, cmd: String) {
|
|
||||||
let mut result = self
|
|
||||||
.container
|
|
||||||
.exec(ExecCommand::new(
|
|
||||||
["/bin/sh", "-c", cmd.as_str()].iter().copied(),
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
// collect stdout to wait until command completion
|
|
||||||
let _output = String::from_utf8(result.stdout_to_vec().await.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue