From eccdd4577a54b230460de6733ed7b003b6f8f182 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 4 Jan 2019 19:24:21 -0800 Subject: add superuser role/flag --- rust/migrations/2018-05-12-001226_init/up.sql | 15 +++++++------ rust/src/api_wrappers.rs | 20 ++++++++--------- rust/src/auth.rs | 32 ++++++++++++++++++--------- rust/src/database_models.rs | 1 + rust/src/database_schema.rs | 1 + rust/tests/test_auth.rs | 6 ++--- 6 files changed, 44 insertions(+), 31 deletions(-) (limited to 'rust') 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 + 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 + 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 + 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 + 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 = 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 { + pub fn parse_macaroon_token(&self, conn: &DbConn, s: &str, endpoint: Option<&str>) -> Result { 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> = 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, + endpoint: Option<&str>, ) -> Result> { let token: Option = 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) -> Result { - match self.parse_swagger(conn, auth_data)? { + pub fn require_auth(&self, conn: &DbConn, auth_data: &Option, endpoint: Option<&str>) -> Result { + 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 = 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()); } -- cgit v1.2.3