Add roles/create REST method

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/13/head
trivernis 4 years ago
parent c75d68890f
commit 04a0767e24
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

2
Cargo.lock generated

@ -392,6 +392,7 @@ dependencies = [
"crossbeam-utils",
"dotenv",
"env_logger",
"lazy_static",
"log 0.4.11",
"mime 0.3.16",
"msgrpc",
@ -401,6 +402,7 @@ dependencies = [
"r2d2",
"r2d2_postgres",
"rand 0.7.3",
"regex",
"rmp",
"rmp-serde",
"rouille",

@ -32,4 +32,6 @@ r2d2 = "0.8.9"
r2d2_postgres = "0.16.0"
scheduled-thread-pool = "0.2.5"
num_cpus = "1.13.0"
parking_lot = "0.11.0"
parking_lot = "0.11.0"
regex = "1.4.2"
lazy_static = "1.4.0"

@ -1,4 +1,5 @@
use crate::database::permissions::Permissions;
use crate::database::models::CreatePermissionsEntry;
use crate::database::permissions::{Permissions, DEFAULT_PERMISSIONS};
use crate::database::role_permissions::RolePermissions;
use crate::database::roles::Roles;
use crate::database::user_roles::UserRoles;
@ -24,7 +25,7 @@ const DEFAULT_ADMIN_PASSWORD: &str = "flotte-admin";
const DEFAULT_ADMIN_EMAIL: &str = "admin@flotte-berlin.de";
const ENV_ADMIN_PASSWORD: &str = "ADMIN_PASSWORD";
const ENV_ADMIN_EMAIL: &str = "ADMIN_EMAIL";
const ADMIN_ROLE_NAME: &str = "SUPERADMIN";
pub(crate) const ADMIN_ROLE_NAME: &str = "SUPERADMIN";
pub trait Table {
fn new(pool: PostgresPool) -> Self;
@ -87,6 +88,15 @@ impl Database {
) {
log::debug!("Failed to create admin role {}", e.to_string())
}
self.permissions.create_permissions(
DEFAULT_PERMISSIONS
.iter()
.map(|(name, description)| CreatePermissionsEntry {
name: name.to_string(),
description: description.to_string(),
})
.collect(),
)?;
log::info!("Database fully initialized!");
Ok(())

@ -1,6 +1,14 @@
use crate::database::models::{CreatePermissionsEntry, Permission};
use crate::database::{DatabaseResult, PostgresPool, Table, ADMIN_ROLE_NAME};
use crate::utils::error::DBError;
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"),
];
/// The permissions table that stores defined
#[derive(Clone)]
@ -14,16 +22,15 @@ impl Table for Permissions {
}
fn init(&self) -> DatabaseResult<()> {
self.pool
.get()?
.batch_execute(
"CREATE TABLE IF NOT EXISTS permissions (
self.pool.get()?.batch_execute(
"CREATE TABLE IF NOT EXISTS permissions (
id SERIAL PRIMARY KEY,
name VARCHAR(128) UNIQUE NOT NULL,
description VARCHAR(512)
);",
)
.map_err(DBError::from)
)?;
Ok(())
}
}

@ -50,6 +50,20 @@ impl Roles {
if exists.is_some() {
return Err(DBError::RecordExists);
}
let permissions_exist = connection.query(
"SELECT id FROM permissions WHERE permissions.id = ANY ($1)",
&[&permissions],
)?;
if permissions_exist.len() != permissions.len() {
return Err(DBError::GenericError(format!(
"Not all provided permissions exist! Existing permissions: {:?}",
permissions_exist
.iter()
.map(|row| -> i32 { row.get(0) })
.collect::<Vec<i32>>()
)));
}
log::trace!("Preparing transaction");
let admin_email = dotenv::var(ENV_ADMIN_EMAIL).unwrap_or(DEFAULT_ADMIN_EMAIL.to_string());
let mut transaction = connection.transaction()?;

@ -146,6 +146,23 @@ impl Users {
}
}
/// Returns if the user has the given permission
pub fn has_permission(&self, id: i32, permission: &str) -> DatabaseResult<bool> {
let mut connection = self.pool.get()?;
let row = connection.query_opt(
"\
SELECT * FROM user_roles, role_permissions, permissions
WHERE user_roles.user_id = $1
AND user_roles.role_id = role_permissions.role_id
AND role_permissions.permission_id = permissions.id
AND permissions.name = $2
LIMIT 1
",
&[&id, &permission],
)?;
Ok(row.is_some())
}
/// 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> {

@ -1,6 +1,12 @@
use crate::database::permissions::CREATE_ROLE_PERMISSION;
use crate::database::Database;
use crate::server::messages::{LoginMessage, LogoutConfirmation, LogoutMessage, RefreshMessage};
use crate::server::messages::{
CreateRoleRequest, CreateRoleResponse, LoginMessage, LogoutConfirmation, LogoutMessage,
RefreshMessage,
};
use crate::utils::error::DBError;
use crate::utils::get_user_id_from_token;
use regex::Regex;
use rouille::{Request, Response, Server};
use serde::export::Formatter;
use serde::Serialize;
@ -82,6 +88,9 @@ impl UserHttpServer {
(POST) (/logout) => {
Self::logout(&database, request).unwrap_or_else(HTTPError::into)
},
(POST)(/roles/create) => {
Self::create_role(&database, request).unwrap_or_else(HTTPError::into)
},
_ => if request.method() == "OPTIONS" {
Response::empty_204()
} else {
@ -143,6 +152,27 @@ impl UserHttpServer {
Ok(Response::json(&LogoutConfirmation { success }).with_status_code(205))
}
fn create_role(database: &Database, request: &Request) -> HTTPResult<Response> {
let (_token, id) = validate_request_token(request, database)?;
if !database.users.has_permission(id, CREATE_ROLE_PERMISSION)? {
return Err(HTTPError::new("Insufficient permissions".to_string(), 403));
}
let message: CreateRoleRequest = serde_json::from_str(parse_string_body(request)?.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))?;
let role =
database
.roles
.create_role(message.name, message.description, message.permissions)?;
let permissions = database.role_permission.by_role(role.id)?;
Ok(Response::json(&CreateRoleResponse {
id: role.id,
permissions,
name: role.name,
})
.with_status_code(201))
}
}
/// Parses the body of a http request into a string representation
@ -156,3 +186,22 @@ fn parse_string_body(request: &Request) -> HTTPResult<String> {
Ok(string_body)
}
/// 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();}
let token = request
.header("authorization")
.ok_or(HTTPError::new("401 Unauthorized".to_string(), 401))?;
let token = BEARER_REGEX.replace(token, "");
let (valid, _) = database.users.validate_request_token(&token.to_string())?;
if !valid {
Err(HTTPError::new("Invalid request token".to_string(), 401))
} else {
Ok((
token.to_string(),
get_user_id_from_token(&token.to_string())
.ok_or(HTTPError::new("Invalid request token".to_string(), 401))?,
))
}
}

@ -1,4 +1,4 @@
use crate::database::models::CreatePermissionsEntry;
use crate::database::models::{CreatePermissionsEntry, Permission};
use crate::utils::error::DBError;
use serde::export::Formatter;
use serde::{Deserialize, Serialize};
@ -99,3 +99,10 @@ pub struct LogoutMessage {
pub struct LogoutConfirmation {
pub success: bool,
}
#[derive(Serialize)]
pub struct CreateRoleResponse {
pub id: i32,
pub name: String,
pub permissions: Vec<Permission>,
}

Loading…
Cancel
Save