Merge pull request #14 from fLotte-meets-HWR-DB/develop

REST methods for user management
leon_tries_rust
Trivernis 4 years ago committed by GitHub
commit 36dae3ad2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,7 +8,7 @@ use r2d2::Pool;
use r2d2_postgres::PostgresConnectionManager;
use crate::database::models::CreatePermissionsEntry;
use crate::database::permissions::{Permissions, DEFAULT_PERMISSIONS};
use crate::database::permissions::{Permissions, USER_MANAGEMENT_PERMISSIONS};
use crate::database::role_permissions::RolePermissions;
use crate::database::roles::Roles;
use crate::database::user_roles::UserRoles;
@ -94,7 +94,7 @@ impl Database {
log::debug!("Failed to create admin role {}", e.to_string())
}
self.permissions.create_permissions(
DEFAULT_PERMISSIONS
USER_MANAGEMENT_PERMISSIONS
.iter()
.map(|(name, description)| CreatePermissionsEntry {
name: name.to_string(),

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
/// Record to store data in when retrieving rows from the users table
#[derive(Clone, Debug, Zeroize)]
#[derive(Clone, Debug, Zeroize, Serialize)]
#[zeroize(drop)]
pub struct UserRecord {
pub id: i32,
@ -55,3 +55,29 @@ pub struct CreatePermissionsEntry {
pub name: String,
pub description: String,
}
/// Information about the user that doesn't contain any critical information
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct UserInformation {
pub id: i32,
pub name: String,
pub email: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct UserFullInformation {
pub id: i32,
pub name: String,
pub email: String,
pub roles: Vec<Role>,
}
impl From<UserRecord> for UserInformation {
fn from(record: UserRecord) -> Self {
Self {
id: record.id.clone(),
name: record.name.clone(),
email: record.email.clone(),
}
}
}

@ -7,15 +7,28 @@ use crate::database::{DatabaseResult, PostgresPool, Table, ADMIN_ROLE_NAME};
use std::collections::HashSet;
use std::iter::FromIterator;
pub(crate) const VIEW_ROLE_PERMISSION: &str = "ROLE_VIEW";
pub(crate) const CREATE_ROLE_PERMISSION: &str = "ROLE_CREATE";
pub(crate) const UPDATE_ROLE_PERMISSION: &str = "ROLE_UPDATE";
pub(crate) const DELETE_ROLE_PERMISSION: &str = "ROLE_DELETE";
pub(crate) const DEFAULT_PERMISSIONS: &[(&'static str, &'static str)] = &[
(CREATE_ROLE_PERMISSION, "Allows the user to create roles"),
(UPDATE_ROLE_PERMISSION, "Allows the user to update roles"),
(DELETE_ROLE_PERMISSION, "Allows the user to delete roles"),
(VIEW_ROLE_PERMISSION, "Allows to see information for roles"),
pub(crate) const ROLE_VIEW_PERM: &str = "ROLE_VIEW";
pub(crate) const ROLE_CREATE_PERM: &str = "ROLE_CREATE";
pub(crate) const ROLE_UPDATE_PERM: &str = "ROLE_UPDATE";
pub(crate) const ROLE_DELETE_PERM: &str = "ROLE_DELETE";
pub(crate) const USER_UPDATE_PERM: &str = "USER_UPDATE";
pub(crate) const USER_VIEW_PERM: &str = "USER_VIEW";
pub(crate) const USER_CREATE_PERM: &str = "USER_CREATE";
pub(crate) const USER_DELETE_PERM: &str = "USER_DELETE";
pub(crate) const USER_MANAGEMENT_PERMISSIONS: &[(&'static str, &'static str)] = &[
(ROLE_CREATE_PERM, "Allows the user to create roles"),
(ROLE_UPDATE_PERM, "Allows the user to update roles"),
(ROLE_DELETE_PERM, "Allows the user to delete roles"),
(ROLE_VIEW_PERM, "Allows to see information of roles"),
(
USER_UPDATE_PERM,
"Allows changing the name, password and email of a user",
),
(USER_VIEW_PERM, "Allows to see information of users"),
(USER_CREATE_PERM, "Allows the creation of new users"),
(USER_DELETE_PERM, "Allows the deletion of users"),
];
/// The permissions table that stores defined

@ -7,7 +7,7 @@ use std::sync::Arc;
use parking_lot::Mutex;
use zeroize::{Zeroize, Zeroizing};
use crate::database::models::UserRecord;
use crate::database::models::{UserInformation, UserRecord};
use crate::database::tokens::{SessionTokens, TokenStore};
use crate::database::user_roles::UserRoles;
use crate::database::{DatabaseResult, PostgresPool, Table};
@ -78,6 +78,105 @@ impl Users {
Ok(UserRecord::from_ordered_row(&row))
}
pub fn update_user(
&self,
old_email: &String,
name: &String,
email: &String,
password: &Option<String>,
) -> DatabaseResult<UserInformation> {
log::trace!(
"Updating user {} with new entries name: {}, email: {}",
old_email,
name,
email
);
let mut connection = self.pool.get()?;
if connection
.query_opt("SELECT email FROM users WHERE email = $1", &[&old_email])?
.is_none()
{
log::trace!("Failed to create user: Record doesn't exist!");
return Err(DBError::RecordDoesNotExist);
}
if old_email != email
&& connection
.query_opt("SELECT email FROM users WHERE email = $1", &[&email])?
.is_some()
{
log::trace!("Failed to create user: New Record exists!");
return Err(DBError::GenericError(format!(
"A user for the email {} already exists!",
email
)));
}
let new_record = if let Some(password) = password {
let salt = Zeroizing::new(create_salt());
let pw_hash =
hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?;
connection.query_one(
"UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *",
&[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &old_email],
)?
} else {
connection.query_one(
"UPDATE users SET name = $1, email = $2 WHERE email = $3 RETURNING *",
&[&name, &email, &old_email],
)?
};
Ok(serde_postgres::from_row::<UserInformation>(&new_record)?)
}
/// Returns information about a user by Id
pub fn get_user(&self, id: i32) -> DatabaseResult<UserInformation> {
log::trace!("Looking up entry for user with id {}", id);
let mut connection = self.pool.get()?;
let result = connection
.query_opt("SELECT id, name, email FROM users WHERE id = $1", &[&id])?
.ok_or(DBError::RecordDoesNotExist)?;
Ok(serde_postgres::from_row::<UserInformation>(&result)?)
}
pub fn get_user_by_email(&self, email: &String) -> DatabaseResult<UserInformation> {
log::trace!("Looking up entry for user with email {}", email);
let mut connection = self.pool.get()?;
let result = connection
.query_opt(
"SELECT id, name, email FROM users WHERE email = $1",
&[email],
)?
.ok_or(DBError::RecordDoesNotExist)?;
Ok(serde_postgres::from_row::<UserInformation>(&result)?)
}
pub fn get_users(&self) -> DatabaseResult<Vec<UserInformation>> {
log::trace!("Returning a list of all users...");
let mut connection = self.pool.get()?;
let results = connection.query("SELECT id, name, email FROM users", &[])?;
let mut users = Vec::new();
for result in results {
users.push(serde_postgres::from_row::<UserInformation>(&result)?);
}
Ok(users)
}
pub fn delete_user(&self, email: &String) -> DatabaseResult<()> {
log::trace!("Deleting user with email {}", email);
let mut connection = self.pool.get()?;
let exists = connection.query_opt("SELECT id FROM users WHERE email = $1", &[email])?;
if exists.is_none() {
return Err(DBError::RecordDoesNotExist);
}
connection.query("DELETE FROM users WHERE email = $1", &[email])?;
Ok(())
}
/// Creates new tokens for a user login that can be used by services
/// that need those tokens to verify a user login
pub fn create_tokens(
@ -85,6 +184,7 @@ impl Users {
email: &String,
password: &String,
) -> DatabaseResult<SessionTokens> {
log::trace!("Creating new tokens for user with email {}", email);
if self.validate_login(&email, password)? {
let mut connection = self.pool.get()?;
let row = connection.query_one("SELECT id FROM users WHERE email = $1", &[&email])?;
@ -170,7 +270,7 @@ impl Users {
/// Validates the login data of the user by creating the hash for the given password
/// and comparing it with the database entry
fn validate_login(&self, email: &String, password: &String) -> DatabaseResult<bool> {
pub fn validate_login(&self, email: &String, password: &String) -> DatabaseResult<bool> {
let mut connection = self.pool.get()?;
let row = connection
.query_opt(

@ -23,7 +23,6 @@ impl RESTDocumentation {
}
pub fn get(&self, path: String) -> String {
log::trace!("Rendering help for {}.", path);
format!(
"<html><head><style type='text/css'>{}</style></head><body>{}</body></html>",
include_str!("style.css"),
@ -47,6 +46,7 @@ impl RESTDocumentation {
method: &str,
description: &str,
) -> Result<(), serde_json::error::Error> {
log::trace!("Prerendering documentation for {}", path);
let input_schema = schema_for!(I);
let output_schema = schema_for!(O);
@ -65,6 +65,8 @@ impl RESTDocumentation {
self.base_path, method, path, description, input_json, output_json
);
self.paths.insert(path.to_string(), content);
log::trace!("Documentation for {} rendered", path);
Ok(())
}
}

@ -11,19 +11,22 @@ use rouille::{Request, Response, Server};
use serde::export::Formatter;
use serde::Serialize;
use crate::database::models::Role;
use crate::database::models::{Role, UserFullInformation, UserInformation};
use crate::database::permissions::{
CREATE_ROLE_PERMISSION, DELETE_ROLE_PERMISSION, UPDATE_ROLE_PERMISSION, VIEW_ROLE_PERMISSION,
ROLE_CREATE_PERM, ROLE_DELETE_PERM, ROLE_UPDATE_PERM, ROLE_VIEW_PERM, USER_CREATE_PERM,
USER_DELETE_PERM, USER_UPDATE_PERM, USER_VIEW_PERM,
};
use crate::database::tokens::SessionTokens;
use crate::database::Database;
use crate::server::documentation::RESTDocumentation;
use crate::server::messages::{
DeleteRoleResponse, ErrorMessage, FullRoleData, LoginMessage, LogoutConfirmation,
LogoutMessage, ModifyRoleRequest, RefreshMessage,
CreateUserRequest, DeleteRoleResponse, DeleteUserRequest, DeleteUserResponse, ErrorMessage,
FullRoleData, LoginMessage, LogoutConfirmation, LogoutMessage, ModifyRoleRequest,
RefreshMessage, UpdateUserRequest,
};
use crate::utils::error::DBError;
use crate::utils::get_user_id_from_token;
use serde::de::DeserializeOwned;
macro_rules! require_permission {
($database:expr,$request:expr,$permission:expr) => {
@ -127,6 +130,21 @@ impl UserHttpServer {
(POST) (/roles/{name: String}/delete) => {
Self::delete_role(&database, request, name).unwrap_or_else(HTTPError::into)
},
(GET) (/users/{email: String}) => {
Self::get_user(&database, request, email).unwrap_or_else(HTTPError::into)
},
(GET) (/users) => {
Self::get_users(&database, request).unwrap_or_else(HTTPError::into)
},
(POST) (/users/create) => {
Self::create_user(&database, request).unwrap_or_else(HTTPError::into)
},
(POST) (/users/{email: String}/update) => {
Self::update_user(&database, request, email).unwrap_or_else(HTTPError::into)
},
(POST) (/users/{email: String}/delete) => {
Self::delete_user(&database, request, email).unwrap_or_else(HTTPError::into)
},
_ => if request.method() == "OPTIONS" {
Response::empty_204()
} else {
@ -196,6 +214,31 @@ impl UserHttpServer {
"POST",
"Deletes a role",
)?;
doc.add_path::<UpdateUserRequest, UserInformation>(
"/users/{email:String}/update",
"POST",
"Change user information",
)?;
doc.add_path::<(), UserFullInformation>(
"/users/{email:String}",
"GET",
"See user information",
)?;
doc.add_path::<(), Vec<UserInformation>>(
"/users",
"GET",
"Returns information for all users",
)?;
doc.add_path::<CreateUserRequest, UserInformation>(
"/users/create",
"POST",
"Creates a new user",
)?;
doc.add_path::<DeleteUserRequest, DeleteUserResponse>(
"/users/{email:String}/delete",
"POST",
"Deletes a user",
)?;
Ok(doc)
}
@ -241,7 +284,7 @@ impl UserHttpServer {
/// Returns the data for a given role
fn get_role(database: &Database, request: &Request, name: String) -> HTTPResult<Response> {
require_permission!(database, request, VIEW_ROLE_PERMISSION);
require_permission!(database, request, ROLE_VIEW_PERM);
let role = database.roles.get_role(name)?;
let permissions = database.role_permission.by_role(role.id)?;
@ -254,14 +297,15 @@ impl UserHttpServer {
/// Returns a list of all roles
fn get_roles(database: &Database, request: &Request) -> HTTPResult<Response> {
require_permission!(database, request, VIEW_ROLE_PERMISSION);
require_permission!(database, request, ROLE_VIEW_PERM);
let roles = database.roles.get_roles()?;
Ok(Response::json(&roles))
}
/// Creates a new role with the given permissions
fn create_role(database: &Database, request: &Request) -> HTTPResult<Response> {
require_permission!(database, request, CREATE_ROLE_PERMISSION);
require_permission!(database, request, ROLE_CREATE_PERM);
let message: ModifyRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))?;
let not_existing = database
@ -288,10 +332,11 @@ impl UserHttpServer {
.with_status_code(201))
}
/// Updates information for a single role
fn update_role(database: &Database, request: &Request, name: String) -> HTTPResult<Response> {
require_permission!(database, request, UPDATE_ROLE_PERMISSION);
let message: ModifyRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))?;
require_permission!(database, request, ROLE_UPDATE_PERM);
let message: ModifyRoleRequest = deserialize_body(&request)?;
let not_existing = database
.permissions
.get_not_existing(&message.permissions)?;
@ -317,8 +362,9 @@ impl UserHttpServer {
}))
}
/// Deletes a role from the database
fn delete_role(database: &Database, request: &Request, role: String) -> HTTPResult<Response> {
require_permission!(database, request, DELETE_ROLE_PERMISSION);
require_permission!(database, request, ROLE_DELETE_PERM);
database.roles.delete_role(&role)?;
Ok(Response::json(&DeleteRoleResponse {
@ -326,6 +372,96 @@ impl UserHttpServer {
role,
}))
}
/// Returns information for a single user
fn get_user(database: &Database, request: &Request, email: String) -> HTTPResult<Response> {
require_permission!(database, request, USER_VIEW_PERM);
let user = database.users.get_user_by_email(&email)?;
let roles = database.user_roles.by_user(user.id)?;
Ok(Response::json(&UserFullInformation {
id: user.id,
name: user.name,
email: user.email,
roles,
}))
}
/// Returns a list of all users
fn get_users(database: &Database, request: &Request) -> HTTPResult<Response> {
require_permission!(database, request, USER_VIEW_PERM);
let users = database.users.get_users()?;
Ok(Response::json(&users))
}
/// Creates a new user
fn create_user(database: &Database, request: &Request) -> HTTPResult<Response> {
require_permission!(database, request, USER_CREATE_PERM);
let message = deserialize_body::<CreateUserRequest>(&request)?;
let result = database.users.create_user(
message.name.clone(),
message.email.clone(),
message.password.clone(),
)?;
Ok(Response::json(&UserInformation::from(result)).with_status_code(201))
}
/// Updates the information of a user. This requires the operating user to revalidate his password
fn update_user(database: &Database, request: &Request, email: String) -> HTTPResult<Response> {
let (_, id) = validate_request_token(request, database)?;
let message = deserialize_body::<UpdateUserRequest>(&request)?;
let logged_in_user = database.users.get_user(id)?;
if !database
.users
.validate_login(&logged_in_user.email, &message.own_password)?
{
return Err(HTTPError::new(
"Invalid authentication data".to_string(),
401,
));
}
if logged_in_user.email != email {
require_permission!(database, request, USER_UPDATE_PERM);
}
let user_record = database.users.get_user_by_email(&email)?;
let record = database.users.update_user(
&email,
&message.name.clone().unwrap_or(user_record.name),
&message.email.clone().unwrap_or(user_record.email),
&message.password,
)?;
Ok(Response::json(&record))
}
/// Deletes a user completely
fn delete_user(database: &Database, request: &Request, email: String) -> HTTPResult<Response> {
let (_, id) = validate_request_token(request, database)?;
let message = deserialize_body::<DeleteUserRequest>(&request)?;
let logged_in_user = database.users.get_user(id)?;
if !database
.users
.validate_login(&logged_in_user.email, &message.own_password)?
{
return Err(HTTPError::new(
"Invalid authentication data".to_string(),
401,
));
}
if !database.users.has_permission(id, USER_DELETE_PERM)? {
return Err(HTTPError::new("Insufficient permissions".to_string(), 403));
}
database.users.delete_user(&email)?;
Ok(Response::json(&DeleteUserResponse {
success: true,
email,
}))
}
}
/// Parses the body of a http request into a string representation
@ -340,6 +476,12 @@ fn parse_string_body(request: &Request) -> HTTPResult<String> {
Ok(string_body)
}
/// Deserialized a json body into the given type
fn deserialize_body<T: DeserializeOwned>(request: &Request) -> HTTPResult<T> {
serde_json::from_str(parse_string_body(request)?.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))
}
/// Parses and validates the request token from the http header
fn validate_request_token(request: &Request, database: &Database) -> HTTPResult<(String, i32)> {
lazy_static::lazy_static! {static ref BEARER_REGEX: Regex = Regex::new(r"^[bB]earer\s+").unwrap();}

@ -119,3 +119,32 @@ pub struct DeleteRoleResponse {
pub success: bool,
pub role: String,
}
#[derive(Deserialize, JsonSchema, Zeroize)]
#[zeroize(drop)]
pub struct UpdateUserRequest {
pub name: Option<String>,
pub email: Option<String>,
pub password: Option<String>,
pub own_password: String,
}
#[derive(Deserialize, JsonSchema, Zeroize)]
#[zeroize(drop)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
pub password: String,
}
#[derive(Deserialize, JsonSchema, Zeroize)]
#[zeroize(drop)]
pub struct DeleteUserRequest {
pub own_password: String,
}
#[derive(Serialize, JsonSchema)]
pub struct DeleteUserResponse {
pub email: String,
pub success: bool,
}

Loading…
Cancel
Save