Browse Source

use migrations properly, avoid panics

pull/1955/head
Stuart Heap 4 years ago
parent
commit
4d39197df2
No known key found for this signature in database GPG Key ID: C753450AB379AA25
  1. 13
      migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql
  2. 7
      migrations/mysql/2021-09-16-133000_add-sso/up.sql
  3. 13
      migrations/postgresql/2019-09-12-100000_create_tables/up.sql
  4. 7
      migrations/postgresql/2021-09-16-133000_add_sso/up.sql
  5. 13
      migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql
  6. 7
      migrations/sqlite/2021-09-16-133000_add_sso/up.sql
  7. 119
      src/api/identity.rs

13
migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql

@ -5,16 +5,9 @@ CREATE TABLE collections (
); );
CREATE TABLE organizations ( CREATE TABLE organizations (
uuid VARCHAR(40) NOT NULL PRIMARY KEY, uuid VARCHAR(40) NOT NULL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
billing_email TEXT NOT NULL, billing_email TEXT NOT NULL
identifier TEXT NOT NULL,
use_sso BOOLEAN NOT NULL,
callback_path TEXT NOT NULL,
signed_out_callback_path TEXT NOT NULL,
authority TEXT NOT NULL,
client_id TEXT NOT NULL,
client_secret TEXT NOT NULL
); );
CREATE TABLE users_collections ( CREATE TABLE users_collections (

7
migrations/mysql/2021-09-16-133000_add-sso/up.sql

@ -0,0 +1,7 @@
ALTER TABLE organizations ADD COLUMN identifier TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN use_sso BOOLEAN NOT NULL;
ALTER TABLE organizations ADD COLUMN callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN signed_out_callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN authority TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_id TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_secret TEXT NOT NULL;

13
migrations/postgresql/2019-09-12-100000_create_tables/up.sql

@ -33,16 +33,9 @@ CREATE TABLE devices (
); );
CREATE TABLE organizations ( CREATE TABLE organizations (
uuid VARCHAR(40) NOT NULL PRIMARY KEY, uuid VARCHAR(40) NOT NULL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
billing_email TEXT NOT NULL, billing_email TEXT NOT NULL
identifier TEXT NOT NULL,
use_sso BOOLEAN NOT NULL,
callback_path TEXT NOT NULL,
signed_out_callback_path TEXT NOT NULL,
authority TEXT NOT NULL,
client_id TEXT NOT NULL,
client_secret TEXT NOT NULL
); );
CREATE TABLE ciphers ( CREATE TABLE ciphers (

7
migrations/postgresql/2021-09-16-133000_add_sso/up.sql

@ -0,0 +1,7 @@
ALTER TABLE organizations ADD COLUMN identifier TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN use_sso BOOLEAN NOT NULL;
ALTER TABLE organizations ADD COLUMN callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN signed_out_callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN authority TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_id TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_secret TEXT NOT NULL;

13
migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql

@ -5,16 +5,9 @@ CREATE TABLE collections (
); );
CREATE TABLE organizations ( CREATE TABLE organizations (
uuid TEXT NOT NULL PRIMARY KEY, uuid TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
billing_email TEXT NOT NULL, billing_email TEXT NOT NULL
identifier TEXT NOT NULL,
use_sso BOOLEAN NOT NULL,
callback_path TEXT NOT NULL,
signed_out_callback_path TEXT NOT NULL,
authority TEXT NOT NULL,
client_id TEXT NOT NULL,
client_secret TEXT NOT NULL
); );

7
migrations/sqlite/2021-09-16-133000_add_sso/up.sql

@ -0,0 +1,7 @@
ALTER TABLE organizations ADD COLUMN identifier TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN use_sso BOOLEAN NOT NULL;
ALTER TABLE organizations ADD COLUMN callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN signed_out_callback_path TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN authority TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_id TEXT NOT NULL;
ALTER TABLE organizations ADD COLUMN client_secret TEXT NOT NULL;

119
src/api/identity.rs

@ -96,7 +96,10 @@ struct TokenPayload {
fn _authorization_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { fn _authorization_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult {
let org_identifier = data.org_identifier.as_ref().unwrap(); let org_identifier = data.org_identifier.as_ref().unwrap();
let code = data.code.as_ref().unwrap(); let code = data.code.as_ref().unwrap();
let (access_token, refresh_token) = get_auth_code_access_token(&code, &org_identifier, &conn); let (access_token, refresh_token) = match get_auth_code_access_token(&code, &org_identifier, &conn) {
Ok((access_token, refresh_token)) => (access_token, refresh_token),
Err(err) => err!(err),
};
let token = jsonwebtoken::dangerous_insecure_decode::<TokenPayload>(access_token.as_str()).unwrap().claims; let token = jsonwebtoken::dangerous_insecure_decode::<TokenPayload>(access_token.as_str()).unwrap().claims;
let expiry = token.exp; let expiry = token.exp;
let user_email = token.email; let user_email = token.email;
@ -562,17 +565,7 @@ use openidconnect::{
Scope, OAuth2TokenResponse, Scope, OAuth2TokenResponse,
}; };
fn handle_error<T: std::error::Error>(fail: &T, msg: &'static str) { fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> Result<CoreClient, &'static str> {
let mut err_msg = format!("ERROR: {}", msg);
let mut cur_fail: Option<&dyn std::error::Error> = Some(fail);
while let Some(cause) = cur_fail {
err_msg += &format!("\n caused by: {}", cause);
cur_fail = cause.source();
}
panic!("{}", err_msg);
}
fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> CoreClient {
let organization = Organization::find_by_identifier(identifier, conn); let organization = Organization::find_by_identifier(identifier, conn);
match organization { match organization {
@ -581,21 +574,22 @@ fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> CoreClient {
let client_id = ClientId::new(organization.client_id); let client_id = ClientId::new(organization.client_id);
let client_secret = ClientSecret::new(organization.client_secret); let client_secret = ClientSecret::new(organization.client_secret);
let issuer_url = IssuerUrl::new(organization.authority).expect("invalid issuer URL"); let issuer_url = IssuerUrl::new(organization.authority).expect("invalid issuer URL");
let provider_metadata = CoreProviderMetadata::discover(&issuer_url, http_client) let provider_metadata = match CoreProviderMetadata::discover(&issuer_url, http_client) {
.unwrap_or_else(|err| { Ok(metadata) => metadata,
handle_error(&err, "Failed to discover OpenID Provider"); Err(_err) => {
unreachable!(); return Err("Failed to discover OpenID provider");
}); },
};
let client = CoreClient::from_provider_metadata( let client = CoreClient::from_provider_metadata(
provider_metadata, provider_metadata,
client_id, client_id,
Some(client_secret), Some(client_secret),
) )
.set_redirect_uri(RedirectUrl::new(redirect).expect("Invalid redirect URL")); .set_redirect_uri(RedirectUrl::new(redirect).expect("Invalid redirect URL"));
return client; return Ok(client);
}, },
None => { None => {
panic!("unable to find org"); Err("unable to find org")
}, },
} }
} }
@ -605,59 +599,62 @@ fn authorize(
domain_hint: &RawStr, domain_hint: &RawStr,
state: &RawStr, state: &RawStr,
conn: DbConn, conn: DbConn,
) -> Redirect { ) -> ApiResult<Redirect> {
let domain_hint_decoded = &domain_hint.percent_decode().expect("Invalid domain_hint").into_owned(); let domain_hint_decoded = &domain_hint.percent_decode().expect("Invalid domain_hint").into_owned();
let state_decoded = &state.percent_decode().expect("Invalid state").into_owned(); let state_decoded = &state.percent_decode().expect("Invalid state").into_owned();
let client = get_client_from_identifier(domain_hint_decoded, &conn); match get_client_from_identifier(domain_hint_decoded, &conn) {
Ok(client) => {
// TODO store the nonce for validation on authorization token exchange - unclear where to store // TODO store the nonce for validation on authorization token exchange - unclear where to store
// this // this
let (mut authorize_url, _csrf_state, _nonce) = client let (mut authorize_url, _csrf_state, _nonce) = client
.authorize_url( .authorize_url(
AuthenticationFlow::<CoreResponseType>::AuthorizationCode, AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
CsrfToken::new_random, CsrfToken::new_random,
Nonce::new_random, Nonce::new_random,
) )
.add_scope(Scope::new("email".to_string())) .add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("profile".to_string())) .add_scope(Scope::new("profile".to_string()))
.url(); .url();
// it seems impossible to set the state going in dynamically (requires static lifetime string) // it seems impossible to set the state going in dynamically (requires static lifetime string)
// so I change it after the fact // so I change it after the fact
let old_pairs = authorize_url.query_pairs().clone(); let old_pairs = authorize_url.query_pairs().clone();
let new_pairs = old_pairs.map(|pair| { let new_pairs = old_pairs.map(|pair| {
let (key, value) = pair; let (key, value) = pair;
if key == "state" { if key == "state" {
return format!("{}={}", key, state_decoded); return format!("{}={}", key, state_decoded);
} }
return format!("{}={}", key, value); return format!("{}={}", key, value);
}); });
let full_query = Vec::from_iter(new_pairs).join("&"); let full_query = Vec::from_iter(new_pairs).join("&");
authorize_url.set_query(Some(full_query.as_str())); authorize_url.set_query(Some(full_query.as_str()));
return Redirect::to(authorize_url.to_string()); return Ok(Redirect::to(authorize_url.to_string()));
},
Err(_err) => err!("Unable to find client from identifier"),
}
} }
fn get_auth_code_access_token ( fn get_auth_code_access_token (
code: &str, code: &str,
org_identifier: &str, org_identifier: &str,
conn: &DbConn, conn: &DbConn,
) -> (String, String) { ) -> Result<(String, String), &'static str> {
let oidc_code = AuthorizationCode::new(String::from(code)); let oidc_code = AuthorizationCode::new(String::from(code));
let client = get_client_from_identifier(org_identifier, conn); match get_client_from_identifier(org_identifier, conn) {
Ok(client) => {
let token_response = client match client.exchange_code(oidc_code).request(http_client) {
.exchange_code(oidc_code) Ok(token_response) => {
.request(http_client) let access_token = token_response.access_token().secret().to_string();
.unwrap_or_else(|err| { let refresh_token = token_response.refresh_token().unwrap().secret().to_string();
handle_error(&err, "Failed to contact token endpoint");
unreachable!();
});
let access_token = token_response.access_token().secret().to_string(); Ok((access_token, refresh_token))
let refresh_token = token_response.refresh_token().unwrap().secret().to_string(); },
Err(_err) => Err("Failed to contact token endpoint"),
}
(access_token, refresh_token) },
Err(_err) => Err("unable to find client"),
}
} }

Loading…
Cancel
Save