aboutsummaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2018-12-31 14:57:29 -0800
committerBryan Newbold <bnewbold@robocracy.org>2018-12-31 14:57:29 -0800
commite16ba3d02564121ae5f27e0784be86137f3b9386 (patch)
treee1e19c2e1b678d041b64501c35a1ace6e8da900b /rust
parent230032ec1a13dd3830bcffed6112c2fddabc4b6e (diff)
downloadfatcat-e16ba3d02564121ae5f27e0784be86137f3b9386.tar.gz
fatcat-e16ba3d02564121ae5f27e0784be86137f3b9386.zip
rustfmt; implement role-based auth checks
Diffstat (limited to 'rust')
-rw-r--r--rust/src/api_helpers.rs17
-rw-r--r--rust/src/api_server.rs3
-rw-r--r--rust/src/api_wrappers.rs113
-rw-r--r--rust/src/auth.rs192
-rw-r--r--rust/src/bin/fatcat-auth.rs53
-rw-r--r--rust/src/bin/fatcatd.rs1
-rw-r--r--rust/src/lib.rs20
-rw-r--r--rust/tests/test_auth.rs12
8 files changed, 304 insertions, 107 deletions
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs
index 3c5a2e17..7478da9d 100644
--- a/rust/src/api_helpers.rs
+++ b/rust/src/api_helpers.rs
@@ -205,23 +205,23 @@ fn test_hide_flags() {
pub fn make_edit_context(
conn: &DbConn,
+ editor_id: FatCatId,
editgroup_id: Option<FatCatId>,
autoaccept: bool,
) -> Result<EditContext> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
let editgroup_id: FatCatId = match (editgroup_id, autoaccept) {
(Some(eg), _) => eg,
// If autoaccept and no editgroup_id passed, always create a new one for this transaction
(None, true) => {
let eg_row: EditgroupRow = diesel::insert_into(editgroup::table)
- .values((editgroup::editor_id.eq(editor_id),))
+ .values((editgroup::editor_id.eq(editor_id.to_uuid()),))
.get_result(conn)?;
FatCatId::from_uuid(&eg_row.id)
}
- (None, false) => FatCatId::from_uuid(&get_or_create_editgroup(editor_id, conn)?),
+ (None, false) => FatCatId::from_uuid(&get_or_create_editgroup(editor_id.to_uuid(), conn)?),
};
Ok(EditContext {
- editor_id: FatCatId::from_uuid(&editor_id),
+ editor_id: editor_id,
editgroup_id: editgroup_id,
extra_json: None,
autoaccept: autoaccept,
@@ -229,7 +229,12 @@ pub fn make_edit_context(
}
// TODO: verify username (alphanum, etc)
-pub fn create_editor(conn: &DbConn, username: String, is_admin: bool, is_bot: bool) -> Result<EditorRow> {
+pub fn create_editor(
+ conn: &DbConn,
+ username: String,
+ is_admin: bool,
+ is_bot: bool,
+) -> Result<EditorRow> {
let ed: EditorRow = diesel::insert_into(editor::table)
.values((
editor::username.eq(username),
@@ -237,7 +242,7 @@ pub fn create_editor(conn: &DbConn, username: String, is_admin: bool, is_bot: bo
editor::is_bot.eq(is_bot),
))
.get_result(conn)?;
- Ok(ed)
+ Ok(ed)
}
/// This function should always be run within a transaction
diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs
index cbf5be21..853f7bc2 100644
--- a/rust/src/api_server.rs
+++ b/rust/src/api_server.rs
@@ -20,11 +20,12 @@ macro_rules! entity_batch_handler {
&self,
entity_list: &[models::$model],
autoaccept: bool,
+ editor_id: FatCatId,
editgroup_id: Option<FatCatId>,
conn: &DbConn,
) -> Result<Vec<EntityEdit>> {
- let edit_context = make_edit_context(conn, editgroup_id, autoaccept)?;
+ let edit_context = make_edit_context(conn, editor_id, editgroup_id, autoaccept)?;
edit_context.check(&conn)?;
let model_list: Vec<&models::$model> = entity_list.iter().map(|e| e).collect();
let edits = $model::db_create_batch(conn, &edit_context, model_list.as_slice())?;
diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs
index 25b4fab1..ae070e02 100644
--- a/rust/src/api_wrappers.rs
+++ b/rust/src/api_wrappers.rs
@@ -2,8 +2,8 @@
use api_entity_crud::EntityCrud;
use api_helpers::*;
-use auth::*;
use api_server::Server;
+use auth::*;
use database_models::EntityEditRow;
use diesel::Connection;
use errors::*;
@@ -85,12 +85,14 @@ macro_rules! wrap_entity_handlers {
) -> 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.parse_swagger(&conn, &context.auth_data)?;
- // XXX: auth_context.expect("not authorized");
+ let auth_context = self.auth_confectionary.require_auth(&conn, &context.auth_data)?;
+ 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()
}) {
@@ -111,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) => {
@@ -126,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)?;
+ 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),
@@ -152,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) => {
@@ -167,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)?;
+ 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()
}) {
@@ -202,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() })
@@ -214,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)?;
+ 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()
}) {
@@ -246,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() })
@@ -356,11 +392,15 @@ 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)?;
+ 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(()) =>
@@ -373,6 +413,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() })
@@ -862,11 +907,17 @@ impl Api for Server {
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)?;
+ 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 {
@@ -882,6 +933,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(),
}),
@@ -919,11 +980,27 @@ 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)?;
+ auth_context.require_role(FatcatRole::Editor)?;
+ 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
{
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index 450a19d6..ee3c6fb0 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -1,33 +1,76 @@
//! Editor bearer token authentication
-use swagger::auth::AuthData;
-use macaroon::{Format, Macaroon, Verifier};
use data_encoding::BASE64;
+use macaroon::{Format, Macaroon, Verifier};
+use swagger::auth::AuthData;
-use std::collections::HashMap;
-use database_models::*;
-use database_schema::*;
use api_helpers::*;
use chrono::prelude::*;
+use database_models::*;
+use database_schema::*;
use diesel;
use diesel::prelude::*;
use errors::*;
+use std::collections::HashMap;
use std::str::FromStr;
// 32 bytes max (!)
static DUMMY_KEY: &[u8] = b"dummy-key-a-one-two-three-a-la";
+#[derive(Clone, Copy, Debug)]
+pub enum FatcatRole {
+ Public,
+ Editor,
+ Bot,
+ Human,
+ Admin,
+}
+
#[derive(Clone)]
pub struct AuthContext {
pub editor_id: FatCatId,
editor_row: EditorRow,
- roles: Vec<String>, // TODO: BTreeSet
}
impl AuthContext {
+ pub fn has_role(&self, role: FatcatRole) -> bool {
+ if self.editor_row.is_admin {
+ return true;
+ }
+ match role {
+ FatcatRole::Public => true,
+ FatcatRole::Editor => true,
+ FatcatRole::Bot => self.editor_row.is_bot,
+ FatcatRole::Human => !self.editor_row.is_bot,
+ FatcatRole::Admin => self.editor_row.is_admin,
+ }
+ }
- pub fn has_role(&self, role: &str) -> bool {
- self.roles.contains(&role.to_string()) || self.roles.contains(&"admin".to_string())
+ pub fn require_role(&self, role: FatcatRole) -> Result<()> {
+ match self.has_role(role) {
+ true => Ok(()),
+ // TODO: better message
+ false => Err(ErrorKind::InsufficientPrivileges(
+ "doesn't have required role".to_string(),
+ )
+ .into()),
+ }
+ }
+
+ pub fn require_editgroup(&self, conn: &DbConn, editgroup_id: FatCatId) -> Result<()> {
+ if self.has_role(FatcatRole::Admin) {
+ return Ok(())
+ }
+ let editgroup: EditgroupRow = editgroup::table
+ .find(editgroup_id.to_uuid())
+ .get_result(conn)?;
+ match editgroup.editor_id == self.editor_id.to_uuid() {
+ true => Ok(()),
+ false => Err(ErrorKind::InsufficientPrivileges(
+ "editor does not own this editgroup".to_string(),
+ )
+ .into()),
+ }
}
}
@@ -40,7 +83,11 @@ pub struct AuthConfectionary {
}
impl AuthConfectionary {
- pub fn new(location: String, identifier: String, key_base64: String) -> Result<AuthConfectionary> {
+ pub fn new(
+ location: String,
+ identifier: String,
+ key_base64: String,
+ ) -> Result<AuthConfectionary> {
let key = BASE64.decode(key_base64.as_bytes())?;
let mut root_keys = HashMap::new();
root_keys.insert(identifier.clone(), key.clone());
@@ -57,18 +104,26 @@ impl AuthConfectionary {
"test.fatcat.wiki".to_string(),
"dummy".to_string(),
BASE64.encode(DUMMY_KEY),
- ).unwrap()
+ )
+ .unwrap()
}
- pub fn create_token(&self, editor_id: FatCatId, expires: Option<DateTime<Utc>>) -> Result<String> {
- let mut mac = Macaroon::create(&self.location, &self.key, &self.identifier).expect("Macaroon creation");
+ pub fn create_token(
+ &self,
+ editor_id: FatCatId,
+ expires: Option<DateTime<Utc>>,
+ ) -> Result<String> {
+ let mut mac = Macaroon::create(&self.location, &self.key, &self.identifier)
+ .expect("Macaroon creation");
mac.add_first_party_caveat(&format!("editor_id = {}", editor_id.to_string()));
// TODO: put created one second in the past to prevent timing synchronization glitches?
let now = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true);
mac.add_first_party_caveat(&format!("created = {}", now));
if let Some(expires) = expires {
- mac.add_first_party_caveat(&format!("expires = {:?}",
- &expires.to_rfc3339_opts(SecondsFormat::Secs, true)));
+ mac.add_first_party_caveat(&format!(
+ "expires = {:?}",
+ &expires.to_rfc3339_opts(SecondsFormat::Secs, true)
+ ));
};
let raw = mac.serialize(Format::V2).expect("macaroon serialization");
Ok(BASE64.encode(&raw))
@@ -79,18 +134,30 @@ impl AuthConfectionary {
let raw = BASE64.decode(s.as_bytes())?;
let mac = match Macaroon::deserialize(&raw) {
Ok(m) => m,
- Err(e) => bail!("macaroon deserialize error: {:?}", e),
+ Err(_e) => {
+ // TODO: should be "chaining" here
+ //bail!("macaroon deserialize error: {:?}", e),
+ return Err(
+ ErrorKind::InvalidCredentials("macaroon deserialize error".to_string()).into(),
+ );
+ }
};
let mac = match mac.validate() {
Ok(m) => m,
- Err(e) => bail!("macaroon validate error: {:?}", e),
+ Err(_e) => {
+ // TODO: should be "chaining" here
+ //bail!("macaroon validate error: {:?}", e),
+ return Err(
+ ErrorKind::InvalidCredentials("macaroon validate error".to_string()).into(),
+ );
+ }
};
let mut verifier = Verifier::new();
let mut editor_id: Option<FatCatId> = None;
for caveat in mac.first_party_caveats() {
if caveat.predicate().starts_with("editor_id = ") {
editor_id = Some(FatCatId::from_str(caveat.predicate().get(12..).unwrap())?);
- break
+ break;
}
}
let editor_id = editor_id.expect("expected an editor_id caveat");
@@ -98,20 +165,27 @@ impl AuthConfectionary {
let mut created: Option<DateTime<Utc>> = None;
for caveat in mac.first_party_caveats() {
if caveat.predicate().starts_with("created = ") {
- created = Some(DateTime::parse_from_rfc3339(caveat.predicate().get(10..).unwrap())
- .unwrap()
- .with_timezone(&Utc));
- break
+ created = Some(
+ DateTime::parse_from_rfc3339(caveat.predicate().get(10..).unwrap())
+ .unwrap()
+ .with_timezone(&Utc),
+ );
+ break;
}
}
let created = created.expect("expected a 'created' caveat");
- verifier.satisfy_exact(&format!("created = {}", created.to_rfc3339_opts(SecondsFormat::Secs, true)));
- let editor: EditorRow = editor::table
- .find(&editor_id.to_uuid())
- .get_result(conn)?;
+ verifier.satisfy_exact(&format!(
+ "created = {}",
+ created.to_rfc3339_opts(SecondsFormat::Secs, true)
+ ));
+ let editor: EditorRow = editor::table.find(&editor_id.to_uuid()).get_result(conn)?;
let auth_epoch = DateTime::<Utc>::from_utc(editor.auth_epoch, Utc);
if created < auth_epoch {
- bail!("token created before current auth_epoch (was probably revoked by editor)")
+ return Err(ErrorKind::InvalidCredentials(
+ "token created before current auth_epoch (was probably revoked by editor)"
+ .to_string(),
+ )
+ .into());
}
verifier.satisfy_general(|p: &str| -> bool {
// not expired (based on expires)
@@ -126,42 +200,78 @@ impl AuthConfectionary {
});
let verify_key = match self.root_keys.get(mac.identifier()) {
Some(key) => key,
- None => bail!("key not found for identifier: {}", mac.identifier()),
+ None => {
+ // TODO: better message
+ //bail!("key not found for identifier: {}", mac.identifier()),
+ return Err(ErrorKind::InvalidCredentials(
+ "key not found for identifier".to_string(),
+ )
+ .into());
+ }
};
match mac.verify(verify_key, &mut verifier) {
Ok(true) => (),
- Ok(false) => bail!("token overall verification failed"),
- Err(e) => bail!("token parsing failed: {:?}", e),
+ Ok(false) => {
+ return Err(ErrorKind::InvalidCredentials(
+ "token overall verification failed".to_string(),
+ )
+ .into());
+ }
+ Err(_e) => {
+ // TODO: chain
+ //bail!("token parsing failed: {:?}", e),
+ return Err(
+ ErrorKind::InvalidCredentials("token parsing failed".to_string()).into(),
+ );
+ }
}
Ok(editor)
}
- pub fn parse_swagger(&self, conn: &DbConn, auth_data: &Option<AuthData>) -> Result<Option<AuthContext>> {
-
+ pub fn parse_swagger(
+ &self,
+ conn: &DbConn,
+ auth_data: &Option<AuthData>,
+ ) -> Result<Option<AuthContext>> {
let token: Option<String> = match auth_data {
Some(AuthData::ApiKey(header)) => {
- let header: Vec<String> = header.split_whitespace().map(|s| s.to_string()).collect();
+ let header: Vec<String> =
+ header.split_whitespace().map(|s| s.to_string()).collect();
if !(header.len() == 2 && header[0] == "Bearer") {
- bail!("invalid Bearer Auth HTTP header");
+ return Err(ErrorKind::InvalidCredentials(
+ "invalid Bearer Auth HTTP header".to_string(),
+ )
+ .into());
}
Some(header[1].clone())
- },
+ }
None => None,
- _ => bail!("Authentication HTTP Header should either be empty or a Beaerer API key"),
+ _ => {
+ return Err(ErrorKind::InvalidCredentials(
+ "Authentication HTTP Header should either be empty or a Beaerer API key"
+ .to_string(),
+ )
+ .into());
+ }
};
let token = match token {
Some(t) => t,
None => return Ok(None),
};
let editor_row = self.parse_macaroon_token(conn, &token)?;
- let roles = if editor_row.is_admin { vec!["admin".to_string()] } else { vec![] };
Ok(Some(AuthContext {
editor_id: FatCatId::from_uuid(&editor_row.id),
editor_row: editor_row,
- roles: roles,
}))
}
+ pub fn require_auth(&self, conn: &DbConn, auth_data: &Option<AuthData>) -> Result<AuthContext> {
+ match self.parse_swagger(conn, auth_data)? {
+ Some(auth) => Ok(auth),
+ None => Err(ErrorKind::InvalidCredentials("no token supplied".to_string()).into()),
+ }
+ }
+
// TODO: refactor out of this file?
/// Only used from CLI tool
pub fn inspect_token(&self, conn: &DbConn, token: &str) -> Result<()> {
@@ -206,13 +316,13 @@ pub fn revoke_tokens_everyone(conn: &DbConn) -> Result<()> {
// TODO: refactor out of this file?
/// Only used from CLI tool
-pub fn print_editors(conn: &DbConn) -> Result<()>{
+pub fn print_editors(conn: &DbConn) -> Result<()> {
// iterate over all editors. format id, print flags, auth_epoch
- let all_editors: Vec<EditorRow> = editor::table
- .load(conn)?;
+ let all_editors: Vec<EditorRow> = editor::table.load(conn)?;
println!("editor_id\t\t\tis_admin/is_bot\tauth_epoch\t\t\tusername\twrangler_id");
for e in all_editors {
- println!("{}\t{}\t{}\t{}\t{}\t{:?}",
+ println!(
+ "{}\t{}\t{}\t{}\t{}\t{:?}",
FatCatId::from_uuid(&e.id).to_string(),
e.is_admin,
e.is_bot,
diff --git a/rust/src/bin/fatcat-auth.rs b/rust/src/bin/fatcat-auth.rs
index 3240964f..addd2b66 100644
--- a/rust/src/bin/fatcat-auth.rs
+++ b/rust/src/bin/fatcat-auth.rs
@@ -8,16 +8,16 @@ extern crate dotenv;
extern crate error_chain;
extern crate fatcat;
//#[macro_use]
-extern crate log;
extern crate env_logger;
+extern crate log;
extern crate serde_json;
extern crate uuid;
use clap::{App, SubCommand};
use diesel::prelude::*;
-use fatcat::errors::*;
use fatcat::api_helpers::FatCatId;
+use fatcat::errors::*;
use std::str::FromStr;
//use uuid::Uuid;
@@ -26,15 +26,13 @@ use std::str::FromStr;
//use std::io::prelude::*;
//use std::io::{BufReader, BufWriter};
-
fn run() -> Result<()> {
let m = App::new("fatcat-auth")
.version(env!("CARGO_PKG_VERSION"))
.author("Bryan Newbold <bnewbold@archive.org>")
.about("Editor authentication admin tool")
.subcommand(
- SubCommand::with_name("list-editors")
- .about("Prints all currently registered editors")
+ SubCommand::with_name("list-editors").about("Prints all currently registered editors"),
)
.subcommand(
SubCommand::with_name("create-editor")
@@ -42,41 +40,37 @@ fn run() -> Result<()> {
.args_from_usage(
"<username> 'username for editor'
--admin 'creates editor with admin privs'
- --bot 'this editor is a bot'"
- )
+ --bot 'this editor is a bot'",
+ ),
)
.subcommand(
SubCommand::with_name("create-token")
.about("Creates a new auth token (macaroon) for the given editor")
.args_from_usage(
"<editor-id> 'id of the editor (fatcatid, not username)'
- --env-format 'outputs in a format that shells can source'" // TODO
- )
+ --env-format 'outputs in a format that shells can source'", // TODO
+ ),
)
.subcommand(
SubCommand::with_name("inspect-token")
.about("Dumps token metadata (and whether it is valid)")
- .args_from_usage(
- "<token> 'base64-encoded token (macaroon)'"
- )
+ .args_from_usage("<token> 'base64-encoded token (macaroon)'"),
)
.subcommand(
SubCommand::with_name("create-key")
.about("Creates a new auth secret key (aka, root/signing key for tokens)")
.args_from_usage(
- "--env-format 'outputs in a format that shells can source'" // TODO
- )
+ "--env-format 'outputs in a format that shells can source'", // TODO
+ ),
)
.subcommand(
SubCommand::with_name("revoke-tokens")
.about("Resets auth_epoch for a single editor (invalidating all existing tokens)")
- .args_from_usage(
- "<editor-id> 'identifier (fcid) of editor'"
- )
+ .args_from_usage("<editor-id> 'identifier (fcid) of editor'"),
)
.subcommand(
SubCommand::with_name("revoke-tokens-everyone")
- .about("Resets auth_epoch for all editors (invalidating tokens for all users!)")
+ .about("Resets auth_epoch for all editors (invalidating tokens for all users!)"),
)
.get_matches();
@@ -84,27 +78,30 @@ fn run() -> Result<()> {
match m.subcommand() {
("create-key", Some(_subm)) => {
println!("{}", fatcat::auth::create_key());
- return Ok(())
- },
+ return Ok(());
+ }
_ => (),
}
// Then the ones that do
- let db_conn = fatcat::database_worker_pool()?.get().expect("database pool");
+ let db_conn = fatcat::database_worker_pool()?
+ .get()
+ .expect("database pool");
let confectionary = fatcat::env_confectionary()?;
match m.subcommand() {
("list-editors", Some(_subm)) => {
fatcat::auth::print_editors(&db_conn)?;
- },
+ }
("create-editor", Some(subm)) => {
let editor = fatcat::api_helpers::create_editor(
&db_conn,
subm.value_of("username").unwrap().to_string(),
subm.is_present("admin"),
- subm.is_present("bot"))?;
+ subm.is_present("bot"),
+ )?;
//println!("{:?}", editor);
println!("{}", FatCatId::from_uuid(&editor.id).to_string());
- },
+ }
("create-token", Some(subm)) => {
let editor_id = FatCatId::from_str(subm.value_of("editor-id").unwrap())?;
// check that editor exists
@@ -112,19 +109,19 @@ fn run() -> Result<()> {
.find(&editor_id.to_uuid())
.get_result(&db_conn)?;
println!("{}", confectionary.create_token(editor_id, None)?);
- },
+ }
("inspect-token", Some(subm)) => {
confectionary.inspect_token(&db_conn, subm.value_of("token").unwrap())?;
- },
+ }
("revoke-tokens", Some(subm)) => {
let editor_id = FatCatId::from_str(subm.value_of("editor-id").unwrap())?;
fatcat::auth::revoke_tokens(&db_conn, editor_id)?;
println!("success!");
- },
+ }
("revoke-tokens-everyone", Some(_subm)) => {
fatcat::auth::revoke_tokens_everyone(&db_conn)?;
println!("success!");
- },
+ }
_ => {
println!("Missing or unimplemented command!");
println!("{}", m.usage());
diff --git a/rust/src/bin/fatcatd.rs b/rust/src/bin/fatcatd.rs
index 7def7f66..7d77d90b 100644
--- a/rust/src/bin/fatcatd.rs
+++ b/rust/src/bin/fatcatd.rs
@@ -21,7 +21,6 @@ use iron::{status, Chain, Iron, IronResult, Request, Response};
use iron_slog::{DefaultLogFormatter, LoggerMiddleware};
use slog::{Drain, Logger};
-
/// Create custom server, wire it to the autogenerated router,
/// and pass it to the web server.
fn main() {
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 233f8642..a31404da 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -22,16 +22,16 @@ extern crate data_encoding;
extern crate regex;
#[macro_use]
extern crate lazy_static;
-extern crate sha1;
extern crate macaroon;
+extern crate sha1;
pub mod api_entity_crud;
pub mod api_helpers;
pub mod api_server;
pub mod api_wrappers;
+pub mod auth;
pub mod database_models;
pub mod database_schema;
-pub mod auth;
pub mod errors {
// Create the Error, ErrorKind, ResultExt, and Result types
@@ -74,6 +74,14 @@ pub mod errors {
description("Invalid Entity State Transform")
display("tried to mutate an entity which was not in an appropriate state: {}", message)
}
+ InvalidCredentials(message: String) {
+ description("auth token was missing, expired, revoked, or corrupt")
+ display("auth token was missing, expired, revoked, or corrupt: {}", message)
+ }
+ InsufficientPrivileges(message: String) {
+ description("editor account doesn't have authorization")
+ display("editor account doesn't have authorization: {}", message)
+ }
OtherBadRequest(message: String) {
description("catch-all error for bad or unallowed requests")
display("broke a constraint or made an otherwise invalid request: {}", message)
@@ -86,14 +94,13 @@ pub mod errors {
pub use errors::*;
pub use self::errors::*;
+use auth::AuthConfectionary;
use diesel::pg::PgConnection;
-use diesel::prelude::*;
use diesel::r2d2::ConnectionManager;
use dotenv::dotenv;
use iron::middleware::AfterMiddleware;
use iron::{Request, Response};
use std::env;
-use auth::AuthConfectionary;
#[cfg(feature = "postgres")]
embed_migrations!("../migrations/");
@@ -127,7 +134,10 @@ pub fn server() -> Result<api_server::Server> {
.build(manager)
.expect("Failed to create database pool.");
let confectionary = env_confectionary()?;
- Ok(api_server::Server { db_pool: pool, auth_confectionary: confectionary })
+ Ok(api_server::Server {
+ db_pool: pool,
+ auth_confectionary: confectionary,
+ })
}
pub fn test_server() -> Result<api_server::Server> {
diff --git a/rust/tests/test_auth.rs b/rust/tests/test_auth.rs
index 5b04d595..8d20dafd 100644
--- a/rust/tests/test_auth.rs
+++ b/rust/tests/test_auth.rs
@@ -1,17 +1,16 @@
-
+extern crate chrono;
extern crate fatcat;
extern crate uuid;
-extern crate chrono;
-use std::str::FromStr;
use chrono::prelude::*;
-use fatcat::auth::*;
use fatcat::api_helpers::*;
+use fatcat::auth::*;
+use std::str::FromStr;
#[test]
fn test_macaroons() {
// Test everything we can without connecting to database
-
+
let c = fatcat::auth::AuthConfectionary::new_dummy();
let editor_id = FatCatId::from_str("q3nouwy3nnbsvo3h5klxsx4a7y").unwrap();
@@ -23,7 +22,6 @@ fn test_macaroons() {
c.create_token(editor_id, Some(tomorrow)).unwrap();
}
-
#[test]
fn test_auth_db() {
// Test things that require database
@@ -39,7 +37,7 @@ fn test_auth_db() {
// verify token
let editor_row = c.parse_macaroon_token(&conn, &token).unwrap();
assert_eq!(editor_row.id, editor_id.to_uuid());
-
+
// revoke token
revoke_tokens(&conn, editor_id).unwrap();