summaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2019-01-03 16:53:27 -0800
committerBryan Newbold <bnewbold@robocracy.org>2019-01-03 16:53:27 -0800
commit39678e1410a06e99ea71655485786caaf5847e7f (patch)
treed04ba9ae4083892e4d0208be952c98f174d1feef /rust
parent9bfb8e968fcecbe4dc729b89017d0606d271b287 (diff)
downloadfatcat-39678e1410a06e99ea71655485786caaf5847e7f.tar.gz
fatcat-39678e1410a06e99ea71655485786caaf5847e7f.zip
start to impl oidc auth
Diffstat (limited to 'rust')
-rw-r--r--rust/src/api_server.rs33
-rw-r--r--rust/src/api_wrappers.rs79
-rw-r--r--rust/src/database_models.rs28
-rw-r--r--rust/src/database_schema.rs14
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,