aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2019-01-04 19:24:21 -0800
committerBryan Newbold <bnewbold@robocracy.org>2019-01-04 19:24:21 -0800
commiteccdd4577a54b230460de6733ed7b003b6f8f182 (patch)
treebd29d08b715de4b1a790f80e3c08f335675dbf79
parent6eeead67f1d9af4ff2fc3c6c1188bc372e7d05a0 (diff)
downloadfatcat-eccdd4577a54b230460de6733ed7b003b6f8f182.tar.gz
fatcat-eccdd4577a54b230460de6733ed7b003b6f8f182.zip
add superuser role/flag
-rw-r--r--rust/migrations/2018-05-12-001226_init/up.sql15
-rw-r--r--rust/src/api_wrappers.rs20
-rw-r--r--rust/src/auth.rs32
-rw-r--r--rust/src/database_models.rs1
-rw-r--r--rust/src/database_schema.rs1
-rw-r--r--rust/tests/test_auth.rs6
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());
}