aboutsummaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
Diffstat (limited to 'rust')
-rw-r--r--rust/src/api_helpers.rs49
-rw-r--r--rust/src/api_wrappers.rs82
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))
}) {