Skip to content

Commit

Permalink
refactor: rebase logout path PR with mulitple providers PR
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Engelhardt <[email protected]>
  • Loading branch information
antonengelhardt committed Sep 10, 2024
1 parent 24ed426 commit 18f31e3
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The plugin is configured via the `envoy.yaml`-file. The following configuration
| `id_token_header_name` | `string` | If set, this name will be used to forward the id token to the backend. | `X-Id-Token` ||
| `id_token_header_prefix` | `string` | The prefix of the header, that is used to forward the id token, if empty "" is used. | `Bearer ` ||
| `cookie_name` | `string` | The name of the cookie, that is used to store the session. | `oidcSession` ||
| `logout_path` | `string` | The path, that is used to logout the user. The user will be redirected to `end_session_endpoint` of the OIDC provider, if the server supports this; alternatively the user is sent to "/" | `/logout` ||
| `filter_plugin_cookies` | `bool` | Whether to filter the cookies that are managed and controlled by the plugin (namely cookie_name and `nonce`). | `true` ||
| `cookie_duration` | `u64` | The duration in seconds, after which the session cookie expires. | `86400` ||
| `token_validation` | bool | Whether to validate the token or not. | `true` ||
Expand Down
1 change: 1 addition & 0 deletions demo/configmap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ data:
id_token_header_prefix: "Bearer "
cookie_name: "oidcSession"
logout_path: "/logout"
filter_plugin_cookies: true # or false
cookie_duration: 8640000 # in seconds
token_validation: true # or false
Expand Down
1 change: 1 addition & 0 deletions envoy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ static_resources:
id_token_header_prefix: "Bearer "
cookie_name: "oidcSession" # max. 32 characters
logout_path: "/logout"
filter_plugin_cookies: true # or false
cookie_duration: 8640000 # in seconds
token_validation: true # or false
Expand Down
4 changes: 2 additions & 2 deletions k8s/configmap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ data:
id_token_header_prefix: "Bearer "
cookie_name: "oidcSession" # max. 32 characters
filter_plugin_cookies: true # or false
cookie_duration: 86400 # in seconds
logout_path: "/logout"
cookie_duration: 8640000 # in seconds
token_validation: true # or false
aes_key: "i-am-a-forty-four-characters-long-string-key" # generate with `openssl rand -base64 32`
Expand Down
55 changes: 55 additions & 0 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ impl HttpContext for ConfiguredOidc {
return Action::Continue;
}

// If Path is logout route, clear cookies and redirect to base path
if path == self.plugin_config.logout_path {
match self.logout() {
Ok(action) => return action,
Err(e) => {
warn!("logout failed: {}", e);
self.show_error_page(503, "Logout failed", "Please try again, delete your cookies or contact your system administrator.");
}
}
}

// If the path matches the provider selection endpoint, redirect to the authorization endpoint
// with the selected provider.
if path.contains("/_wasm-oidc-plugin/provider-selection") {
Expand Down Expand Up @@ -573,6 +584,50 @@ impl ConfiguredOidc {
}
}

/// Clear the cookies and redirect to the base path or `end_session_endpoint`.
fn logout(&self) -> Result<Action, PluginError> {
let cookie_values = Session::make_cookie_values(
"",
"",
&self.plugin_config.cookie_name,
0,
self.get_number_of_cookies() as u64,
);

let mut headers = Session::make_set_cookie_headers(&cookie_values);

// Get session from cookie
let cookie = self.get_session_cookie_as_string()?;
let nonce = self.get_nonce()?;
let session = Session::decode_and_decrypt(
cookie,
self.plugin_config.aes_key.reveal().clone(),
nonce,
)?;

// Get provider to use based on issuer because the end session endpoint is provider-specific
let provider = self
.open_id_providers
.iter()
.find(|provider| provider.issuer == session.issuer.clone().unwrap())
.unwrap();
// TODO: Error handling

// Redirect to end session endpoint, if available (not all OIDC providers support this)
let location = match &provider.end_session_endpoint {
// if the end session endpoint is available, redirect to it
Some(url) => url.as_str(),
// else, redirect to the base path
None => "/",
};
headers.push(("Location", location));
headers.push(("Cache-Control", "no-cache"));

self.send_http_response(307, headers, Some(b"Logging out..."));

Ok(Action::Pause)
}

/// Show the auth page or redirect to the authorization endpoint.
fn generate_auth_page(&self) {
// If there is more than one provider, show an auth page where the user selects the provider
Expand Down
5 changes: 3 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use regex::Regex;
// url
use url::Url;

/// Struct that holds the configuration for the plugin. It is loaded from the config file
/// `envoy.yaml`
/// Struct that holds the configuration for the plugin. It is loaded from the config file `envoy.yaml`
#[derive(Clone, Debug, Deserialize)]
pub struct PluginConfiguration {
// OpenID Connect Configuration
Expand Down Expand Up @@ -59,6 +58,8 @@ pub struct PluginConfiguration {
// Cookie settings
/// The cookie name that will be used for the session cookie
pub cookie_name: String,
/// The URL to logout the user
pub logout_path: String,
/// Filter out the cookies created and controlled by the plugin
/// If the value is true, the cookies will be filtered out
pub filter_plugin_cookies: bool,
Expand Down
16 changes: 16 additions & 0 deletions src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub struct OpenIdProvider {
pub auth_endpoint: Url,
/// The URL of the token endpoint
pub token_endpoint: Url,
/// The URL of the end session endpoint
pub end_session_endpoint: Option<Url>,
/// The issuer that will be used for the token request
pub issuer: String,
/// The public keys that will be used for the validation of the ID Token
Expand Down Expand Up @@ -460,6 +462,7 @@ impl Context for Root {
open_id_config: resolver_to_update.open_id_config.clone(),
auth_endpoint: open_id_response.authorization_endpoint.clone(),
token_endpoint: open_id_response.token_endpoint.clone(),
end_session_endpoint: open_id_response.end_session_endpoint.clone(),
issuer: open_id_response.issuer.clone(),
public_keys: keys,
});
Expand Down Expand Up @@ -513,6 +516,19 @@ impl Root {
return Err(PluginError::ConfigError("`cookie_name` is empty or not valid meaning that it contains invalid characters like ;, =, :, /, space".to_string()));
}

// Logout Path
if plugin_config.logout_path.is_empty() {
return Err(PluginError::ConfigError(
"`logout_path` is empty".to_string(),
));
}

if !plugin_config.logout_path.starts_with('/') {
return Err(PluginError::ConfigError(
"`logout_path` does not start with a `/`".to_string(),
));
}

// Cookie Duration
if plugin_config.cookie_duration == 0 {
return Err(PluginError::ConfigError(
Expand Down
2 changes: 2 additions & 0 deletions src/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub struct OpenIdDiscoveryResponse {
pub authorization_endpoint: Url,
/// The token endpoint to exchange the code for a token
pub token_endpoint: Url,
/// The URL to logout the user
pub end_session_endpoint: Option<Url>,
/// The jwks uri to load the jwks response from
pub jwks_uri: Url,
}
Expand Down

0 comments on commit 18f31e3

Please sign in to comment.