From ea8beb985247aac4345ecefc8ec551f52f5f1a24 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 26 Nov 2019 04:20:43 -0500 Subject: allow multiple oauth configurations using the same auth type this should allow us to configure a separate oauth application for tt web than normal (since the redirect_url needs to be different) --- README.md | 19 ++++--- teleterm/src/cmd/server.rs | 15 ++++-- teleterm/src/cmd/stream.rs | 1 + teleterm/src/cmd/watch.rs | 1 + teleterm/src/config.rs | 99 +++++++++++++++++++++++------------- teleterm/src/error.rs | 10 +++- teleterm/src/oauth.rs | 2 +- teleterm/src/oauth/recurse_center.rs | 8 ++- teleterm/src/protocol.rs | 96 ++++++++++++++++++++++++++++++---- teleterm/src/server.rs | 39 +++++++++----- teleterm/src/server/tls.rs | 5 +- 11 files changed, 225 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index c797841..4bdf079 100644 --- a/README.md +++ b/README.md @@ -155,16 +155,23 @@ create one for you automatically. The configuration has several sections: * Same as `uid`, except sets the user's primary group. * Default: unset -#### `[oauth.]` (used by `tt server`) +#### `[oauth..]` (used by `tt server`) -`` corresponds to an OAuth-using login method - for instance, a section -would be named something like `[oauth.recurse_center]`. Note that OAuth login -methods are required to use `http://localhost:44141` as their redirect URL. +`` corresponds to an OAuth-using login method. Currently only +`recurse_center` is supported. `` describes what types of clients will +be using this configuration. Currently valid values for `` are `cli` +(for `tt stream` and `tt watch`) and `web` (for `tt web`). For example, a valid +configuration section will look like `[oauth.recurse_center.cli]`. You will +need to configure separate OAuth applications for `cli` and `web` since the +`redirect_url` will need to be different in each case. * `client_id` - * OAuth client id. + * OAuth client id. Required. * `client_secret` - * OAuth client secret. + * OAuth client secret. Required. +* `redirect_url` + * OAuth client redirect url. Required if `` is `web`, and must be + the `/oauth` path at the externally reachable domain of your web server. #### `[client]` (used by `tt stream` and `tt watch`) diff --git a/teleterm/src/cmd/server.rs b/teleterm/src/cmd/server.rs index 7eff187..25571dc 100644 --- a/teleterm/src/cmd/server.rs +++ b/teleterm/src/cmd/server.rs @@ -13,7 +13,10 @@ pub struct Config { )] oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, } @@ -76,7 +79,10 @@ fn create_server( >, oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, uid: Option, gid: Option, @@ -106,7 +112,10 @@ fn create_server_tls( >, oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, uid: Option, gid: Option, diff --git a/teleterm/src/cmd/stream.rs b/teleterm/src/cmd/stream.rs index 83556de..92026f1 100644 --- a/teleterm/src/cmd/stream.rs +++ b/teleterm/src/cmd/stream.rs @@ -38,6 +38,7 @@ impl crate::config::Config for Config { crate::protocol::AuthType::RecurseCenter => { let id = crate::oauth::load_client_auth_id(self.client.auth); crate::protocol::Auth::recurse_center( + crate::protocol::AuthClient::Cli, id.as_ref().map(std::string::String::as_str), ) } diff --git a/teleterm/src/cmd/watch.rs b/teleterm/src/cmd/watch.rs index f205002..dc80d47 100644 --- a/teleterm/src/cmd/watch.rs +++ b/teleterm/src/cmd/watch.rs @@ -36,6 +36,7 @@ impl crate::config::Config for Config { crate::protocol::AuthType::RecurseCenter => { let id = crate::oauth::load_client_auth_id(self.client.auth); crate::protocol::Auth::recurse_center( + crate::protocol::AuthClient::Cli, id.as_ref().map(std::string::String::as_str), ) } diff --git a/teleterm/src/config.rs b/teleterm/src/config.rs index 7454a2a..4000cb7 100644 --- a/teleterm/src/config.rs +++ b/teleterm/src/config.rs @@ -872,51 +872,80 @@ pub fn oauth_configs<'a, D>( ) -> std::result::Result< std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, D::Error, > where D: serde::de::Deserializer<'a>, { - let configs = - >::deserialize( - deserializer, - )?; - let mut ret = std::collections::HashMap::new(); - for (key, config) in configs { + let configs = , + >>::deserialize(deserializer)?; + let mut all_configs = std::collections::HashMap::new(); + for (key, client_configs) in configs { let auth_type = crate::protocol::AuthType::try_from(key.as_str()) .map_err(serde::de::Error::custom)?; - let real_config = match auth_type { - crate::protocol::AuthType::RecurseCenter => { - let client_id = config - .client_id - .context(crate::error::OauthMissingConfiguration { - field: "client_id", - auth_type, - }) + let mut auth_type_configs = std::collections::HashMap::new(); + for (key, config) in client_configs { + let auth_client = + crate::protocol::AuthClient::try_from(key.as_str()) .map_err(serde::de::Error::custom)?; - let client_secret = config - .client_secret - .context(crate::error::OauthMissingConfiguration { - field: "client_secret", - auth_type, - }) - .map_err(serde::de::Error::custom)?; - crate::oauth::RecurseCenter::config( - &client_id, - &client_secret, - ) - } - ty if !ty.is_oauth() => { - return Err(Error::AuthTypeNotOauth { ty: auth_type }) - .map_err(serde::de::Error::custom); - } - _ => unreachable!(), - }; - ret.insert(auth_type, real_config); + let real_config = match auth_type { + crate::protocol::AuthType::RecurseCenter => { + let client_id = config + .client_id + .context(crate::error::OauthMissingConfiguration { + field: "client_id", + auth_type, + auth_client, + }) + .map_err(serde::de::Error::custom)?; + let client_secret = config + .client_secret + .context(crate::error::OauthMissingConfiguration { + field: "client_secret", + auth_type, + auth_client, + }) + .map_err(serde::de::Error::custom)?; + let redirect_url = + if auth_client == crate::protocol::AuthClient::Cli { + url::Url::parse(crate::oauth::CLI_REDIRECT_URL) + .unwrap() + } else { + config + .redirect_url + .context( + crate::error::OauthMissingConfiguration { + field: "redirect_url", + auth_type, + auth_client, + }, + ) + .map_err(serde::de::Error::custom)? + }; + crate::oauth::RecurseCenter::config( + &client_id, + &client_secret, + &redirect_url, + ) + } + ty if !ty.is_oauth() => { + return Err(Error::AuthTypeNotOauth { ty: auth_type }) + .map_err(serde::de::Error::custom); + } + _ => unreachable!(), + }; + auth_type_configs.insert(auth_client, real_config); + } + all_configs.insert(auth_type, auth_type_configs); } - Ok(ret) + Ok(all_configs) } #[derive(serde::Deserialize, Debug)] diff --git a/teleterm/src/error.rs b/teleterm/src/error.rs index 9cc0ca7..f7946dd 100644 --- a/teleterm/src/error.rs +++ b/teleterm/src/error.rs @@ -107,6 +107,12 @@ pub enum Error { #[snafu(display("failed to find any resolvable addresses"))] HasResolvedAddr, + #[snafu(display("invalid auth client {}", ty))] + InvalidAuthClient { ty: u8 }, + + #[snafu(display("invalid auth client {}", ty))] + InvalidAuthClientStr { ty: String }, + #[snafu(display("invalid auth type {}", ty))] InvalidAuthType { ty: u8 }, @@ -143,13 +149,15 @@ pub enum Error { NotAFileName { path: String }, #[snafu(display( - "missing oauth configuration item {} for auth type {}", + "missing oauth configuration item {} for section oauth.{}.{}", field, auth_type.name(), + auth_client.name(), ))] OauthMissingConfiguration { field: String, auth_type: crate::protocol::AuthType, + auth_client: crate::protocol::AuthClient, }, #[snafu(display("failed to open file {}: {}", filename, source))] diff --git a/teleterm/src/oauth.rs b/teleterm/src/oauth.rs index 680b6f4..5283957 100644 --- a/teleterm/src/oauth.rs +++ b/teleterm/src/oauth.rs @@ -6,7 +6,7 @@ mod recurse_center; pub use recurse_center::RecurseCenter; // this needs to be fixed because we listen for it in a hardcoded place -pub const REDIRECT_URL: &str = "http://localhost:44141/oauth"; +pub const CLI_REDIRECT_URL: &str = "http://localhost:44141/oauth"; pub trait Oauth { fn client(&self) -> &oauth2::basic::BasicClient; diff --git a/teleterm/src/oauth/recurse_center.rs b/teleterm/src/oauth/recurse_center.rs index 6eeb69c..c43be15 100644 --- a/teleterm/src/oauth/recurse_center.rs +++ b/teleterm/src/oauth/recurse_center.rs @@ -13,7 +13,11 @@ impl RecurseCenter { } } - pub fn config(client_id: &str, client_secret: &str) -> super::Config { + pub fn config( + client_id: &str, + client_secret: &str, + redirect_url: &url::Url, + ) -> super::Config { super::Config { client_id: client_id.to_string(), client_secret: client_secret.to_string(), @@ -23,7 +27,7 @@ impl RecurseCenter { .unwrap(), token_url: url::Url::parse("https://www.recurse.com/oauth/token") .unwrap(), - redirect_url: url::Url::parse(super::REDIRECT_URL).unwrap(), + redirect_url: redirect_url.clone(), } } } diff --git a/teleterm/src/protocol.rs b/teleterm/src/protocol.rs index d2b7806..130e52d 100644 --- a/teleterm/src/protocol.rs +++ b/teleterm/src/protocol.rs @@ -52,7 +52,67 @@ impl FramedWriter { pub const PROTO_VERSION: u8 = 1; #[repr(u8)] -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, serde::Serialize)] +#[derive( + Copy, + Clone, + Debug, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] +pub enum AuthClient { + Cli = 0, + Web, +} + +impl AuthClient { + pub fn name(self) -> &'static str { + match self { + Self::Cli => "cli", + Self::Web => "web", + } + } +} + +impl std::convert::TryFrom for AuthClient { + type Error = Error; + + fn try_from(n: u8) -> Result { + Ok(match n { + 0 => Self::Cli, + 1 => Self::Web, + _ => return Err(Error::InvalidAuthClient { ty: n }), + }) + } +} + +impl std::convert::TryFrom<&str> for AuthClient { + type Error = Error; + + fn try_from(s: &str) -> Result { + Ok(match s { + s if Self::Cli.name() == s => Self::Cli, + s if Self::Web.name() == s => Self::Web, + _ => { + return Err(Error::InvalidAuthClientStr { ty: s.to_string() }) + } + }) + } +} + +#[repr(u8)] +#[derive( + Copy, + Clone, + Debug, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, +)] pub enum AuthType { Plain = 0, RecurseCenter, @@ -107,8 +167,13 @@ impl std::convert::TryFrom<&str> for AuthType { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] pub enum Auth { - Plain { username: String }, - RecurseCenter { id: Option }, + Plain { + username: String, + }, + RecurseCenter { + auth_client: AuthClient, + id: Option, + }, } impl Auth { @@ -118,8 +183,9 @@ impl Auth { } } - pub fn recurse_center(id: Option<&str>) -> Self { + pub fn recurse_center(auth_client: AuthClient, id: Option<&str>) -> Self { Self::RecurseCenter { + auth_client, id: id.map(std::string::ToString::to_string), } } @@ -128,8 +194,13 @@ impl Auth { self.auth_type().is_oauth() } - pub fn name(&self) -> &'static str { - self.auth_type().name() + pub fn name(&self) -> String { + match &self { + Self::RecurseCenter { auth_client, .. } => { + format!("{}.{}", self.auth_type().name(), auth_client.name()) + } + _ => self.auth_type().name().to_string(), + } } pub fn auth_type(&self) -> AuthType { @@ -489,8 +560,9 @@ impl From<&Message> for Packet { Auth::Plain { username } => { write_str(username, data); } - Auth::RecurseCenter { id } => { + Auth::RecurseCenter { auth_client, id } => { let id = id.as_ref().map_or("", |s| s.as_str()); + write_u8(*auth_client as u8, data); write_str(id, data); } } @@ -656,9 +728,11 @@ impl std::convert::TryFrom for Message { (auth, data) } AuthType::RecurseCenter => { + let (auth_client, data) = read_u8(data)?; + let auth_client = AuthClient::try_from(auth_client)?; let (id, data) = read_str(data)?; let id = if id == "" { None } else { Some(id) }; - let auth = Auth::RecurseCenter { id }; + let auth = Auth::RecurseCenter { auth_client, id }; (auth, data) } }; @@ -876,13 +950,17 @@ mod test { ), Message::login( &Auth::RecurseCenter { + auth_client: AuthClient::Cli, id: Some("some-random-id".to_string()), }, "screen", crate::term::Size { rows: 24, cols: 80 }, ), Message::login( - &Auth::RecurseCenter { id: None }, + &Auth::RecurseCenter { + auth_client: AuthClient::Cli, + id: None, + }, "screen", crate::term::Size { rows: 24, cols: 80 }, ), diff --git a/teleterm/src/server.rs b/teleterm/src/server.rs index 1f565a2..e36f3fc 100644 --- a/teleterm/src/server.rs +++ b/teleterm/src/server.rs @@ -309,7 +309,10 @@ pub struct Server< allowed_auth_types: std::collections::HashSet, oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, } @@ -324,7 +327,10 @@ impl >, oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, ) -> Self { Self { @@ -379,19 +385,28 @@ impl )); } oauth if oauth.is_oauth() => { - let config = self.oauth_configs.get(&ty).context( + let configs = self.oauth_configs.get(&ty).context( crate::error::AuthTypeMissingOauthConfig { ty }, )?; let (refresh, client) = match oauth { - crate::protocol::Auth::RecurseCenter { id } => ( - id.is_some(), - Box::new(crate::oauth::RecurseCenter::new( - config.clone(), - &id.clone().unwrap_or_else(|| { - format!("{}", uuid::Uuid::new_v4()) - }), - )), - ), + crate::protocol::Auth::RecurseCenter { + auth_client, + id, + .. + } => { + let config = configs.get(auth_client).context( + crate::error::AuthTypeMissingOauthConfig { ty }, + )?; + ( + id.is_some(), + Box::new(crate::oauth::RecurseCenter::new( + config.clone(), + &id.clone().unwrap_or_else(|| { + format!("{}", uuid::Uuid::new_v4()) + }), + )), + ) + } _ => unreachable!(), }; diff --git a/teleterm/src/server/tls.rs b/teleterm/src/server/tls.rs index 28b9b28..694866a 100644 --- a/teleterm/src/server/tls.rs +++ b/teleterm/src/server/tls.rs @@ -28,7 +28,10 @@ impl Server { >, oauth_configs: std::collections::HashMap< crate::protocol::AuthType, - crate::oauth::Config, + std::collections::HashMap< + crate::protocol::AuthClient, + crate::oauth::Config, + >, >, ) -> Self { let (tls_sock_w, tls_sock_r) = tokio::sync::mpsc::channel(100); -- cgit v1.2.3