diff options
Diffstat (limited to 'rust')
| -rw-r--r-- | rust/migrations/2018-05-12-001226_init/up.sql | 15 | ||||
| -rw-r--r-- | rust/src/api_wrappers.rs | 20 | ||||
| -rw-r--r-- | rust/src/auth.rs | 32 | ||||
| -rw-r--r-- | rust/src/database_models.rs | 1 | ||||
| -rw-r--r-- | rust/src/database_schema.rs | 1 | ||||
| -rw-r--r-- | rust/tests/test_auth.rs | 6 | 
6 files changed, 44 insertions, 31 deletions
| diff --git a/rust/migrations/2018-05-12-001226_init/up.sql b/rust/migrations/2018-05-12-001226_init/up.sql index 53762f40..b5b39f6f 100644 --- a/rust/migrations/2018-05-12-001226_init/up.sql +++ b/rust/migrations/2018-05-12-001226_init/up.sql @@ -17,6 +17,7 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";  CREATE TABLE editor (      id                  UUID PRIMARY KEY DEFAULT uuid_generate_v4(),      username            TEXT NOT NULL CHECK (username ~* '^[A-Za-z0-9][A-Za-z0-9._-]{2,15}$'), -- UNIQ below +    is_superuser        BOOLEAN NOT NULL DEFAULT false,      is_admin            BOOLEAN NOT NULL DEFAULT false,      is_bot              BOOLEAN NOT NULL DEFAULT false,      is_active           BOOLEAN NOT NULL DEFAULT true, @@ -470,10 +471,13 @@ CREATE INDEX webcapture_rev_release_target_release_idx ON webcapture_rev_release  BEGIN; -INSERT INTO editor (id, username, is_admin, auth_epoch) VALUES -    ('00000000-0000-0000-AAAA-000000000001', 'admin', true, '1970-01-01T01:01:01Z'),        -- aaaaaaaaaaaabkvkaaaaaaaaae -    ('00000000-0000-0000-AAAA-000000000002', 'demo-user', true, '1970-01-01T01:01:01Z'),    -- aaaaaaaaaaaabkvkaaaaaaaaai -    ('00000000-0000-0000-AAAA-000000000003', 'claire', false, default);                     -- aaaaaaaaaaaabkvkaaaaaaaaam +INSERT INTO editor (id, username, is_superuser, is_admin, is_bot, auth_epoch) VALUES +    ('00000000-0000-0000-AAAA-000000000001', 'root', true, true, false, '1970-01-01T01:01:01Z'),          -- aaaaaaaaaaaabkvkaaaaaaaaae +    ('00000000-0000-0000-AAAA-000000000002', 'admin', true, true, false, '1970-01-01T01:01:01Z'),         -- aaaaaaaaaaaabkvkaaaaaaaaai +    ('00000000-0000-0000-AAAA-000000000003', 'demo-user', false, true, false, '1970-01-01T01:01:01Z'),    -- aaaaaaaaaaaabkvkaaaaaaaaam +    ('00000000-0000-0000-AAAA-000000000004', 'claire', false, false, false, default),                     -- aaaaaaaaaaaabkvkaaaaaaaaaq +    ('00000000-0000-0000-AAAA-000000000005', 'webface-bot', true, true, true, '1970-01-01T01:01:01Z'),    -- aaaaaaaaaaaabkvkaaaaaaaaau +    ('00000000-0000-0000-AAAA-000000000006', 'bnewbold', false, true, false, '1970-01-01T01:01:01Z');     -- aaaaaaaaaaaabkvkaaaaaaaaay  INSERT INTO editgroup (id, editor_id, description) VALUES      ('00000000-0000-0000-BBBB-000000000001', '00000000-0000-0000-AAAA-000000000001', 'first edit ever!'),       -- aaaaaaaaaaaabo53aaaaaaaaae @@ -483,9 +487,6 @@ INSERT INTO editgroup (id, editor_id, description) VALUES      ('00000000-0000-0000-BBBB-000000000005', '00000000-0000-0000-AAAA-000000000001', 'journal edit'),           -- aaaaaaaaaaaabo53aaaaaaaaau      ('00000000-0000-0000-BBBB-000000000006', '00000000-0000-0000-AAAA-000000000001', 'another journal edit');   -- aaaaaaaaaaaabo53aaaaaaaaay -INSERT INTO editor (id, username, is_admin, active_editgroup_id) VALUES -    ('00000000-0000-0000-AAAA-000000000004', 'bnewbold', true, '00000000-0000-0000-BBBB-000000000004'); -  INSERT INTO changelog (editgroup_id) VALUES      ('00000000-0000-0000-BBBB-000000000001'),      ('00000000-0000-0000-BBBB-000000000002'), diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs index c663c11d..614a0007 100644 --- a/rust/src/api_wrappers.rs +++ b/rust/src/api_wrappers.rs @@ -85,7 +85,7 @@ 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.require_auth(&conn, &context.auth_data)?; +                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 {                      let eg_id = FatCatId::from_str(&s)?; @@ -137,7 +137,7 @@ macro_rules! wrap_entity_handlers {          ) -> 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)?; +                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 {                      let eg_id = FatCatId::from_str(&s)?; @@ -187,7 +187,7 @@ macro_rules! wrap_entity_handlers {          ) -> 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)?; +                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 { @@ -243,7 +243,7 @@ macro_rules! wrap_entity_handlers {          ) -> 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)?; +                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 { @@ -397,7 +397,7 @@ macro_rules! wrap_entity_handlers {              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)?; +                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))?; @@ -920,7 +920,7 @@ impl Api for Server {              }              let auth_context = self                  .auth_confectionary -                .require_auth(&conn, &context.auth_data)?; +                .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 { @@ -988,7 +988,7 @@ impl Api for Server {              let editgroup_id = FatCatId::from_str(&editgroup_id)?;              let auth_context = self                  .auth_confectionary -                .require_auth(&conn, &context.auth_data)?; +                .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)?; @@ -1060,7 +1060,7 @@ impl Api for Server {          let ret = match conn.transaction(|| {              let auth_context = self                  .auth_confectionary -                .require_auth(&conn, &context.auth_data)?; +                .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() { @@ -1151,8 +1151,8 @@ impl Api for Server {          let ret = match conn.transaction(|| {              let auth_context = self                  .auth_confectionary -                .require_auth(&conn, &context.auth_data)?; -            auth_context.require_role(FatcatRole::Admin)?; +                .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; leave it to webface to attenuate to a given duration              let token = self.auth_confectionary.create_token( diff --git a/rust/src/auth.rs b/rust/src/auth.rs index 4b608a96..0160d2e8 100644 --- a/rust/src/auth.rs +++ b/rust/src/auth.rs @@ -18,13 +18,14 @@ use std::str::FromStr;  // 32 bytes max (!)  static DUMMY_KEY: &[u8] = b"dummy-key-a-one-two-three-a-la"; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)]  pub enum FatcatRole {      Public,      Editor,      Bot,      Human,      Admin, +    Superuser,  }  #[derive(Clone)] @@ -35,6 +36,10 @@ pub struct AuthContext {  impl AuthContext {      pub fn has_role(&self, role: FatcatRole) -> bool { +        if !self.editor_row.is_active { +            // if account is disabled, only allow public role +            return role == FatcatRole::Public; +        }          if self.editor_row.is_admin {              return true;          } @@ -44,15 +49,15 @@ impl AuthContext {              FatcatRole::Bot => self.editor_row.is_bot,              FatcatRole::Human => !self.editor_row.is_bot,              FatcatRole::Admin => self.editor_row.is_admin, +            FatcatRole::Superuser => self.editor_row.is_superuser,          }      }      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(), +                format!("doesn't have required role: {:?}", role),              )              .into()),          } @@ -225,8 +230,7 @@ impl AuthConfectionary {          Ok(BASE64.encode(&raw))      } -    /// 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> { +    pub fn parse_macaroon_token(&self, conn: &DbConn, s: &str, endpoint: Option<&str>) -> Result<EditorRow> {          let raw = BASE64.decode(s.as_bytes())?;          let mac = match Macaroon::deserialize(&raw) {              Ok(m) => m, @@ -258,6 +262,10 @@ impl AuthConfectionary {          }          let editor_id = editor_id.expect("expected an editor_id caveat");          verifier.satisfy_exact(&format!("editor_id = {}", editor_id.to_string())); +        if let Some(endpoint) = endpoint { +            // API endpoint +            verifier.satisfy_exact(&format!("endpoint = {}", endpoint)); +        }          let mut created: Option<DateTime<Utc>> = None;          for caveat in mac.first_party_caveats() {              if caveat.predicate().starts_with("created = ") { @@ -329,6 +337,7 @@ impl AuthConfectionary {          &self,          conn: &DbConn,          auth_data: &Option<AuthData>, +        endpoint: Option<&str>,      ) -> Result<Option<AuthContext>> {          let token: Option<String> = match auth_data {              Some(AuthData::ApiKey(header)) => { @@ -355,15 +364,15 @@ impl AuthConfectionary {              Some(t) => t,              None => return Ok(None),          }; -        let editor_row = self.parse_macaroon_token(conn, &token)?; +        let editor_row = self.parse_macaroon_token(conn, &token, endpoint)?;          Ok(Some(AuthContext {              editor_id: FatCatId::from_uuid(&editor_row.id),              editor_row: editor_row,          }))      } -    pub fn require_auth(&self, conn: &DbConn, auth_data: &Option<AuthData>) -> Result<AuthContext> { -        match self.parse_swagger(conn, auth_data)? { +    pub fn require_auth(&self, conn: &DbConn, auth_data: &Option<AuthData>, endpoint: Option<&str>) -> Result<AuthContext> { +        match self.parse_swagger(conn, auth_data, endpoint)? {              Some(auth) => Ok(auth),              None => Err(ErrorKind::InvalidCredentials("no token supplied".to_string()).into()),          } @@ -384,7 +393,7 @@ impl AuthConfectionary {          for caveat in mac.first_party_caveats() {              println!("caveat: {}", caveat.predicate());          } -        println!("verify: {:?}", self.parse_macaroon_token(conn, token)); +        println!("verify: {:?}", self.parse_macaroon_token(conn, token, None));          Ok(())      }  } @@ -416,11 +425,12 @@ pub fn revoke_tokens_everyone(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)?; -    println!("editor_id\t\t\tis_admin/is_bot\tauth_epoch\t\t\tusername\twrangler_id"); +    println!("editor_id\t\t\tsuper/admin/bot\tauth_epoch\t\t\tusername\twrangler_id");      for e in all_editors {          println!( -            "{}\t{}\t{}\t{}\t{}\t{:?}", +            "{}\t{}/{}/{}\t{}\t{}\t{:?}",              FatCatId::from_uuid(&e.id).to_string(), +            e.is_superuser,              e.is_admin,              e.is_bot,              e.auth_epoch, diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 5c8e17d3..59953f6b 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -577,6 +577,7 @@ impl EditgroupRow {  pub struct EditorRow {      pub id: Uuid,      pub username: String, +    pub is_superuser: bool,      pub is_admin: bool,      pub is_bot: bool,      pub is_active: bool, diff --git a/rust/src/database_schema.rs b/rust/src/database_schema.rs index 49863fc7..0c553b40 100644 --- a/rust/src/database_schema.rs +++ b/rust/src/database_schema.rs @@ -107,6 +107,7 @@ table! {      editor (id) {          id -> Uuid,          username -> Text, +        is_superuser -> Bool,          is_admin -> Bool,          is_bot -> Bool,          is_active -> Bool, diff --git a/rust/tests/test_auth.rs b/rust/tests/test_auth.rs index b06f3e7b..5ccbb6cb 100644 --- a/rust/tests/test_auth.rs +++ b/rust/tests/test_auth.rs @@ -35,13 +35,13 @@ fn test_auth_db() {      let token = c.create_token(editor_id, None).unwrap();      // verify token -    let editor_row = c.parse_macaroon_token(&conn, &token).unwrap(); +    let editor_row = c.parse_macaroon_token(&conn, &token, None).unwrap();      assert_eq!(editor_row.id, editor_id.to_uuid());      // revoke token      revoke_tokens(&conn, editor_id).unwrap();      // verification should fail -    // XXX: one-second slop breads this -    //assert!(c.parse_macaroon_token(&conn, &token).is_err()); +    // XXX: one-second slop breaks this +    //assert!(c.parse_macaroon_token(&conn, &token, None).is_err());  } | 
