diff --git a/libs/state/src/database.rs b/libs/state/src/database.rs index d7351c3..2f7079b 100644 --- a/libs/state/src/database.rs +++ b/libs/state/src/database.rs @@ -7,13 +7,14 @@ use sqlx::{ sqlite::{Sqlite, SqliteConnectOptions, SqliteJournalMode, SqlitePool}, QueryBuilder, }; -use utils::Group; -use utils::Peer; use std::env; use std::path::Path; +use utils::guid_into_uuid; use utils::types::AddressBook; use utils::AbPeer; use utils::AbTag; +use utils::Group; +use utils::Peer; use utils::UpdateUserRequest; use utils::UserListResponse; @@ -102,7 +103,10 @@ impl Database { pub async fn find_user_by_name( &self, username: &str, - ) -> (DatabaseConnection, Option<(UserId, Option, DatabaseUserInfo)>) { + ) -> ( + DatabaseConnection, + Option<(UserId, Option, DatabaseUserInfo)>, + ) { let mut conn = DatabaseConnection { conn: self.pool.acquire().await.unwrap(), }; @@ -1146,19 +1150,23 @@ impl Database { let mut conn = self.pool.acquire().await.unwrap(); let mut query = "UPDATE user SET ".to_string(); let mut query_params = Vec::new(); - if user_parameters.name.is_some() { + if user_parameters.name.is_some() && user_parameters.name.clone().unwrap().len() > 0 { query.push_str("name = ?, "); query_params.push(user_parameters.name.unwrap()); } - if user_parameters.email.is_some() { + if user_parameters.email.is_some() && user_parameters.email.clone().unwrap().len() > 0 { query.push_str("email = ?, "); query_params.push(user_parameters.email.unwrap()); } - if user_parameters.note.is_some() { + if user_parameters.note.is_some() && user_parameters.note.clone().unwrap().len() > 0 { query.push_str("note = ?, "); query_params.push(user_parameters.note.unwrap()); } - if user_parameters.password.is_some() && user_parameters.confirm_password.is_some() { + if user_parameters.password.is_some() + && user_parameters.confirm_password.is_some() + && user_parameters.password.clone().unwrap().len() > 0 + && user_parameters.confirm_password.clone().unwrap().len() > 0 + { let password = user_parameters.password.unwrap(); let confirm_password = user_parameters.confirm_password.unwrap(); if password == confirm_password { @@ -1173,7 +1181,7 @@ impl Database { } if let Some(is_admin) = user_parameters.is_admin { query.push_str("role = ?, "); - query_params.push(is_admin.to_string()); + query_params.push(if is_admin { "1" } else { "0" }.to_string()); } query.pop(); query.pop(); @@ -1185,12 +1193,15 @@ impl Database { for param in query_params { res = res.bind(param); } - let _res = res.bind(user_id).execute(&mut conn).await; - + let res = res.bind(user_id).execute(&mut conn).await; + if res.is_err() { + log::error!("user_update error: {:?}", res); + return None; + } Some(()) } - pub async fn get_all_peers(&self) -> Option> { + pub async fn get_all_peers(&self) -> Option> { let mut conn = self.pool.acquire().await.unwrap(); let res = sqlx::query!( r#" @@ -1209,7 +1220,7 @@ impl Database { .await .ok()?; - let mut peers:Vec = Vec::new(); + let mut peers: Vec = Vec::new(); for row in res { let uuid = guid_into_uuid(row.guid).unwrap_or("".to_string()); let peer_info = serde_json::from_str::(&row.info); @@ -1218,7 +1229,7 @@ impl Database { return None; } let peer_info = peer_info.unwrap(); - peers.push(Peer{ + peers.push(Peer { id: row.id, guid: uuid, info: peer_info, @@ -1230,7 +1241,7 @@ impl Database { Some(peers) } - pub async fn get_groups(&self, offset: u32, page_size: u32)->Option>{ + pub async fn get_groups(&self, offset: u32, page_size: u32) -> Option> { let mut conn = self.pool.acquire().await.unwrap(); let res = sqlx::query!( r#" @@ -1252,11 +1263,11 @@ impl Database { .fetch_all(&mut conn) .await .ok()?; - let mut groups:Vec = Vec::new(); + let mut groups: Vec = Vec::new(); for row in res { let guid = guid_into_uuid(row.guid).unwrap_or("".to_string()); - let team = guid_into_uuid(row.team).unwrap_or("".to_string()); - groups.push(Group{ + let team = guid_into_uuid(row.team).unwrap_or("".to_string()); + groups.push(Group { guid: guid, name: row.name, team: team, @@ -1264,21 +1275,9 @@ impl Database { created_at: row.created_at.into(), access_to: Vec::::new(), accessed_from: Vec::::new(), - info: row.info + info: row.info, }); } Some(groups) - - } -} - -fn guid_into_uuid(guid: Vec) -> Option { - let guid_u8: Result<[u8; 16], _> = guid.try_into(); - if guid_u8.is_err() { - log::error!("get_ab_personal_guid error: {:?}", guid_u8); - return None; } - let guid_u8: [u8; 16] = guid_u8.unwrap(); - let guid = Uuid::from_bytes(guid_u8).to_string(); - Some(guid) } diff --git a/libs/state/src/state.rs b/libs/state/src/state.rs index 96ae827..0eaaccf 100644 --- a/libs/state/src/state.rs +++ b/libs/state/src/state.rs @@ -16,7 +16,7 @@ use oauth2::ProviderConfig; use tokio::sync::RwLock; use utils::{ - AbPeer, AbTag, AddUserRequest, AddressBook, Group, OidcState, Peer, PeerInfo, Token, UpdateUserRequest, UserListResponse + AbPeer, AbTag, AddUserRequest, AddressBook, Group, OidcState, Peer, Token, UpdateUserRequest, UserListResponse }; pub struct ApiState { @@ -169,6 +169,7 @@ impl ApiState { utils::UserInfo { name: username.to_string(), email, + admin: db_user_info.admin, ..Default::default() }, access_token, @@ -319,6 +320,11 @@ impl ApiState { state_users.get(&user.user_id).map(|ui| ui.username.clone()) } + pub async fn is_current_user_admin(&self, user: &AuthenticatedUserInfo) -> Option { + let state_users = self.users.read().await; + state_users.get(&user.user_id).map(|ui| ui.admin) + } + pub async fn with_user_info( &self, user_id: &UserId, diff --git a/libs/utils/src/lib.rs b/libs/utils/src/lib.rs index c446f3f..dc52c90 100644 --- a/libs/utils/src/lib.rs +++ b/libs/utils/src/lib.rs @@ -9,6 +9,7 @@ pub use tokens::Token; pub use bearer::{BearerAuthToken, CookieAuthToken, MixedAuthToken, IntoToken}; // pub use address_book::AddressBook; pub use types::*; +use uuid::Uuid; #[macro_export] macro_rules! unwrap_or_return { @@ -46,3 +47,25 @@ macro_rules! include_png_as_base64 { } }; } + +pub fn guid_into_uuid(guid: Vec) -> Option { + let guid_u8: Result<[u8; 16], _> = guid.try_into(); + if guid_u8.is_err() { + log::error!("get_ab_personal_guid error: {:?}", guid_u8); + return None; + } + let guid_u8: [u8; 16] = guid_u8.unwrap(); + let guid = Uuid::from_bytes(guid_u8).to_string(); + Some(guid) +} + +pub fn uuid_into_guid(uuid: &str) -> Option> { + let uuid = Uuid::parse_str(uuid); + if uuid.is_err() { + log::error!("uuid_into_guid error: {:?}", uuid); + return None; + } + let uuid = uuid.unwrap(); + let guid = uuid.as_bytes().to_vec(); + Some(guid) +} \ No newline at end of file diff --git a/libs/utils/src/types.rs b/libs/utils/src/types.rs index 91d69ef..ec911da 100644 --- a/libs/utils/src/types.rs +++ b/libs/utils/src/types.rs @@ -113,6 +113,7 @@ pub struct UserInfo { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub email: Option, + pub admin: bool } #[derive(Serialize, Debug, JsonSchema)] @@ -400,6 +401,7 @@ pub struct User { #[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct UpdateUserRequest { + pub uuid: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -420,6 +422,7 @@ pub struct UpdateUserRequest { impl Default for UpdateUserRequest { fn default() -> Self { UpdateUserRequest { + uuid: uuid::Uuid::new_v4().to_string(), name: None, password: None, confirm_password: None, diff --git a/src/lib.rs b/src/lib.rs index 3a81077..bb83639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,9 +36,7 @@ use rocket::{ }; use state::{ApiState, UserPasswordInfo}; use utils::{ - include_png_as_base64, unwrap_or_return, AbTagRenameRequest, AddUserRequest, AddressBook, - EnableUserRequest, Group, GroupsResponse, OidcSettingsResponse, PeersResponse, - SoftwareResponse, SoftwareVersionResponse, UpdateUserRequest, UserList, + include_png_as_base64, unwrap_or_return, uuid_into_guid, AbTagRenameRequest, AddUserRequest, AddressBook, EnableUserRequest, Group, GroupsResponse, OidcSettingsResponse, PeersResponse, SoftwareResponse, SoftwareVersionResponse, UpdateUserRequest, UserList }; use utils::{ AbGetResponse, AbRequest, AuditRequest, CurrentUserRequest, CurrentUserResponse, @@ -397,6 +395,11 @@ async fn users( log::debug!("users"); state.check_maintenance().await; + let email = if email.is_some() && email.unwrap().is_empty() { + None + } else { + email + }; let res = state.get_all_users(name, email, current, pageSize).await; if res.is_none() { return Err(status::NotFound::<()>(())); @@ -1001,19 +1004,30 @@ async fn user_enable( #[openapi(tag = "user")] #[put("/api/user", format = "application/json", data = "")] async fn user_update( - state: &State, + state: &State, user: AuthenticatedUser, request: Json, ) -> Result, status::Unauthorized<()>> { log::debug!("update_user"); state.check_maintenance().await; + let mut guid = uuid_into_guid(request.0.uuid.as_str()); + if guid.is_none() { + guid = Some(user.info.user_id.clone()); + } + + let guid = guid.unwrap(); + let is_admin = state.is_current_user_admin(&user.info).await.unwrap_or(false); + + if !is_admin && user.info.user_id != guid { + return Err(status::Unauthorized::<()>(())); + } let response = UsersResponse { msg: "success".to_string(), total: 1, data: "[{}]".to_string(), }; let user_update = request.0; - state.user_update(user.info.user_id, user_update).await; + state.user_update(guid, user_update).await; Ok(Json(response)) } diff --git a/swagger-codegen.sh b/swagger-codegen.sh index a106961..bb8f684 100755 --- a/swagger-codegen.sh +++ b/swagger-codegen.sh @@ -7,6 +7,7 @@ if [ "$response" -eq 200 ]; then curl http://127.0.0.1:21114/openapi.json >webconsole/src/api/openapi.json && docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli-v3 generate -i file:///local/webconsole/src/api/openapi.json --additional-properties modelPropertyNaming=snake_case -l typescript-axios -o /local/webconsole/src/api && sed -ibak -e 's/confirm_password/\"confirm-password\"/' webconsole/src/api/models/add-user-request.ts && + sed -ibak -e 's/confirm_password/\"confirm-password\"/' webconsole/src/api/models/update-user-request.ts && sed -ibak -e 's/device_info/\"deviceInfo\"/' webconsole/src/api/models/oidc-auth-request.ts && rm webconsole/src/api/models/*tsbak else diff --git a/webconsole/src/api/models/current-user-response.ts b/webconsole/src/api/models/current-user-response.ts index 2705c76..dbbe3b9 100644 --- a/webconsole/src/api/models/current-user-response.ts +++ b/webconsole/src/api/models/current-user-response.ts @@ -37,4 +37,10 @@ export interface CurrentUserResponse { * @memberof CurrentUserResponse */ email?: string | null; + + /** + * @type {boolean} + * @memberof CurrentUserResponse + */ + admin: boolean; } diff --git a/webconsole/src/api/models/update-user-request.ts b/webconsole/src/api/models/update-user-request.ts index d36f867..48022f6 100644 --- a/webconsole/src/api/models/update-user-request.ts +++ b/webconsole/src/api/models/update-user-request.ts @@ -20,6 +20,12 @@ */ export interface UpdateUserRequest { + /** + * @type {string} + * @memberof UpdateUserRequest + */ + uuid: string; + /** * @type {string} * @memberof UpdateUserRequest @@ -36,7 +42,7 @@ export interface UpdateUserRequest { * @type {string} * @memberof UpdateUserRequest */ - confirm_password?: string | null; + "confirm-password"?: string | null; /** * @type {string} diff --git a/webconsole/src/api/models/user-info.ts b/webconsole/src/api/models/user-info.ts index 3744641..fb1e375 100644 --- a/webconsole/src/api/models/user-info.ts +++ b/webconsole/src/api/models/user-info.ts @@ -31,4 +31,10 @@ export interface UserInfo { * @memberof UserInfo */ email?: string | null; + + /** + * @type {boolean} + * @memberof UserInfo + */ + admin: boolean; } diff --git a/webconsole/src/api/openapi.json b/webconsole/src/api/openapi.json index 09bc48d..3467541 100644 --- a/webconsole/src/api/openapi.json +++ b/webconsole/src/api/openapi.json @@ -1542,6 +1542,7 @@ "UserInfo": { "type": "object", "required": [ + "admin", "name" ], "properties": { @@ -1551,6 +1552,9 @@ "email": { "type": "string", "nullable": true + }, + "admin": { + "type": "boolean" } } }, @@ -1620,6 +1624,7 @@ "CurrentUserResponse": { "type": "object", "required": [ + "admin", "error", "name" ], @@ -1633,6 +1638,9 @@ "email": { "type": "string", "nullable": true + }, + "admin": { + "type": "boolean" } } }, @@ -1958,7 +1966,13 @@ }, "UpdateUserRequest": { "type": "object", + "required": [ + "uuid" + ], "properties": { + "uuid": { + "type": "string" + }, "name": { "type": "string", "nullable": true diff --git a/webconsole/src/components/AddressBook.vue b/webconsole/src/components/AddressBook.vue new file mode 100644 index 0000000..0a2b86a --- /dev/null +++ b/webconsole/src/components/AddressBook.vue @@ -0,0 +1,40 @@ + + \ No newline at end of file diff --git a/webconsole/src/components/EditUser.vue b/webconsole/src/components/EditUser.vue new file mode 100644 index 0000000..6108a24 --- /dev/null +++ b/webconsole/src/components/EditUser.vue @@ -0,0 +1,201 @@ + + \ No newline at end of file diff --git a/webconsole/src/components/UsersCard.vue b/webconsole/src/components/UsersCard.vue index 3d5a590..ee3023a 100644 --- a/webconsole/src/components/UsersCard.vue +++ b/webconsole/src/components/UsersCard.vue @@ -93,7 +93,7 @@ - Edit @@ -107,7 +107,9 @@ - + +