summaryrefslogtreecommitdiffstats
path: root/rust/src/auth.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/auth.rs')
-rw-r--r--rust/src/auth.rs293
1 files changed, 110 insertions, 183 deletions
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index 580cde28..c188233a 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -1,208 +1,151 @@
//! Editor bearer token authentication
-use swagger::auth::{AuthData, Authorization, Scopes};
+use swagger::auth::AuthData;
use macaroon::{Format, Macaroon, Verifier};
use data_encoding::BASE64;
-use std::collections::BTreeSet;
-use std::fmt;
+use std::collections::{BTreeSet,HashMap};
use database_models::*;
use database_schema::*;
use api_helpers::*;
use chrono::prelude::*;
use diesel;
-use iron;
use diesel::prelude::*;
use errors::*;
-//use serde_json;
use std::str::FromStr;
-//use uuid::Uuid;
// 32 bytes max (!)
static DUMMY_KEY: &[u8] = b"dummy-key-a-one-two-three-a-la";
-#[derive(Debug)]
-pub struct OpenAuthMiddleware;
-
-#[derive(Debug)]
-pub struct AuthError {
- msg: String,
+#[derive(Clone)]
+pub struct AuthContext {
+ pub editor_id: FatCatId,
+ editor_row: EditorRow,
+ roles: Vec<String>, // TODO: BTreeSet
}
-impl fmt::Display for AuthError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "AuthError: {}", &self.msg)
- }
-}
+impl AuthContext {
-impl iron::Error for AuthError {
- fn description(&self) -> &str {
- &self.msg
- }
- fn cause(&self) -> Option<&iron::Error> {
- None
+ pub fn has_role(&self, role: &str) -> bool {
+ self.roles.contains(&role.to_string()) || self.roles.contains(&"admin".to_string())
}
}
-/*
-#[derive(Debug)]
-pub struct FatcatAuthBakery {
- root_key_store: bool, // hashmap
- signing_key: bool, // string name
+#[derive(Clone)]
+pub struct AuthConfectionary {
+ pub root_keys: HashMap<String, [u8; 32]>,
}
-// impl:
-// - new()
-// - verify(&str) -> Result<AuthContext>
-*/
-
-fn new_auth_ironerror(m: &str) -> iron::error::IronError {
- iron::error::IronError::new(
- AuthError { msg: m.to_string() },
- (iron::status::BadRequest, m.to_string())
- )
-}
-
-impl OpenAuthMiddleware {
- /// Create a middleware that authorizes with the configured subject.
- pub fn new() -> OpenAuthMiddleware {
- OpenAuthMiddleware
- }
-}
-impl iron::middleware::BeforeMiddleware for OpenAuthMiddleware {
- fn before(&self, req: &mut iron::Request) -> iron::IronResult<()> {
- req.extensions.insert::<Authorization>(Authorization {
- subject: "undefined".to_string(),
- scopes: Scopes::All,
- issuer: None,
- });
- Ok(())
+impl AuthConfectionary {
+ pub fn new() -> AuthConfectionary {
+ AuthConfectionary {
+ root_keys: HashMap::new(),
+ }
}
-}
-#[derive(Debug)]
-pub struct MacaroonAuthMiddleware;
-
-impl MacaroonAuthMiddleware {
+ pub fn create_token(&self, conn: &DbConn, editor_id: FatCatId, expires: Option<DateTime<Utc>>) -> Result<String> {
+ let _ed: EditorRow = editor::table
+ .find(&editor_id.to_uuid())
+ .get_result(conn)?;
+ let mut mac = Macaroon::create("fatcat.wiki", DUMMY_KEY, "dummy-key").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)));
+ };
+ let raw = mac.serialize(Format::V2).expect("macaroon serialization");
+ Ok(BASE64.encode(&raw))
+ }
- pub fn new() -> MacaroonAuthMiddleware {
- macaroon::initialize().unwrap();
- MacaroonAuthMiddleware
+ /// On success, returns Some((editor_id, scopes)), where `scopes` is a vector of strings.
+ pub fn parse_macaroon_token(&self, conn: &DbConn, s: &str) -> Result<EditorRow> {
+ let raw = BASE64.decode(s.as_bytes())?;
+ let mac = match Macaroon::deserialize(&raw) {
+ Ok(m) => m,
+ Err(e) => bail!("macaroon deserialize error: {:?}", e),
+ };
+ let mac = match mac.validate() {
+ Ok(m) => m,
+ Err(e) => bail!("macaroon validate error: {:?}", e),
+ };
+ 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
+ }
+ }
+ let editor_id = editor_id.expect("expected an editor_id caveat");
+ verifier.satisfy_exact(&format!("editor_id = {}", editor_id.to_string()));
+ 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
+ }
+ }
+ 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)?;
+ 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)")
+ }
+ verifier.satisfy_general(|p: &str| -> bool {
+ // not expired (based on expires)
+ if p.starts_with("expires = ") {
+ let expires: DateTime<Utc> = DateTime::parse_from_rfc3339(p.get(12..).unwrap())
+ .unwrap()
+ .with_timezone(&Utc);
+ expires < Utc::now()
+ } else {
+ false
+ }
+ });
+ if !mac.verify_signature(DUMMY_KEY) {
+ bail!("token signature verification failed");
+ };
+ match mac.verify(DUMMY_KEY, &mut verifier) {
+ Ok(true) => (),
+ Ok(false) => bail!("token overall verification failed"),
+ Err(e) => bail!("token parsing failed: {:?}", e),
+ }
+ Ok(editor)
}
-}
-impl iron::middleware::BeforeMiddleware for MacaroonAuthMiddleware {
- fn before(&self, req: &mut iron::Request) -> iron::IronResult<()> {
+ pub fn parse_swagger(&self, conn: &DbConn, auth_data: &Option<AuthData>) -> Result<Option<AuthContext>> {
- let res: Option<(String, Vec<String>)> = match req.extensions.get::<AuthData>() {
+ let token: Option<String> = match auth_data {
Some(AuthData::ApiKey(header)) => {
let header: Vec<String> = header.split_whitespace().map(|s| s.to_string()).collect();
if !(header.len() == 2 && header[0] == "Bearer") {
- return Err(new_auth_ironerror("invalid bearer auth HTTP Header"));
+ bail!("invalid Bearer Auth HTTP header");
}
- sniff_macaroon_token(&header[1]).expect("valid macaroon")
+ Some(header[1].clone())
},
None => None,
- _ => {
- return Err(new_auth_ironerror("auth HTTP Header should be empty or API key"));
- }
+ _ => bail!("Authentication HTTP Header should either be empty or a Beaerer API key"),
};
- if let Some((editor_id, scopes)) = res {
- let mut scope_set = BTreeSet::new();
- for s in scopes {
- scope_set.insert(s);
- }
- req.extensions.insert::<Authorization>(Authorization {
- subject: editor_id,
- scopes: Scopes::Some(scope_set),
- issuer: None,
- });
+ let token = match token {
+ Some(t) => t,
+ None => return Ok(None),
};
- Ok(())
- }
-}
-
-/// Just checks signature and expired time; can't hit database, so nothing else
-pub fn sniff_macaroon_token(s: &str) -> Result<Option<(String,Vec<String>)>> {
- let raw = BASE64.decode(s.as_bytes())?;
- let mac = match Macaroon::deserialize(&raw) {
- Ok(m) => m,
- Err(e) => bail!("macaroon deserialize error: {:?}", e),
- };
- let mac = match mac.validate() {
- Ok(m) => m,
- Err(e) => bail!("macaroon validate error: {:?}", e),
- };
- 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
- }
- }
- let editor_id = editor_id.expect("expected an editor_id caveat");
- Ok(Some((editor_id.to_string(), vec![])))
-}
-
-/// On success, returns Some((editor_id, scopes)), where `scopes` is a vector of strings.
-pub fn parse_macaroon_token(conn: &DbConn, s: &str) -> Result<Option<(String,Vec<String>)>> {
- let raw = BASE64.decode(s.as_bytes())?;
- let mac = match Macaroon::deserialize(&raw) {
- Ok(m) => m,
- Err(e) => bail!("macaroon deserialize error: {:?}", e),
- };
- let mac = match mac.validate() {
- Ok(m) => m,
- Err(e) => bail!("macaroon validate error: {:?}", e),
- };
- 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
- }
- }
- let editor_id = editor_id.expect("expected an editor_id caveat");
- verifier.satisfy_exact(&format!("editor_id = {}", editor_id.to_string()));
- 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
- }
- }
- 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)?;
- 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)")
+ 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,
+ }))
}
- verifier.satisfy_general(|p: &str| -> bool {
- // not expired (based on expires)
- if p.starts_with("expires = ") {
- let expires: DateTime<Utc> = DateTime::parse_from_rfc3339(p.get(12..).unwrap())
- .unwrap()
- .with_timezone(&Utc);
- expires < Utc::now()
- } else {
- false
- }
- });
- if !mac.verify_signature(DUMMY_KEY) {
- bail!("token signature verification failed");
- };
- match mac.verify(DUMMY_KEY, &mut verifier) {
- Ok(true) => (),
- Ok(false) => bail!("token overall verification failed"),
- Err(e) => bail!("token parsing failed: {:?}", e),
- }
- Ok(Some((editor_id.to_string(), vec![])))
}
pub fn print_editors(conn: &DbConn) -> Result<()>{
@@ -236,23 +179,6 @@ pub fn create_editor(conn: &DbConn, username: String, is_admin: bool, is_bot: bo
Ok(ed)
}
-pub fn create_token(conn: &DbConn, editor_id: FatCatId, expires: Option<DateTime<Utc>>) -> Result<String> {
- let _ed: EditorRow = editor::table
- .find(&editor_id.to_uuid())
- .get_result(conn)?;
- let mut mac = Macaroon::create("fatcat.wiki", DUMMY_KEY, "dummy-key").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)));
- };
- let raw = mac.serialize(Format::V2).expect("macaroon serialization");
- Ok(BASE64.encode(&raw))
-}
-
pub fn inspect_token(conn: &DbConn, token: &str) -> Result<()> {
let raw = BASE64.decode(token.as_bytes())?;
let mac = match Macaroon::deserialize(&raw) {
@@ -266,8 +192,9 @@ pub fn inspect_token(conn: &DbConn, token: &str) -> Result<()> {
for caveat in mac.first_party_caveats() {
println!("caveat: {}", caveat.predicate());
}
+ let ac = AuthConfectionary::new();
// TODO: don't display full stacktrace on failure
- println!("verify: {:?}", parse_macaroon_token(conn, token));
+ println!("verify: {:?}", ac.parse_macaroon_token(conn, token));
Ok(())
}