use crate::common::authorizeurl::ParsedAuthorizeUrl;
use crate::common::cookiejar::SessionCookieJarMiddleware;
use crate::common::oidc_emulator::OpenIdConnectEmulator;
use crate::common::{assert_redirect, assert_response, create_test_server, get_config};
use http_types::StatusCode;
use tide_testing::TideTestingExt;
use tide_openidconnect::{OpenIdConnectMiddleware, RedirectUrl};
pub mod common;
#[async_std::test]
async fn middleware_can_be_initialized() -> tide::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let _mw = OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await;
Ok(())
})
.await
}
#[async_std::test]
async fn middleware_provides_login_route() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default(),
);
Ok(())
})
.await
}
#[async_std::test]
async fn login_path_can_be_changed() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(
OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url()))
.await
.with_login_path("/oauthlogin"),
);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/oauthlogin").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default(),
);
Ok(())
})
.await
}
#[async_std::test]
async fn oauth_scopes_can_be_changed() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(
OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url()))
.await
.with_scopes(&["profile"]),
);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default().with_scopes("openid profile"),
);
Ok(())
})
.await
}
#[async_std::test]
#[should_panic(
expected = "request session not initialized, did you enable tide::sessions::SessionMiddleware?"
)]
async fn login_panics_on_missing_session_middleware() {
let _result = OpenIdConnectEmulator::new(
RedirectUrl::new("http://localhost/callback".to_string()).unwrap(),
)
.run_with_emulator(|emu| async move {
let mut app = tide::new();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let _res = client.get("/login").await;
Ok(())
})
.await;
}
#[async_std::test]
async fn login_rejects_invalid_csrf() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let res = client
.get("/callback?code=12345&state=BADCSRFSTATE")
.await?;
assert_eq!(res.status(), StatusCode::Unauthorized);
Ok(())
})
.await
}
#[async_std::test]
async fn login_route_rejects_invalid_nonce() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.clone().with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default(),
);
let callback_url = emu
.add_token(
"atoken",
"openid",
"id",
&authorize_url.with_nonce(Some("BADNONCE".to_string())),
)
.await;
let res = client.get(callback_url).await?;
assert_eq!(res.status(), StatusCode::Unauthorized);
Ok(())
})
.await
}
#[async_std::test]
async fn redirect_route_errors_on_missing_session_data() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/callback?code=12345&state=CSRFSTATE").await?;
assert_eq!(res.status(), StatusCode::InternalServerError);
Ok(())
})
.await
}
#[async_std::test]
#[should_panic(
expected = "request session not initialized, did you enable tide::sessions::SessionMiddleware?"
)]
async fn redirect_route_panics_on_missing_session_middleware() {
let _result = OpenIdConnectEmulator::new(
RedirectUrl::new("http://localhost/callback".to_string()).unwrap(),
)
.run_with_emulator(|emu| async move {
let mut app = tide::new();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let _res = client.get("/callback?code=12345&state=CSRFSTATE").await;
Ok(())
})
.await;
}
#[async_std::test]
async fn login_and_destructive_logout() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url())).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let mut res = client.get("/").await?;
assert_response(&mut res, "unauthed visits=1").await;
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.clone().with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default(),
);
let callback_url = emu
.add_token("atoken", "openid", "id", &authorize_url)
.await;
let res = client.get(callback_url).await?;
assert_redirect(&res, "/");
let mut res = client.get("/").await?;
assert_response(
&mut res,
"authed visits=2 access_token=atoken scopes=[\"openid\"] userid=id",
)
.await;
let res = client.get("/logout").await?;
assert_redirect(&res, "/");
let mut res = client.get("/").await?;
assert_response(&mut res, "unauthed visits=1").await;
Ok(())
})
.await
}
#[async_std::test]
async fn login_and_auth_only_logout() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
app.with(
OpenIdConnectMiddleware::new(&get_config(&emu.issuer_url()))
.await
.with_logout_destroys_session(false),
);
let client = app.client().with(SessionCookieJarMiddleware::default());
let mut res = client.get("/").await?;
assert_response(&mut res, "unauthed visits=1").await;
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
assert_eq!(
authorize_url.clone().with_nonce(None).with_state(None),
ParsedAuthorizeUrl::default(),
);
let callback_url = emu
.add_token("atoken", "openid", "id", &authorize_url)
.await;
let res = client.get(callback_url).await?;
assert_redirect(&res, "/");
let mut res = client.get("/").await?;
assert_response(
&mut res,
"authed visits=2 access_token=atoken scopes=[\"openid\"] userid=id",
)
.await;
let res = client.get("/logout").await?;
assert_redirect(&res, "/");
let mut res = client.get("/").await?;
assert_response(&mut res, "unauthed visits=3").await;
Ok(())
})
.await
}
#[async_std::test]
async fn logout_can_clear_idp_state() -> http_types::Result<()> {
OpenIdConnectEmulator::new(RedirectUrl::new("http://localhost/callback".to_string()).unwrap())
.run_with_emulator(|emu| async move {
let mut app = create_test_server();
let config = tide_openidconnect::Config {
idp_logout_url: Some("http://idp.logout".to_string()),
..get_config(&emu.issuer_url())
};
app.with(OpenIdConnectMiddleware::new(&config).await);
let client = app.client().with(SessionCookieJarMiddleware::default());
let res = client.get("/login").await?;
assert_eq!(res.status(), StatusCode::Found);
let authorize_url = ParsedAuthorizeUrl::from_response(&res);
let callback_url = emu
.add_token("atoken", "openid", "id", &authorize_url)
.await;
let res = client.get(callback_url).await?;
assert_redirect(&res, "/");
let res = client.get("/logout").await?;
assert_redirect(&res, "http://idp.logout");
Ok(())
})
.await
}