diff options
| -rw-r--r-- | rust/src/api_server.rs | 33 | ||||
| -rw-r--r-- | rust/src/api_wrappers.rs | 79 | ||||
| -rw-r--r-- | rust/src/database_models.rs | 28 | ||||
| -rw-r--r-- | rust/src/database_schema.rs | 14 | 
4 files changed, 146 insertions, 8 deletions
| diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs index be9f1883..1edf739c 100644 --- a/rust/src/api_server.rs +++ b/rust/src/api_server.rs @@ -477,12 +477,7 @@ impl Server {      pub fn get_editor_handler(&self, editor_id: FatCatId, conn: &DbConn) -> Result<Editor> {          let row: EditorRow = editor::table.find(editor_id.to_uuid()).first(conn)?; - -        let ed = Editor { -            editor_id: Some(uuid2fcid(&row.id)), -            username: row.username, -        }; -        Ok(ed) +        Ok(row.into_model())      }      pub fn get_editor_changelog_handler( @@ -544,6 +539,32 @@ impl Server {          Ok(entry)      } +    /// This helper either finds an Editor model by OIDC parameters (eg, remote domain and +    /// identifier), or creates one and inserts the appropriate auth rows. The semantics are +    /// basically an "upsert" of signup/account-creation. +    /// Returns an editor model and boolean flag indicating whether a new editor was created or +    /// not. +    /// If this function creates an editor, it sets the username to "{iss}-{provider}"; the intent +    /// is for this to be temporary but unique. Might look like "bnewbold-github", or might look +    /// like "895139824-github". This is a hack to make check/creation idempotent. +    pub fn auth_oidc_handler(&self, params: AuthOidc, conn: &DbConn) -> Result<(Editor, bool)> { +        let existing: Vec<(EditorRow, AuthOidcRow)> = editor::table +            .inner_join(auth_oidc::table) +            .filter(auth_oidc::oidc_sub.eq(params.sub.clone())) +            .filter(auth_oidc::oidc_iss.eq(params.iss)) +            .load(conn)?; + +        let (editor_row, created): (EditorRow, bool) = match existing.first() { +            Some((editor, _)) => (editor.clone(), false), +            None => { +                let username = format!("{}-{}", params.sub, params.provider); +                (create_editor(conn, username, false, false)?, true) +            } +        }; + +        Ok((editor_row.into_model(), created)) +    } +      entity_batch_handler!(create_container_batch_handler, ContainerEntity);      entity_batch_handler!(create_creator_batch_handler, CreatorEntity);      entity_batch_handler!(create_file_batch_handler, FileEntity); diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs index 6c003802..c6966cee 100644 --- a/rust/src/api_wrappers.rs +++ b/rust/src/api_wrappers.rs @@ -1067,4 +1067,83 @@ impl Api for Server {          };          Box::new(futures::done(Ok(ret)))      } + +    fn auth_oidc( +        &self, +        params: models::AuthOidc, +        context: &Context, +    ) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> { +        let conn = self.db_pool.get().expect("db_pool error"); +        let ret = match conn.transaction(|| { +            let auth_context = self +                .auth_confectionary +                .require_auth(&conn, &context.auth_data)?; +            auth_context.require_role(FatcatRole::Admin)?; +            let (editor, created) = self.auth_oidc_handler(params, &conn)?; +            // create an auth token; leave it to webface to attenuate to a given duration +            let token = self +                .auth_confectionary +                .create_token(FatCatId::from_str(&editor.editor_id.clone().unwrap())?, None)?; +            let result = AuthOidcResult { editor, token }; +            Ok((result, created)) +        }) { +            Ok((result, true)) => AuthOidcResponse::Created(result), +            Ok((result, false)) => AuthOidcResponse::Found(result), +            Err(Error(ErrorKind::Diesel(e), _)) => AuthOidcResponse::BadRequest(ErrorResponse { +                message: e.to_string(), +            }), +            Err(Error(ErrorKind::Uuid(e), _)) => AuthOidcResponse::BadRequest(ErrorResponse { +                message: e.to_string(), +            }), +            Err(Error(ErrorKind::InvalidFatcatId(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: ErrorKind::InvalidFatcatId(e).to_string(), +                }) +            } +            Err(Error(ErrorKind::MalformedExternalId(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::MalformedChecksum(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::InvalidCredentials(e), _)) => +            // TODO: why can't I NotAuthorized here? +            { +                AuthOidcResponse::Forbidden(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { +                AuthOidcResponse::Forbidden(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(Error(ErrorKind::OtherBadRequest(e), _)) => { +                AuthOidcResponse::BadRequest(ErrorResponse { +                    message: e.to_string(), +                }) +            } +            Err(e) => { +                error!("{}", e); +                AuthOidcResponse::GenericError(ErrorResponse { +                    message: e.to_string(), +                }) +            } +        }; +        Box::new(futures::done(Ok(ret))) +    }  } diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 7a65f901..5c8e17d3 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -4,7 +4,7 @@ use api_helpers::uuid2fcid;  use chrono;  use database_schema::*;  use errors::*; -use fatcat_api_spec::models::{ChangelogEntry, Editgroup, EntityEdit}; +use fatcat_api_spec::models::{ChangelogEntry, Editgroup, Editor, EntityEdit};  use serde_json;  use uuid::Uuid; @@ -559,7 +559,7 @@ pub struct EditgroupRow {  }  impl EditgroupRow { -    /// Returns an Edigroup API model *without* the entity edits actually populated. Useful for, +    /// Returns an Editgroup API model *without* the entity edits actually populated. Useful for,      /// eg, entity history queries (where we already have the entity edit we want)      pub fn into_model_partial(self) -> Editgroup {          Editgroup { @@ -579,12 +579,36 @@ pub struct EditorRow {      pub username: String,      pub is_admin: bool,      pub is_bot: bool, +    pub is_active: bool,      pub registered: chrono::NaiveDateTime,      pub auth_epoch: chrono::NaiveDateTime,      pub wrangler_id: Option<Uuid>,      pub active_editgroup_id: Option<Uuid>,  } +impl EditorRow { +    pub fn into_model(self) -> Editor { +        Editor { +            editor_id: Some(uuid2fcid(&self.id)), +            username: self.username, +            is_admin: Some(self.is_admin), +            is_bot: Some(self.is_bot), +            is_active: Some(self.is_active), +        } +    } +} + +#[derive(Debug, Clone, Queryable, Associations, AsChangeset)] +#[table_name = "auth_oidc"] +pub struct AuthOidcRow { +    pub id: i64, +    pub created: chrono::NaiveDateTime, +    pub editor_id: Uuid, +    pub provider: String, +    pub oidc_iss: String, +    pub oidc_sub: String, +} +  #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]  #[table_name = "changelog"]  pub struct ChangelogRow { diff --git a/rust/src/database_schema.rs b/rust/src/database_schema.rs index c240048e..49863fc7 100644 --- a/rust/src/database_schema.rs +++ b/rust/src/database_schema.rs @@ -6,6 +6,17 @@ table! {  }  table! { +    auth_oidc (id) { +        id -> Int8, +        created -> Timestamptz, +        editor_id -> Uuid, +        provider -> Text, +        oidc_iss -> Text, +        oidc_sub -> Text, +    } +} + +table! {      changelog (id) {          id -> Int8,          editgroup_id -> Uuid, @@ -98,6 +109,7 @@ table! {          username -> Text,          is_admin -> Bool,          is_bot -> Bool, +        is_active -> Bool,          registered -> Timestamptz,          auth_epoch -> Timestamptz,          wrangler_id -> Nullable<Uuid>, @@ -387,6 +399,7 @@ table! {      }  } +joinable!(auth_oidc -> editor (editor_id));  joinable!(changelog -> editgroup (editgroup_id));  joinable!(container_edit -> editgroup (editgroup_id));  joinable!(container_ident -> container_rev (rev_id)); @@ -424,6 +437,7 @@ joinable!(work_ident -> work_rev (rev_id));  allow_tables_to_appear_in_same_query!(      abstracts, +    auth_oidc,      changelog,      container_edit,      container_ident, | 
