diff options
-rw-r--r-- | rust/src/api_helpers.rs | 49 | ||||
-rw-r--r-- | rust/src/api_wrappers.rs | 82 |
2 files changed, 127 insertions, 4 deletions
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs index 7478da9d..79114d4f 100644 --- a/rust/src/api_helpers.rs +++ b/rust/src/api_helpers.rs @@ -228,13 +228,13 @@ 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> { + check_username(&username)?; let ed: EditorRow = diesel::insert_into(editor::table) .values(( editor::username.eq(username), @@ -245,6 +245,19 @@ pub fn create_editor( Ok(ed) } +pub fn update_editor_username( + conn: &DbConn, + editor_id: FatCatId, + username: String, +) -> Result<EditorRow> { + check_username(&username)?; + diesel::update(editor::table.find(editor_id.to_uuid())) + .set(editor::username.eq(username)) + .execute(conn)?; + let editor: EditorRow = editor::table.find(editor_id.to_uuid()).get_result(conn)?; + Ok(editor) +} + /// This function should always be run within a transaction pub fn get_or_create_editgroup(editor_id: Uuid, conn: &DbConn) -> Result<Uuid> { // check for current active @@ -344,6 +357,40 @@ pub fn uuid2fcid(id: &Uuid) -> String { BASE32_NOPAD.encode(raw).to_lowercase() } +pub fn check_username(raw: &str) -> Result<()> { + lazy_static! { + static ref RE: Regex = Regex::new(r"^[A-Za-z0-9][A-Za-z0-9._-]{2,15}$").unwrap(); + } + if RE.is_match(raw) { + Ok(()) + } else { + Err(ErrorKind::MalformedExternalId(format!( + "not a valid username: '{}' (expected, eg, 'AcidBurn')", + raw + )) + .into()) + } +} + +#[test] +fn test_check_username() { + assert!(check_username("bnewbold").is_ok()); + assert!(check_username("BNEWBOLD").is_ok()); + assert!(check_username("admin").is_ok()); + assert!(check_username("friend-bot").is_ok()); + assert!(check_username("dog").is_ok()); + assert!(check_username("g_____").is_ok()); + + assert!(check_username("").is_err()); + assert!(check_username("_").is_err()); + assert!(check_username("gg").is_err()); + assert!(check_username("adminadminadminadminadminadminadmin").is_err()); + assert!(check_username("bryan newbold").is_err()); + assert!(check_username("01234567-3456-6780").is_err()); + assert!(check_username(".admin").is_err()); + assert!(check_username("-bot").is_err()); +} + pub fn check_pmcid(raw: &str) -> Result<()> { lazy_static! { static ref RE: Regex = Regex::new(r"^PMC\d+$").unwrap(); diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs index c6966cee..abaa2310 100644 --- a/rust/src/api_wrappers.rs +++ b/rust/src/api_wrappers.rs @@ -904,6 +904,81 @@ impl Api for Server { Box::new(futures::done(Ok(ret))) } + /// For now, only implements updating username + fn update_editor( + &self, + editor_id: String, + editor: models::Editor, + context: &Context, + ) -> Box<Future<Item = UpdateEditorResponse, Error = ApiError> + Send> { + let conn = self.db_pool.get().expect("db_pool error"); + let ret = match conn.transaction(|| { + if Some(editor_id.clone()) != editor.editor_id { + return Err( + ErrorKind::OtherBadRequest("editor_id doesn't match".to_string()).into(), + ); + } + let auth_context = self + .auth_confectionary + .require_auth(&conn, &context.auth_data)?; + let editor_id = FatCatId::from_str(&editor_id)?; + // DANGER! these permissions are for username updates only! + if editor_id == auth_context.editor_id { + // self edit of username allowed + auth_context.require_role(FatcatRole::Editor)?; + } else { + // admin can update any username + auth_context.require_role(FatcatRole::Admin)?; + }; + update_editor_username(&conn, editor_id, editor.username) + .map(|e| e.into_model()) + }) { + Ok(editor) => UpdateEditorResponse::UpdatedEditor(editor), + Err(Error(ErrorKind::Diesel(e), _)) => { + UpdateEditorResponse::BadRequest(ErrorResponse { + message: e.to_string(), + }) + } + Err(Error(ErrorKind::Uuid(e), _)) => UpdateEditorResponse::BadRequest(ErrorResponse { + message: e.to_string(), + }), + Err(Error(ErrorKind::InvalidFatcatId(e), _)) => { + UpdateEditorResponse::BadRequest(ErrorResponse { + message: ErrorKind::InvalidFatcatId(e).to_string(), + }) + } + Err(Error(ErrorKind::MalformedExternalId(e), _)) => { + UpdateEditorResponse::BadRequest(ErrorResponse { + message: e.to_string(), + }) + } + Err(Error(ErrorKind::InvalidCredentials(e), _)) => + // TODO: why can't I NotAuthorized here? + { + UpdateEditorResponse::Forbidden(ErrorResponse { + message: e.to_string(), + }) + } + Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { + UpdateEditorResponse::Forbidden(ErrorResponse { + message: e.to_string(), + }) + } + Err(Error(ErrorKind::OtherBadRequest(e), _)) => { + UpdateEditorResponse::BadRequest(ErrorResponse { + message: e.to_string(), + }) + } + Err(e) => { + error!("{}", e); + UpdateEditorResponse::GenericError(ErrorResponse { + message: e.to_string(), + }) + } + }; + Box::new(futures::done(Ok(ret))) + } + fn accept_editgroup( &self, editgroup_id: String, @@ -1081,9 +1156,10 @@ impl Api for Server { auth_context.require_role(FatcatRole::Admin)?; 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(FatCatId::from_str(&editor.editor_id.clone().unwrap())?, None)?; + let token = self.auth_confectionary.create_token( + FatCatId::from_str(&editor.editor_id.clone().unwrap())?, + None, + )?; let result = AuthOidcResult { editor, token }; Ok((result, created)) }) { |