Compare commits

..

26 Commits

Author SHA1 Message Date
8ebe2d8aff Merge pull request 'Improve README' (#24) from README-fix into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #24
Reviewed-by: Leon Wilzer <leon@noreply.localhost>
Reviewed-by: hendrik <hendrik@noreply.localhost>
2024-02-15 14:56:24 +01:00
a67e1439eb Merge branch 'main' into README-fix
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-02-15 14:53:45 +01:00
db1a21f5b3 Merge pull request 'duplicate stupid workarounds' (#27) from drone-amd64-fix into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #27
Reviewed-by: Leon Wilzer <leon@noreply.localhost>
2024-02-15 14:53:36 +01:00
475ac447d6 Merge branch 'main' into README-fix
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-02-15 14:51:22 +01:00
b645c1cdaa duplicate stupid workarounds
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-15 08:04:37 +01:00
3c6be42b9e revert to regex-compliant readme
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-02-15 07:26:47 +01:00
f5997f3ab2 Merge pull request 'removed some unneeded --verbose flags' (#25) from less-verbose-ci into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #25
2024-02-15 02:55:21 +01:00
8b02f33718
removed some unneeded --verbose flags
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-15 02:53:19 +01:00
cd95c46131 tidying up
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-14 18:38:37 +01:00
89b62242bf some final improvements
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-14 18:37:07 +01:00
ae6539e456 add some A's
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-14 18:30:42 +01:00
c751a3240e actually sane readme
All checks were successful
continuous-integration/drone/push Build is passing
#23
2024-02-14 18:20:35 +01:00
2cca88d709 fix v2
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-14 18:19:42 +01:00
9b57da84c3
doubled the amount of A
All checks were successful
continuous-integration/drone/push Build is passing
A is an important part of every healthy breakfast, increased it so that everyone may get enough A. Relates to #23
2024-02-14 18:11:04 +01:00
82575d8d6a Merge pull request 'inserted some useful comments' (#22) from ci-comments into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #22
2024-02-14 01:27:58 +01:00
c39a6ec626 inserted some useful comments
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-02-14 01:26:33 +01:00
a239e5d867 Merge pull request 'some small fixes' (#21) from ci-fixes into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #21
2024-02-14 01:23:42 +01:00
68ff9732ae some small fixes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-14 01:22:33 +01:00
a85f43ea4f auto-format (#20)
All checks were successful
continuous-integration/drone/push Build is passing
Automatically formats commits using rustfmt if needed.
Auto-format commits have @wanessa as the committer and the respective author of the current commit as the of the auto-format commit. Every auto-format commit message is “rustfmt”.
Includes PGP Signing.

Reviewed-on: #20
2024-02-14 01:18:01 +01:00
c8725cf861 Merge pull request 'replaced images with alpine, since a dependency wants to link with local cc' (#19) from alpine->slim-bookworm into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #19
2024-02-13 01:15:33 +01:00
94f274d4d4 replaced images with alpine, since a dependency wants to link with local cc
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-13 01:11:37 +01:00
91b1aeb1be Merge pull request 'more stupid workarounds' (#17) from more-workarounds into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #17
2024-02-13 01:04:23 +01:00
89ea97dd6a more stupid workarounds
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
An oversight of the original """fix""". Cargo needs to resolve DNS to pull dependencies.
2024-02-13 01:01:35 +01:00
bd4e7eadcb ccc (#14)
All checks were successful
continuous-integration/drone/push Build is passing
Adjusted linting and formatting settings

Reviewed-on: #14
Reviewed-by: hendrik <hendrik@noreply.localhost>
Co-authored-by: Leon Wilzer <leon.wilzer@protonmail.com>
Co-committed-by: Leon Wilzer <leon.wilzer@protonmail.com>
2024-02-09 21:26:45 +01:00
02d175676a Fixed Project name (rust wants snake_case)
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-13 21:36:53 +01:00
202f146467 Split test into build and test. Added pipelines for release build.
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-13 21:35:42 +01:00
9 changed files with 213 additions and 655 deletions

View File

@ -1,28 +1,196 @@
kind: pipeline
name: test-on-amd64
name: amd64 [debug]
platform:
arch: amd64
steps:
- name: test
image: rust:alpine
- name: build [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
- cargo test --verbose --all
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo build --all
- name: test [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo test --all
- name: lint-clippy [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- rustup component add clippy
- cargo clippy --all
---
kind: pipeline
name: test-on-arm64
name: arm64 [debug]
platform:
arch: arm64
steps:
- name: test
image: rust:alpine
- name: build [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
- cargo test --verbose --all
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo build --all
- name: test [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo test --all
- name: lint-clippy [debug]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- rustup component add clippy
- cargo clippy --all
---
kind: pipeline
name: amd64 [release]
platform:
arch: amd64
steps:
- name: build and test [release]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo test --all --release
---
kind: pipeline
name: arm64 [release]
platform:
arch: arm64
steps:
- name: build and test [release]
image: rust:1.71.1-slim-bookworm
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- cargo test --all --release
---
kind: pipeline
name: rustfmt
steps:
- name: Format Project with rustfmt
image: rust:1.71.1-alpine
commands:
- rustc --version
- cargo --version
# Stupid workarounds, for stupid problems
- echo "nameserver 1.1.1.1" >> /etc/resolv.conf
- echo "nameserver 1.0.0.1" >> /etc/resolv.conf
- rustup component add rustfmt
- cargo fmt --all --check && echo "No formatting required, exiting early..." && exit 0
- cargo fmt --all --verbose
# Check for format loop
- apk add git
- '[ "$(git log -1 --pretty=%B | grep -E ".+")" = "rustfmt" ] && echo "format loop detected, aborting..." && exit 1'
# Get necessary packages
- apk add openssh gpg gpg-agent
# configure SSH and import private key
- mkdir -p "$${HOME}/.ssh"
- echo "$SSH_PRIVATE_KEY" > "$${HOME}/.ssh/git"
- echo "Host git.libre.moe" >> "$${HOME}/.ssh/config"
- echo " User git" >> "$${HOME}/.ssh/config"
- echo " IdentityFile $${HOME}/.ssh/git" >> "$${HOME}/.ssh/config"
- echo "$GITEA_ED25519_SIG" >> "$${HOME}/.ssh/known_hosts"
- echo "$GITEA_RSA_SIG" >> "$${HOME}/.ssh/known_hosts"
- echo "$GITEA_ECDSA_SIG" >> "$${HOME}/.ssh/known_hosts"
- chmod 700 -R "$${HOME}/.ssh"
# setup gpg
- gpg-agent --daemon
# the git config gpg.program absolutely despises anything other than a path, including additional arguments.
# so we just put it into it's own shell script and use that later
- echo "#!/bin/sh" >> /tmp/gpg.sh
- echo gpg --batch --pinentry-mode loopback --passphrase '$GPG_PASSPHRASE' \$@ >> /tmp/gpg.sh
- chmod 777 /tmp/gpg.sh
# import gpg key
- echo "$GPG_PRIVKEY" > /tmp/private.key
- /tmp/gpg.sh --import /tmp/private.key >> /tmp/import.sh || exit 2
# configure git
- git config --local user.name "WANessa"
- git config --local user.email "$GIT_EMAIL_ADDRESS"
- git config --local user.signingkey "$GPG_PUBKEY_ID"
- git config --local gpg.program "/tmp/gpg.sh" # see above comment
# Uncomment below line for ssh debugging
# - git config core.sshCommand '/usr/bin/ssh -v'
- git remote add ssh "$DRONE_GIT_SSH_URL"
# commit and push every modified file, does not include new files, because why should it?
- GIT_COMMITTER_NAME="WANessa" GIT_COMMITTER_EMAIL="$GIT_EMAIL_ADDRESS" git commit --author "$DRONE_COMMIT_AUTHOR_NAME <$DRONE_COMMIT_AUTHOR_EMAIL>" -S -a -m "rustfmt"
- git push ssh
environment:
SSH_PRIVATE_KEY:
from_secret: WANESSA_SSH_PRIVKEY
GIT_EMAIL_ADDRESS:
from_secret: WANESSA_EMAIL_ADDRESS
GITEA_ED25519_SIG:
from_secret: GIT_ED25519_SIG
GITEA_RSA_SIG:
from_secret: GIT_RSA_SIG
GITEA_ECDSA_SIG:
from_secret: GIT_ECDSA_SIG
GPG_PUBKEY_ID:
from_secret: WANESSA_GPG_PUBKEY_ID
GPG_PASSPHRASE:
from_secret: WANESSA_GPG_PASSPHRASE
GPG_PRIVKEY:
from_secret: WANESSA_GPG_PRIVKEY
depends_on:
- amd64 [debug]
- arm64 [debug]
- amd64 [release]
- arm64 [release]

View File

@ -1,7 +0,0 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml",
"./Cargo.toml"
],
"rust-analyzer.showUnlinkedFileNotification": false
}

17
.vscode/tasks.json vendored
View File

@ -1,17 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "rust: cargo build"
}
]
}

View File

@ -6,4 +6,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mysql = "*"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
enum_glob_use = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/enum_glob_use
pedantic = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=pedantic
nursery = "deny" # wip lints: https://rust-lang.github.io/rust-clippy/master/index.html#/?groups=nursery
unwrap_used = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_used
missing_const_for_fn = "warn" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn
missing_assert_message = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message
missing_errors_doc = "deny" # https://rust-lang.github.io/rust-clippy/master/index.html#/missing_assert_message

File diff suppressed because one or more lines are too long

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
hard_tabs = true

View File

@ -1,354 +0,0 @@
//!
//!
//! The database module to use WANessa's MySQL database
//!
//! This module provides abstraction for the networking and SQL queries
//! to simplify the database access and provide structures to represent
//! the database's content. The following functionalities are provided:
//! - Creating a new WANessa database from scratch
//! - Reading localized texts from the database
//! - Getting info about permission spaces
//! - Getting and setting info about a user's permissions in specific spaces
//!
//! Note that this module is not responsible for ANY safety features. Even
//! simple attacks like SQL injections are not checked for.
//! DO NOT PASS ANY UNCHECKED USER INPUT TO ANY OF THESE FUNCTIONS!
//!
use mysql::{*, prelude::{Queryable, FromRow}};
use std::fmt;
/// The 'TextType' enum represents the type of a LocalizedText.
/// It is used to categorize the meaning of the text's content
/// (is it a greeting, an affirmation, an insult, ...)
pub enum TextType {
/// The text is some type of greeting
Greeting = 0,
/// The text is some kind of an affirmative statement
Ok = 1,
/// The text is some kind of a negative statement
NotOk = 2,
/// The text is a translated name for a role
RoleName = 3,
}
impl TextType {
#[allow(dead_code)]
pub fn fmt(&self, tt: &mut fmt::Formatter) -> fmt::Result {
let s: &str = match self {
TextType::Greeting => "Greeting",
TextType::Ok => "Ok",
TextType::NotOk => "NotOk",
TextType::RoleName => "RoleName"
};
tt.write_str(s)?;
Ok(())
}
fn from(item: u32) -> Self {
match item {
0 => TextType::Greeting,
1 => TextType::Ok,
2 => TextType::NotOk,
3 => TextType::RoleName,
_ => panic!("Invalid value"),
}
}
}
/// A representation of a tuple stored in the 'LocalizedText' relation of the database
pub struct LocalizedText {
/// The ID used to identify the specific text in the database. Note
/// that the same text translated to different languages still has
/// the same ID
pub id: usize,
/// The language the text is translated in
pub language_id: usize,
/// The actual text content
pub content: String,
/// The type of the text
pub text_type: TextType
}
impl FromRow for LocalizedText {
fn from_row_opt(row: Row) -> std::result::Result<Self, FromRowError> {
let (id, language_id, content, text_type): (usize, usize, String, u32) = from_row_opt(row)?;
let text_type = TextType::from(text_type);
Ok(Self { id, language_id, content, text_type })
}
}
/// A representation of a tuple stored in the 'Space' relation of the database
pub struct Space {
pub id: usize,
pub name: String,
pub parent_id: Option<usize>
}
impl FromRow for Space {
fn from_row_opt(row: Row) -> std::result::Result<Self, FromRowError> {
let (id, name, parent_id): (usize, String, Option<usize>) = from_row_opt(row)?;
Ok(Self { id, name, parent_id})
}
}
/// A representation of a tuple stored in the 'Role' relation of the database
pub struct Role {
pub id: usize,
pub localized_name_id: usize,
pub permissions: String
}
impl FromRow for Role {
fn from_row_opt(row: Row) -> std::result::Result<Self, FromRowError> {
let (id, localized_name_id, permissions): (usize, usize, String) = from_row_opt(row)?;
Ok(Self { id, localized_name_id, permissions})
}
}
/// A representation of a tuple stored in the 'User' relation of the database
pub struct User {
/// Represents the internal ID for this user
pub id: String,
/// Represents the display name of the user
pub name: String,
}
impl FromRow for User {
fn from_row_opt(row: Row) -> std::result::Result<Self, FromRowError> {
let (id, name): (String, String) = from_row_opt(row)?;
Ok(Self { id, name})
}
}
///
/// A non-public helper function that performs a query and returns the result as an option value
/// Note that the value enclosed in the option needs to implement FromRow.
///
fn query_and_handle_error<RetType: FromRow>(connection: &mut PooledConn, query: String) -> Option<RetType> {
match connection.query_first(query) {
Ok(opt) => {
match opt {
Some(lt) => Some(lt),
None => None,
}
},
Err(e) => {
println!("ERR! {}", e.to_string());
None
},
}
}
/// Initializes the database. Tries to establish a connection to the url specified
/// and returns true if the initialization succeeded and false otherwise.
#[allow(dead_code)]
pub fn initialize(url: &str, port: u16, dbname: &str, username: &str, password: &str) -> Result<PooledConn, Error> {
let full_url: String = format!("mysql://{}:{}@{}:{}/{}", username, password, url, port, dbname);
let pool = Pool::new(full_url.as_str())?;
let conn = pool.get_conn()?;
Ok(conn)
}
/// A helper function for more readable code. Initializes the database and panics
/// if any error occurs during the function.
#[allow(dead_code)]
pub fn initialize_or_panic(url: &str, port: u16, dbname: &str, username: &str, password: &str) -> PooledConn {
match initialize(url, port, dbname, username, password) {
Ok(c) => {
return c;
},
Err(e) => {
// Panic at this point as this is a critical error that doesn't allow the program
// to be run safely
panic!("Database initialization error! (\"{}\")", e.to_string());
}
}
}
/// Retrieves a LocalizedText from the database and returns an instance
/// of Option<LocalizedText>. See LocalizedText definition for more information.
///
/// connection is the database connection returned from the initialize function.
///
/// text_id is the ID of the text as described in the "LocalizedText"
/// documentation. This will return an empty option if no text with the ID
/// specified is present in the database.
///
/// language_id is the target language in which the text
/// should be returned. Note that this will fall back to any other
/// language if the text is not present for the specified language in the
/// database.
#[allow(dead_code)]
pub fn localized_text(connection: &mut PooledConn, text_id: usize, language_id: usize) -> Option<LocalizedText> {
let query: String = format!("SELECT * FROM LocalizedText WHERE ID={} AND LanguageID={}" , text_id, language_id);
return query_and_handle_error(connection, query);
}
/// Retrieves a Space from the database and returns an instance of Option<Space>, which holds a space instance
/// if the space was found and no error occured and None otherwise.
///
/// connection is the database connection returned from the initialize function.
///
/// space_id is the ID of the space that should be retrieved from the database.
#[allow(dead_code)]
pub fn get_space(connection: &mut PooledConn, space_id: usize) -> Option<Space> {
let query: String = format!("SELECT * FROM Space WHERE ID={}" , space_id);
return query_and_handle_error(connection, query);
}
/// Retrieves the permission description string for the specified user from the database and returns
/// an instance of Option<String>. Note that this function will not take into account the parent spaces
/// (if the parent space defines "Allow" for a permission and the specified space defined "Disallow", this
/// function will return only the space-local settings, which in this case would be "Disallow").
/// See the get_user_permissions_recurse function to recursively get the permission string that actually
/// determines what a user can do in the specified space.
///
/// connection is the database connection returned from the initialize function.
///
/// user_id is the ID of the user whose permissions should be returned.
///
/// space_id is the ID for the space, for that the permissions should be evaluated.
#[allow(dead_code)]
pub fn get_user_permissions(connection: &mut PooledConn, user_id: &str, space_id: usize) -> Option<String> {
let query: String = format!(
"SELECT Permissions FROM UserRoles JOIN Role ON UserRoles.RoleID=Role.RoleID WHERE SpaceID={} AND UserID='{}'",
space_id,
user_id);
return query_and_handle_error(connection, query);
}
/// Updates the permissions of a specific user in a specific space
#[allow(dead_code)]
pub fn set_role_permissions(connection: &mut PooledConn, role_id: usize, new_perms: &str) {
let query: String = format!("UPDATE Role SET Permissions='{}' WHERE RoleID={}", new_perms, role_id);
let _: Option<String> = query_and_handle_error(connection, query);
}
/// Returns the permissions of a specific role
#[allow(dead_code)]
pub fn get_role_permissions(connection: &mut PooledConn, role_id: usize) -> Option<String> {
let query: String = format!("SELECT Permissions FROM Role WHERE RoleID={}", role_id);
return query_and_handle_error(connection, query);
}
/// Returns the username for the user with the specified ID
#[allow(dead_code)]
pub fn get_username(connection: &mut PooledConn, user_id: String) -> Option<String> {
let query: String = format!("SELECT Name FROM User WHERE ID={}", user_id);
return query_and_handle_error(connection, query);
}
/// Creates the WANessa database from scratch.
#[allow(dead_code)]
pub fn create_db(connection: &mut PooledConn) {
// Create the LocalizedText relation
let _: Option<String> = query_and_handle_error(connection, String::from("CREATE TABLE LocalizedText (\
ID INT PRIMARY KEY,\
LanguageID INT NOT NULL,\
Content varchar(200) NOT NULL,\
Type INT NOT NULL CHECK (Type >= 0 AND Type <= 3))"));
// Create the Role relation
let _: Option<String> = query_and_handle_error(connection, String::from("CREATE TABLE Role (\
RoleID INT PRIMARY KEY,\
LocalizedNameID INT references LocalizedText(ID) NOT NULL,\
Permissions varchar(60) NOT NULL"));
// Create the Space relation
let _: Option<String> = query_and_handle_error(connection, String::from("CREATE TABLE Space (\
ID INT PRIMARY KEY,\
Name varchar(80) NOT NULL,\
ParentSpaceID INT references Space(ID))"));
// Create the User relation
let _: Option<String> = query_and_handle_error(connection, String::from("CREATE TABLE User (\
ID varchar(50) PRIMARY KEY,\
Name varchar(80) NOT NULL)"));
// Create the UserRoles relation
let _: Option<String> = query_and_handle_error(connection, String::from("CREATE TABLE UserRoles (\
UserID varchar(50) references User(ID) NOT NULL,\
SpaceID INT references Space(ID) NOT NULL,\
RoleID INT references Role(RoleID) NOT NULL,\
PRIMARY KEY (UserID, SpaceID, RoleID)\
)"));
}
///
/// The test module for the database
///
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connect_and_get_text() {
// Test settings
let url = "localhost";
let port = 3306;
let dbname = "WANessaDB";
let username = "testuser";
let password = "12345";
// Query settings
let text_id = 1;
let lang_id = 1;
// Perform test
let mut c = initialize_or_panic(url, port, dbname, username, password);
match localized_text(&mut c, text_id, lang_id) {
Some(t) => println!("Got text from database: {}", t.content),
None => println!("No text found for {} in lang {}!", text_id, lang_id)
}
}
#[test]
fn test_connect_and_get_perms() {
// Test settings
let url = "localhost";
let port = 3306;
let dbname = "WANessaDB";
let username = "testuser";
let password = "12345";
// Query settings
let target_user = "testuser";
let space_id = 1;
// Perform test
let mut c = initialize_or_panic(url, port, dbname, username, password);
match get_user_permissions(&mut c, target_user, space_id) {
Some(t) => println!("Got permissions from database: {}", t.as_str()),
None => println!("No permissions found for user {}!", target_user)
}
}
#[test]
fn test_permission_set() {
let mut db_conn = initialize_or_panic("localhost",
3306,
"WANessaDB",
"testuser",
"12345");
set_role_permissions(&mut db_conn, 1, "p");
}
}

View File

@ -1,25 +1,3 @@
use mysql::PooledConn;
mod database;
mod permission;
fn main()
{
// Database initialization and some example code
let mut db_conn: PooledConn = database::initialize_or_panic(
"localhost",
3306,
"WANessaDB",
"testuser",
"12345");
let text = database::localized_text(&mut db_conn, 1, 1);
match text {
Some(_) => {
println!("Got the text!");
},
None => {
println!("Got no text :(");
}
}
fn main() {
println!("Hello, world!");
}

View File

@ -1,223 +0,0 @@
//!
//!
//! The permission module handles everything regarding permission management.
//! This module's tasks are:
//! - Providing structures and enums related to permission handling to other modules
//! - Granting / Revoking permissions from/to user(s)
//!
//!
use std::{collections::HashMap, str::FromStr, hash::Hash};
use mysql::*;
use crate::database::*;
use crate::database;
/// This enum contains all permissions
#[derive(PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub enum Permission
{
UseCommands = 0,
}
impl Permission {
#[allow(dead_code)]
fn is_set(self, permstring: String) -> bool {
match permstring.chars().nth(self as usize) {
Some(c) => c == 'p',
None => panic!("The permstring provided does not contain all permissions and is therefore invalid!")
}
}
}
/// This enum describes the state of a permission for a PermissionTable. Any Permission
/// can be allowed, disallowed or passed through to a subsidiary PermissionTable (don't care state).
#[allow(dead_code)]
pub enum PermState
{
Allow = 2,
DontCare = 1,
Disallow = 0
}
impl FromStr for PermState {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"p" => Ok(PermState::Allow),
"n" => Ok(PermState::Disallow),
"x" => Ok(PermState::DontCare),
_ => Err(()),
}
}
}
/// Definition of a PermissionTable. A permission table is a map
/// that provides one of the three PermStates for every permission.
#[allow(dead_code)]
pub struct PermissionTable {
map: HashMap<Permission, PermState>
}
impl PermissionTable {
fn new() -> Self {
PermissionTable { map: HashMap::new() }
}
fn insert(&mut self, perm: Permission, state: PermState) {
self.map.insert(perm, state);
}
fn from_str(s: &str) -> Result<Self, ()> {
let mut table = PermissionTable::new();
for (i, state) in s.chars().enumerate() {
if let Ok(state) = PermState::from_str(&state.to_string()) {
let permission = match i {
0 => Permission::UseCommands,
_ => return Err(()),
};
table.insert(permission, state);
} else {
return Err(());
}
}
Ok(table)
}
fn get_permstring(self) -> &'static str {
return ""
}
}
/// Sets the state of the specified Permission for the specified role to the specified state.
#[allow(dead_code)]
pub fn set_permission(db_connection: &mut PooledConn, role_id: usize, perm: Permission, state: PermState) -> bool {
match database::get_role_permissions(db_connection, role_id) {
Some(permstring) => {
match PermissionTable::from_str(permstring.as_str()) {
Ok(mut permtable) => {
permtable.insert(perm, state);
database::set_role_permissions(db_connection, role_id, permtable.get_permstring());
return true;
},
Err(_) => return false
}
},
None => return false
}
}
/// Retrieves the actual permissions of a user from the database. This function also applies all the parent space's rules,
/// so that the result of this function can be used to determine, if a user has a specific permission in a specific space.
/// This function returns an Option, which will be empty if an error occured (e.g. database inconsistency). If no error
/// occurs, the Option will contain a String that only contains 'p' for positive permissions and 'n' for negative ones.
/// Permissions that are globally defined as "Don't care" will be treated as "Disallow" by this function.
#[allow(dead_code)]
pub fn get_permissions(db_connection: &mut PooledConn, user_id: &str, space_id: usize) -> Option<String> {
let mut permstr_opt: Option<String> = get_user_permissions(db_connection, user_id, space_id);
let space_opt: Option<Space> = get_space(db_connection, space_id);
match permstr_opt {
Some(mut permstr) => {
let mut space: Space = match space_opt {
Some(s) => s,
None => return None // The initial space could not be retrieved from the database
};
loop {
// query the current space in order to get the parent space ID
let parent_id: usize;
space = match space.parent_id {
Some(parent_space_id) => {
parent_id = parent_space_id;
match get_space(db_connection, parent_space_id) {
Some(parent_space) => parent_space,
None => return None // The parent space specified in the database could not be retrieved
}
},
None => break // The top of the space hierarchy has been reached. The permission string is complete.
};
permstr_opt = get_user_permissions(db_connection, user_id, parent_id);
permstr = match permstr_opt {
Some(n) => {
// Here we have the permission string from the sub-space ('permstr') and the permission string from the
// super-space ('n'). Now the sub-space string will be overridden by every non-dont-care state in the
// super-space string, because super-space rules are always prioritized higher than the sub-space rules.
let mut newpermstr = String::from("");
if n.len() != permstr.len() {
return None; // There is a database inconsistency where not all permstrings are equally long.
}
for (super_char, sub_char) in n.chars().zip(permstr.chars()) {
match super_char {
'p' => newpermstr += "p",
'n' => newpermstr += "n",
'x' => newpermstr += sub_char.to_string().as_str(),
_ => return None // There is a database inconsistency where not all permstrings consist of 'p', 'n' or 'x'.
}
}
newpermstr
},
None => {
// No explicit entry for the permissions at that level exist. This is valid and every permission entry
// is therefore assumed as "Don't care". Because a string consisting of just "Don't care" states will not
// change the permissions in the sub-space, the permstr will stay the same in this case.
permstr
}
};
};
// Check the characters for validity due to the top-most layer not being checked in the loop
for c in permstr.chars() {
if c != 'p' && c != 'x' && c != 'n' {
return None // The permission string in the top-most layer contains invalid states
}
}
// At this point, permstr contains all the permissions of the user while only considering the database entries. However,
// some permissions could be declared as "Don't care" on every level. These "Don't care" states will now be replaced by
// "Disallow" states.
permstr = permstr.chars().map(|c| if c == 'x' { 'n' } else { c }).collect();
Some(permstr)
},
None => None // The permission query for the user in the space failed
}
}
/// Checks if the user has the specified permission in the specified space
#[allow(dead_code)]
pub fn has_permission(db_connection: &mut PooledConn, user_id: &str, space_id: usize, perm: Permission) -> Option<bool> {
let perms = get_permissions(db_connection, user_id, space_id);
match perms {
Some(permstring) => Some(perm.is_set(permstring)),
None => None
}
}
#[cfg(test)]
mod tests {
use crate::database;
use super::*;
#[test]
fn test_permission_check() {
let mut db_conn = database::initialize_or_panic("localhost",
3306,
"WANessaDB",
"testuser",
"12345");
let hasperm = has_permission(&mut db_conn, "testuser", 0, Permission::UseCommands);
match hasperm {
Some(hasres) => {
match hasres {
true => println!("The user has the specified permission"),
false => println!("The user does not have the specified permission")
}
},
None => panic!("An error occured. has_permission returned no value.")
}
}
}