aboutsummaryrefslogtreecommitdiffstats
path: root/rust/src/api_wrappers.rs
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2019-01-08 16:28:27 -0800
committerBryan Newbold <bnewbold@robocracy.org>2019-01-08 16:28:27 -0800
commit16f2e78298dbd2231f5f337ea17c89a6a131a052 (patch)
tree6e72581e625e73c97cbab72d0f9c35665c99e5d7 /rust/src/api_wrappers.rs
parenteb40a5f274f3608db34309cfd16739a7642ef5e7 (diff)
parentffb721f90c5d97ee80885209bf45feb85ca9625c (diff)
downloadfatcat-16f2e78298dbd2231f5f337ea17c89a6a131a052.tar.gz
fatcat-16f2e78298dbd2231f5f337ea17c89a6a131a052.zip
Merge branch 'bnewbold-crude-auth'
Fixed a conflict in: python/fatcat_export.py
Diffstat (limited to 'rust/src/api_wrappers.rs')
-rw-r--r--rust/src/api_wrappers.rs352
1 files changed, 334 insertions, 18 deletions
diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs
index cf696d15..f03d4041 100644
--- a/rust/src/api_wrappers.rs
+++ b/rust/src/api_wrappers.rs
@@ -3,6 +3,7 @@
use api_entity_crud::EntityCrud;
use api_helpers::*;
use api_server::Server;
+use auth::*;
use database_models::EntityEditRow;
use diesel::Connection;
use errors::*;
@@ -80,14 +81,18 @@ macro_rules! wrap_entity_handlers {
&self,
entity: models::$model,
editgroup_id: Option<String>,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = $post_resp, 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, Some(stringify!($post_fn)))?;
+ auth_context.require_role(FatcatRole::Editor)?;
let editgroup_id = if let Some(s) = editgroup_id {
- Some(FatCatId::from_str(&s)?)
+ let eg_id = FatCatId::from_str(&s)?;
+ auth_context.require_editgroup(&conn, eg_id)?;
+ Some(eg_id)
} else { None };
- let edit_context = make_edit_context(&conn, editgroup_id, false)?;
+ let edit_context = make_edit_context(&conn, auth_context.editor_id, editgroup_id, false)?;
edit_context.check(&conn)?;
entity.db_create(&conn, &edit_context)?.into_model()
}) {
@@ -108,6 +113,11 @@ macro_rules! wrap_entity_handlers {
$post_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) =>
$post_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ $post_resp::Forbidden(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) =>
+ $post_resp::Forbidden(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::OtherBadRequest(e), _)) =>
$post_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
@@ -123,14 +133,18 @@ macro_rules! wrap_entity_handlers {
entity_list: &Vec<models::$model>,
autoaccept: Option<bool>,
editgroup_id: Option<String>,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = $post_batch_resp, 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, Some(stringify!($post_batch_fn)))?;
+ auth_context.require_role(FatcatRole::Editor)?;
let editgroup_id = if let Some(s) = editgroup_id {
- Some(FatCatId::from_str(&s)?)
+ let eg_id = FatCatId::from_str(&s)?;
+ auth_context.require_editgroup(&conn, eg_id)?;
+ Some(eg_id)
} else { None };
- self.$post_batch_handler(entity_list, autoaccept.unwrap_or(false), editgroup_id, &conn)
+ self.$post_batch_handler(entity_list, autoaccept.unwrap_or(false), auth_context.editor_id, editgroup_id, &conn)
}) {
Ok(edit) =>
$post_batch_resp::CreatedEntities(edit),
@@ -149,6 +163,11 @@ macro_rules! wrap_entity_handlers {
$post_batch_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) =>
$post_batch_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ $post_batch_resp::Forbidden(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) =>
+ $post_batch_resp::Forbidden(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::OtherBadRequest(e), _)) =>
$post_batch_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(e) => {
@@ -164,15 +183,19 @@ macro_rules! wrap_entity_handlers {
ident: String,
entity: models::$model,
editgroup_id: Option<String>,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = $update_resp, 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, Some(stringify!($update_fn)))?;
+ auth_context.require_role(FatcatRole::Editor)?;
let entity_id = FatCatId::from_str(&ident)?;
let editgroup_id = if let Some(s) = editgroup_id {
- Some(FatCatId::from_str(&s)?)
+ let eg_id = FatCatId::from_str(&s)?;
+ auth_context.require_editgroup(&conn, eg_id)?;
+ Some(eg_id)
} else { None };
- let edit_context = make_edit_context(&conn, editgroup_id, false)?;
+ let edit_context = make_edit_context(&conn, auth_context.editor_id, editgroup_id, false)?;
edit_context.check(&conn)?;
entity.db_update(&conn, &edit_context, entity_id)?.into_model()
}) {
@@ -199,6 +222,11 @@ macro_rules! wrap_entity_handlers {
$update_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::OtherBadRequest(e), _)) =>
$update_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ $update_resp::Forbidden(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) =>
+ $update_resp::Forbidden(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$update_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -211,16 +239,22 @@ macro_rules! wrap_entity_handlers {
&self,
ident: String,
editgroup_id: Option<String>,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = $delete_resp, 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, Some(stringify!($delete_fn)))?;
+ auth_context.require_role(FatcatRole::Editor)?;
let entity_id = FatCatId::from_str(&ident)?;
let editgroup_id: Option<FatCatId> = match editgroup_id {
- Some(s) => Some(FatCatId::from_str(&s)?),
+ Some(s) => {
+ let editgroup_id = FatCatId::from_str(&s)?;
+ auth_context.require_editgroup(&conn, editgroup_id)?;
+ Some(editgroup_id)
+ },
None => None,
};
- let edit_context = make_edit_context(&conn, editgroup_id, false)?;
+ let edit_context = make_edit_context(&conn, auth_context.editor_id, editgroup_id, false)?;
edit_context.check(&conn)?;
$model::db_delete(&conn, &edit_context, entity_id)?.into_model()
}) {
@@ -243,6 +277,11 @@ macro_rules! wrap_entity_handlers {
$delete_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::OtherBadRequest(e), _)) =>
$delete_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ $delete_resp::Forbidden(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) =>
+ $delete_resp::Forbidden(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$delete_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -353,16 +392,19 @@ macro_rules! wrap_entity_handlers {
fn $delete_edit_fn(
&self,
edit_id: String,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = $delete_edit_resp, Error = ApiError> + Send> {
let conn = self.db_pool.get().expect("db_pool error");
let ret = match conn.transaction(|| {
let edit_id = Uuid::from_str(&edit_id)?;
+ let auth_context = self.auth_confectionary.require_auth(&conn, &context.auth_data, Some(stringify!($delete_edit_fn)))?;
+ auth_context.require_role(FatcatRole::Editor)?;
+ let edit = $model::db_get_edit(&conn, edit_id)?;
+ auth_context.require_editgroup(&conn, FatCatId::from_uuid(&edit.editgroup_id))?;
$model::db_delete_edit(&conn, edit_id)
}) {
Ok(()) =>
- $delete_edit_resp::DeletedEdit(Success { message: format!("Successfully deleted work-in-progress {} edit: {}", stringify!($model), edit_id) } ),
- Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
+ $delete_edit_resp::DeletedEdit(Success { message: format!("Successfully deleted work-in-progress {} edit: {}", stringify!($model), edit_id) } ), Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
$delete_edit_resp::NotFound(ErrorResponse { message: format!("No such {} edit: {}", stringify!($model), edit_id) }),
Err(Error(ErrorKind::Diesel(e), _)) =>
$delete_edit_resp::BadRequest(ErrorResponse { message: e.to_string() }),
@@ -370,6 +412,11 @@ macro_rules! wrap_entity_handlers {
$delete_edit_resp::BadRequest(ErrorResponse { message: e.to_string() }),
Err(Error(ErrorKind::OtherBadRequest(e), _)) =>
$delete_edit_resp::BadRequest(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ $delete_edit_resp::Forbidden(ErrorResponse { message: e.to_string() }),
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) =>
+ $delete_edit_resp::Forbidden(ErrorResponse { message: e.to_string() }),
Err(e) => {
error!("{}", e);
$delete_edit_resp::GenericError(ErrorResponse { message: e.to_string() })
@@ -856,14 +903,98 @@ impl Api for Server {
Box::new(futures::done(Ok(ret)))
}
+ /// For now, only implements updating username
+ fn update_editor(
+ &self,
+ editor_id: String,
+ editor: models::Editor,
+ context: &Context,
+ ) -> Box<Future<Item = UpdateEditorResponse, Error = ApiError> + Send> {
+ let conn = self.db_pool.get().expect("db_pool error");
+ let ret = match conn.transaction(|| {
+ if Some(editor_id.clone()) != editor.editor_id {
+ return Err(
+ ErrorKind::OtherBadRequest("editor_id doesn't match".to_string()).into(),
+ );
+ }
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("update_editor"),
+ )?;
+ let editor_id = FatCatId::from_str(&editor_id)?;
+ // DANGER! these permissions are for username updates only!
+ if editor_id == auth_context.editor_id {
+ // self edit of username allowed
+ auth_context.require_role(FatcatRole::Editor)?;
+ } else {
+ // admin can update any username
+ auth_context.require_role(FatcatRole::Admin)?;
+ };
+ update_editor_username(&conn, editor_id, editor.username).map(|e| e.into_model())
+ }) {
+ Ok(editor) => UpdateEditorResponse::UpdatedEditor(editor),
+ Err(Error(ErrorKind::Diesel(e), _)) => {
+ UpdateEditorResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::Uuid(e), _)) => UpdateEditorResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ }),
+ Err(Error(ErrorKind::InvalidFatcatId(e), _)) => {
+ UpdateEditorResponse::BadRequest(ErrorResponse {
+ message: ErrorKind::InvalidFatcatId(e).to_string(),
+ })
+ }
+ Err(Error(ErrorKind::MalformedExternalId(e), _)) => {
+ UpdateEditorResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ {
+ UpdateEditorResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => {
+ UpdateEditorResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::OtherBadRequest(e), _)) => {
+ UpdateEditorResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(e) => {
+ error!("{}", e);
+ UpdateEditorResponse::GenericError(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ };
+ Box::new(futures::done(Ok(ret)))
+ }
+
fn accept_editgroup(
&self,
editgroup_id: String,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = AcceptEditgroupResponse, Error = ApiError> + Send> {
let conn = self.db_pool.get().expect("db_pool error");
let ret = match conn.transaction(|| {
let editgroup_id = FatCatId::from_str(&editgroup_id)?;
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("accept_editgroup"),
+ )?;
+ auth_context.require_role(FatcatRole::Admin)?;
+ // NOTE: this is currently redundant, but zero-cost
+ auth_context.require_editgroup(&conn, editgroup_id)?;
self.accept_editgroup_handler(editgroup_id, &conn)
}) {
Ok(()) => AcceptEditgroupResponse::MergedSuccessfully(Success {
@@ -879,6 +1010,16 @@ impl Api for Server {
message: ErrorKind::EditgroupAlreadyAccepted(e).to_string(),
})
}
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) => {
+ AcceptEditgroupResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => {
+ AcceptEditgroupResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
Err(e) => AcceptEditgroupResponse::GenericError(ErrorResponse {
message: e.to_string(),
}),
@@ -916,11 +1057,42 @@ impl Api for Server {
fn create_editgroup(
&self,
entity: models::Editgroup,
- _context: &Context,
+ context: &Context,
) -> Box<Future<Item = CreateEditgroupResponse, Error = ApiError> + Send> {
let conn = self.db_pool.get().expect("db_pool error");
- let ret = match conn.transaction(|| self.create_editgroup_handler(entity, &conn)) {
+ let ret = match conn.transaction(|| {
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("create_editgroup"),
+ )?;
+ auth_context.require_role(FatcatRole::Editor)?;
+ let mut entity = entity.clone();
+ match entity.editor_id.clone() {
+ Some(editor_id) => {
+ if !auth_context.has_role(FatcatRole::Admin) {
+ if editor_id != auth_context.editor_id.to_string() {
+ bail!("not authorized to create editgroups in others' names");
+ }
+ }
+ }
+ None => {
+ entity.editor_id = Some(auth_context.editor_id.to_string());
+ }
+ };
+ self.create_editgroup_handler(entity, &conn)
+ }) {
Ok(eg) => CreateEditgroupResponse::SuccessfullyCreated(eg),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) => {
+ CreateEditgroupResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => {
+ CreateEditgroupResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
Err(e) =>
// TODO: dig in to error type here
{
@@ -974,4 +1146,148 @@ 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,
+ Some("auth_oidc"),
+ )?;
+ auth_context.require_role(FatcatRole::Superuser)?;
+ let (editor, created) = self.auth_oidc_handler(params, &conn)?;
+ // create an auth token with 31 day duration
+ let token = self.auth_confectionary.create_token(
+ FatCatId::from_str(&editor.editor_id.clone().unwrap())?,
+ Some(chrono::Duration::days(31)),
+ )?;
+ 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)))
+ }
+
+ fn auth_check(
+ &self,
+ role: Option<String>,
+ context: &Context,
+ ) -> Box<Future<Item = AuthCheckResponse, 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,
+ Some("auth_check"),
+ )?;
+ if let Some(role) = role {
+ let role = match role.to_lowercase().as_ref() {
+ "superuser" => FatcatRole::Superuser,
+ "admin" => FatcatRole::Admin,
+ "editor" => FatcatRole::Editor,
+ "bot" => FatcatRole::Bot,
+ "human" => FatcatRole::Human,
+ "public" => FatcatRole::Public,
+ _ => bail!("unknown auth role: {}", role),
+ };
+ auth_context.require_role(role)?;
+ };
+ Ok(())
+ }) {
+ Ok(()) => AuthCheckResponse::Success(Success {
+ message: "auth check successful!".to_string(),
+ }),
+ Err(Error(ErrorKind::Diesel(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ }),
+ Err(Error(ErrorKind::Uuid(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ {
+ AuthCheckResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => {
+ AuthCheckResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(Error(ErrorKind::OtherBadRequest(e), _)) => {
+ AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ Err(e) => {
+ error!("{}", e);
+ AuthCheckResponse::GenericError(ErrorResponse {
+ message: e.to_string(),
+ })
+ }
+ };
+ Box::new(futures::done(Ok(ret)))
+ }
}