Extended login system, added member selection and database query to get full member (incl. permissions)
This commit is contained in:
parent
d569d134e7
commit
b9f084b2ec
|
@ -205,6 +205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -13,7 +13,7 @@ serde = "1.0.104"
|
|||
log = "0.4.8"
|
||||
env_logger = "0.7.1"
|
||||
rocket = "0.4.3"
|
||||
diesel = { version = "1.4", features = ["postgres", "uuid"] }
|
||||
diesel = { version = "1.4", features = ["postgres", "uuid", "chrono"] }
|
||||
diesel_geometry = "1.4.0"
|
||||
uuid = { version = "0.6", features = ["serde"] } #Only version 0.6 is supported by diesel 1.4
|
||||
rust-argon2 = "0.8"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
drop table permissions;
|
|
@ -0,0 +1,8 @@
|
|||
-- Your SQL goes here
|
||||
create table permissions
|
||||
(
|
||||
permission text not null
|
||||
constraint permissions_pk
|
||||
primary key,
|
||||
description text
|
||||
);
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
drop table roles;
|
|
@ -0,0 +1,8 @@
|
|||
-- Your SQL goes here
|
||||
create table roles
|
||||
(
|
||||
id text not null
|
||||
constraint roles_pk
|
||||
primary key,
|
||||
description text
|
||||
);
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
drop table roles_permissions;
|
|
@ -0,0 +1,14 @@
|
|||
-- Your SQL goes here
|
||||
create table roles_permissions
|
||||
(
|
||||
role_id text not null
|
||||
constraint roles_permissions_roles_id_fk
|
||||
references roles
|
||||
on update cascade on delete cascade,
|
||||
permission_id text not null
|
||||
constraint roles_permissions_permissions_permission_fk
|
||||
references permissions
|
||||
on update cascade on delete cascade,
|
||||
constraint roles_permissions_pk
|
||||
primary key (role_id, permission_id)
|
||||
);
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
drop table members_roles;
|
|
@ -0,0 +1,14 @@
|
|||
-- Your SQL goes here
|
||||
create table members_roles
|
||||
(
|
||||
member_id uuid not null
|
||||
constraint members_roles_entities_entity_id_fk
|
||||
references entities
|
||||
on update cascade on delete cascade,
|
||||
role_id text not null
|
||||
constraint members_roles_roles_id_fk
|
||||
references roles
|
||||
on update cascade on delete cascade,
|
||||
constraint members_roles_pk
|
||||
primary key (member_id, role_id)
|
||||
);
|
|
@ -1,13 +1,18 @@
|
|||
use crate::database::model::members::Sex;
|
||||
use crate::database::controller::connector::establish_connection;
|
||||
use crate::database::model::members::{RawMember, Sex};
|
||||
use crate::diesel::prelude::*;
|
||||
use crate::helper::settings::Settings;
|
||||
use crate::modules::member_management::model::member::Member;
|
||||
use diesel::backend::Backend;
|
||||
use diesel::deserialize;
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::sql_types::Integer;
|
||||
use diesel::sql_types::{Integer, SmallInt};
|
||||
use diesel::{deserialize, JoinOnDsl, QueryDsl};
|
||||
use rocket::State;
|
||||
|
||||
impl<DB> FromSql<Integer, DB> for Sex
|
||||
impl<DB> FromSql<SmallInt, DB> for Sex
|
||||
where
|
||||
DB: Backend,
|
||||
i16: FromSql<Integer, DB>,
|
||||
i16: FromSql<SmallInt, DB>,
|
||||
{
|
||||
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
|
||||
match i16::from_sql(bytes)? {
|
||||
|
@ -19,3 +24,99 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggregates all permissions a member has (from all assigned roles)
|
||||
///
|
||||
/// #Arguments
|
||||
/// * 'uuid' - Member's uuid, type uuid::Uuid
|
||||
/// * 'settings' - Settings, as managed State
|
||||
pub fn get_member_permissions_by_uuid(
|
||||
uuid: uuid::Uuid,
|
||||
settings: &State<Settings>,
|
||||
) -> Option<Vec<String>> {
|
||||
use crate::schema::members::dsl::*;
|
||||
use crate::schema::members_roles::dsl::*;
|
||||
use crate::schema::roles_permissions::dsl::permission_id;
|
||||
use crate::schema::roles_permissions::dsl::role_id as role_id2;
|
||||
use crate::schema::roles_permissions::dsl::roles_permissions;
|
||||
|
||||
let connection = establish_connection(settings);
|
||||
|
||||
let data = members
|
||||
.left_join(members_roles.on(member_id.eq(entity_id)))
|
||||
.left_join(roles_permissions.on(role_id2.eq(role_id)))
|
||||
.select(permission_id.nullable())
|
||||
.filter(entity_id.eq(uuid))
|
||||
.load::<Option<String>>(&connection);
|
||||
|
||||
let data = match data {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error occured while checking permissions for member {}: {}",
|
||||
uuid, e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut permissions: Vec<String> = vec![];
|
||||
|
||||
for permission in data {
|
||||
match permission {
|
||||
Some(permission) => permissions.push(permission),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
if (!permissions.is_empty()) {
|
||||
Some(permissions)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get vector of all members (database::model::members::RawMember) for a user uuid
|
||||
///
|
||||
/// #Arguments
|
||||
/// * 'uuid' - User's uuid, type uuid::Uuid
|
||||
/// * 'settings' - Settings, as managed State
|
||||
pub fn get_raw_members_by_uuid(uuid: uuid::Uuid, settings: &State<Settings>) -> Vec<RawMember> {
|
||||
use crate::schema::members::dsl::*;
|
||||
use diesel::debug_query;
|
||||
|
||||
let connection = establish_connection(settings);
|
||||
|
||||
let member: Result<Vec<RawMember>, diesel::result::Error> =
|
||||
members.filter(users_id.eq(uuid)).load(&connection);
|
||||
match member {
|
||||
Ok(member) => member,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error occured while loading data for member {}: {}",
|
||||
uuid, e
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get member_management::model::member::Member
|
||||
///
|
||||
/// #Arguments
|
||||
/// * 'uuid' - Users's uuid, type uuid::Uuid
|
||||
/// * 'settings' - Settings, as managed State
|
||||
pub fn get_member_by_uuid(uuid: uuid::Uuid, settings: &State<Settings>) -> Vec<Member> {
|
||||
let raw_members: Vec<RawMember> = get_raw_members_by_uuid(uuid, settings);
|
||||
let mut members: Vec<Member> = vec![];
|
||||
|
||||
for raw_member in raw_members {
|
||||
let mut permissions = get_member_permissions_by_uuid(raw_member.entity_id, settings);
|
||||
|
||||
let permissions = permissions.unwrap_or(vec![]);
|
||||
|
||||
members.push(Member::from((raw_member, permissions)));
|
||||
}
|
||||
|
||||
members
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::database::model::users::User;
|
|||
use crate::diesel::prelude::*;
|
||||
use crate::diesel::QueryDsl;
|
||||
use crate::helper::settings::Settings;
|
||||
use crate::modules::member_management::model::member::Member;
|
||||
use diesel::ExpressionMethods;
|
||||
use rocket::State;
|
||||
|
||||
|
@ -10,6 +11,7 @@ pub fn get_user_by_email(email: String, settings: &State<Settings>) -> Option<Us
|
|||
use crate::schema::users::dsl::*;
|
||||
|
||||
let connection = establish_connection(settings);
|
||||
|
||||
match users.filter(email.eq(email)).first(&connection) {
|
||||
Ok(user) => Some(user),
|
||||
Err(_) => None,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::time::SystemTime;
|
||||
use chrono::NaiveDate;
|
||||
use diesel::sql_types::SmallInt;
|
||||
|
||||
#[repr(i16)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Copy, Clone, AsExpression, FromSqlRow, Deserialize)]
|
||||
#[sql_type = "SmallInt"]
|
||||
pub enum Sex {
|
||||
UNKNOWN = 0,
|
||||
MALE = 1,
|
||||
|
@ -9,17 +10,17 @@ pub enum Sex {
|
|||
OTHER = 9,
|
||||
}
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct Member {
|
||||
#[derive(Queryable, Clone, Deserialize)]
|
||||
pub struct RawMember {
|
||||
pub(crate) entity_id: uuid::Uuid,
|
||||
pub(crate) users_id: uuid::Uuid,
|
||||
pub(crate) users_id: Option<uuid::Uuid>,
|
||||
pub(crate) firstname: String,
|
||||
pub(crate) lastname: String,
|
||||
pub(crate) date_of_birth: SystemTime,
|
||||
pub(crate) sex: Sex,
|
||||
pub(crate) salutation: String,
|
||||
pub(crate) place_of_birth: String,
|
||||
pub(crate) academic_titles: String,
|
||||
pub(crate) personnel_number: i32,
|
||||
pub(crate) ui_language: String,
|
||||
pub(crate) date_of_birth: Option<NaiveDate>,
|
||||
pub(crate) sex: Option<Sex>,
|
||||
pub(crate) salutation: Option<String>,
|
||||
pub(crate) place_of_birth: Option<String>,
|
||||
pub(crate) academic_titles: Option<String>,
|
||||
pub(crate) personnel_number: Option<i32>,
|
||||
pub(crate) ui_language: Option<String>,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::database::model::users::User;
|
|||
use crate::helper::session_cookies::model::{
|
||||
SessionCookie, SessionCookieError, SessionCookieStorage,
|
||||
};
|
||||
use crate::modules::member_management::model::member::Member;
|
||||
use chrono::{DateTime, Utc};
|
||||
use core::fmt;
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
@ -27,11 +28,12 @@ impl SessionCookieStorage {
|
|||
/// Generates cookie and adds it to the SessionCookieStorage
|
||||
///
|
||||
/// Generates random id via retrieve_id, creates cookie and adds it to the Storage.
|
||||
pub fn add(&self, expires: DateTime<Utc>, user: User) -> SessionCookie {
|
||||
pub fn add(&self, expires: DateTime<Utc>, user: User, member: Option<Member>) -> SessionCookie {
|
||||
let item = SessionCookie {
|
||||
id: self::SessionCookieStorage::retrieve_id(self),
|
||||
expires,
|
||||
user,
|
||||
member,
|
||||
};
|
||||
trace!(
|
||||
"Inserted cookie with id {} into SessionCookieStorage",
|
||||
|
@ -48,7 +50,7 @@ impl SessionCookieStorage {
|
|||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
//TODO: make user don't change id while updating cookie. This would cause inconsistency
|
||||
/// Updates cookie with specified id in Storage or adds it if it doesn't exist
|
||||
pub fn update(&self, cookie: SessionCookie) -> SessionCookie {
|
||||
trace!(
|
||||
|
@ -126,6 +128,7 @@ mod tests {
|
|||
let inserted_cookie = storage.add(
|
||||
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2099, 9, 9).and_hms(0, 00, 00), Utc),
|
||||
user.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(inserted_cookie.expires.eq(&DateTime::<Utc>::from_utc(
|
||||
|
@ -150,6 +153,7 @@ mod tests {
|
|||
let inserted_cookie = storage.add(
|
||||
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2099, 9, 9).and_hms(0, 00, 00), Utc),
|
||||
user.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut changed_cookie = inserted_cookie.clone();
|
||||
|
@ -175,6 +179,7 @@ mod tests {
|
|||
let inserted_cookie = storage.add(
|
||||
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2000, 9, 9).and_hms(0, 00, 00), Utc),
|
||||
user.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
let id = inserted_cookie.clone().id;
|
||||
|
@ -197,6 +202,7 @@ mod tests {
|
|||
let inserted_cookie = storage.add(
|
||||
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2000, 9, 9).and_hms(0, 00, 00), Utc),
|
||||
user.clone(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::database::model::users::User;
|
||||
use crate::modules::member_management::model::member::Member;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
@ -19,6 +20,8 @@ pub struct SessionCookie {
|
|||
/// SessionCookie will get destroyed if called + current DateTime >= expires
|
||||
pub(crate) expires: DateTime<Utc>,
|
||||
pub(crate) user: User,
|
||||
/// contains member data after member was selected
|
||||
pub(crate) member: Option<Member>,
|
||||
}
|
||||
|
||||
/// Error may occur while try to receive cookie out of SessionCookieStorage
|
||||
|
|
|
@ -52,6 +52,7 @@ fn main() {
|
|||
modules::dashboard::view::dashboard,
|
||||
modules::welcome::view::welcome_get::welcome_get,
|
||||
modules::welcome::view::welcome_post::welcome_post,
|
||||
modules::welcome::view::login_select_get::login_select_get,
|
||||
],
|
||||
)
|
||||
.mount("/css", StaticFiles::from("resources/css"))
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
use super::controller::render::get_context;
|
||||
use crate::database::controller::members::{
|
||||
get_member_permissions_by_uuid, get_raw_members_by_uuid,
|
||||
};
|
||||
use crate::database::model::users::User;
|
||||
use crate::helper::session_cookies::model::SessionCookieError;
|
||||
use crate::helper::settings::Settings;
|
||||
use rocket::http::Status;
|
||||
use rocket::State;
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
#[get("/portal")]
|
||||
pub fn dashboard(user: User) -> Result<Template, Status> {
|
||||
pub fn dashboard(user: User, settings: State<Settings>) -> Result<Template, Status> {
|
||||
get_raw_members_by_uuid(
|
||||
"035c34fe-47bc-11ea-af58-a4c3f06b801a".parse().unwrap(),
|
||||
&settings,
|
||||
);
|
||||
Ok(Template::render("module_dashboard", &get_context()))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
use crate::database::model::members::RawMember;
|
||||
use crate::modules::member_management::model::member::Member;
|
||||
|
||||
impl From<(RawMember, Vec<String>)> for Member {
|
||||
fn from(rawmember_permissions: (RawMember, Vec<String>)) -> Self {
|
||||
let raw_member = rawmember_permissions.0;
|
||||
let permissions = rawmember_permissions.1;
|
||||
|
||||
Member {
|
||||
entity_id: raw_member.entity_id,
|
||||
users_id: raw_member.users_id,
|
||||
firstname: raw_member.firstname,
|
||||
lastname: raw_member.lastname,
|
||||
date_of_birth: raw_member.date_of_birth,
|
||||
sex: raw_member.sex,
|
||||
salutation: raw_member.salutation,
|
||||
place_of_birth: raw_member.place_of_birth,
|
||||
academic_titles: raw_member.academic_titles,
|
||||
personnel_number: raw_member.personnel_number,
|
||||
ui_language: raw_member.ui_language,
|
||||
permissions,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod member_trait;
|
|
@ -0,0 +1,3 @@
|
|||
pub mod controller;
|
||||
pub mod model;
|
||||
pub mod view;
|
|
@ -0,0 +1,18 @@
|
|||
use crate::database::model::members::Sex;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
#[derive(Queryable, Clone, Deserialize)]
|
||||
pub struct Member {
|
||||
pub(crate) entity_id: uuid::Uuid,
|
||||
pub(crate) users_id: Option<uuid::Uuid>,
|
||||
pub(crate) firstname: String,
|
||||
pub(crate) lastname: String,
|
||||
pub(crate) date_of_birth: Option<NaiveDate>,
|
||||
pub(crate) sex: Option<Sex>,
|
||||
pub(crate) salutation: Option<String>,
|
||||
pub(crate) place_of_birth: Option<String>,
|
||||
pub(crate) academic_titles: Option<String>,
|
||||
pub(crate) personnel_number: Option<i32>,
|
||||
pub(crate) ui_language: Option<String>,
|
||||
pub(crate) permissions: Vec<String>,
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod member;
|
|
@ -27,6 +27,6 @@ pub fn add_session_cookie(
|
|||
let expires = Utc::now()
|
||||
.checked_add_signed(Duration::seconds(settings.application.session_timeout))
|
||||
.expect("Session timeout specified in configuration is too great!");
|
||||
let cookie = storage.add(expires, user);
|
||||
let cookie = storage.add(expires, user, None);
|
||||
cookies.add_private(Cookie::new("session", cookie.id))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
use crate::database::controller::members::get_member_by_uuid;
|
||||
use crate::database::model::users::User;
|
||||
use crate::helper::session_cookies::model::{SessionCookie, SessionCookieStorage};
|
||||
use crate::helper::settings::Settings;
|
||||
use rocket::http::Cookies;
|
||||
use rocket::http::Status;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::State;
|
||||
use rocket_contrib::templates::Template;
|
||||
use std::error::Error;
|
||||
|
||||
#[get("/loginselect")]
|
||||
pub fn login_select_get(
|
||||
user: User,
|
||||
cookie_storage: State<SessionCookieStorage>,
|
||||
settings: State<Settings>,
|
||||
mut cookies: Cookies,
|
||||
) -> Result<Template, Redirect> {
|
||||
let session_id = match cookies.get_private("session") {
|
||||
Some(session_id) => session_id.clone(),
|
||||
None => {
|
||||
trace!("Couldn't find session id while login_select");
|
||||
return Err(Redirect::to("/?error=unauthorized"));
|
||||
}
|
||||
};
|
||||
let mut cookie: SessionCookie = match cookie_storage.get(session_id.value().to_string()) {
|
||||
Ok(cookie) => cookie,
|
||||
Err(e) => {
|
||||
trace!(
|
||||
"Couldn't find session cookie for session_id: {} while login_select: {}",
|
||||
session_id,
|
||||
e.to_string()
|
||||
);
|
||||
return Err(Redirect::to("/?error=unauthorized"));
|
||||
}
|
||||
};
|
||||
|
||||
let members = get_member_by_uuid(user.id, &settings);
|
||||
trace!("Found {} members for user uuid {}", members.len(), user.id);
|
||||
|
||||
//TODO: Add selection screen when there is more then 1 member available.
|
||||
match members.len() {
|
||||
1 => {
|
||||
cookie.member = Some(members[0].clone());
|
||||
cookie_storage.update(cookie);
|
||||
return Err(Redirect::to("/portal/"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Err(Redirect::to("/?error=notimplemented"));
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod login_select_get;
|
||||
pub mod welcome_get;
|
||||
pub mod welcome_post;
|
||||
|
|
|
@ -11,6 +11,10 @@ pub fn welcome_get(error: Option<String>) -> Result<Template, Status> {
|
|||
AlertClass::Warning,
|
||||
"Session invalid or expired. Please log in again.".to_string(),
|
||||
)),
|
||||
"notimplemented" => Some(Alert::new(
|
||||
AlertClass::Danger,
|
||||
"Error: Feature not implemented yet.".to_string(),
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
None => None,
|
||||
|
|
|
@ -29,7 +29,7 @@ pub fn welcome_post(
|
|||
let alert: Option<Alert> = match user {
|
||||
Some(user) => {
|
||||
add_session_cookie(user, &settings, cookieStorage, cookies);
|
||||
return Ok(Redirect::to("/portal"));
|
||||
return Ok(Redirect::to("/loginselect"));
|
||||
}
|
||||
None => Some(Alert::new(
|
||||
AlertClass::Danger,
|
||||
|
|
|
@ -176,7 +176,6 @@ joinable!(members -> users (users_id));
|
|||
joinable!(members_roles -> entities (member_id));
|
||||
joinable!(members_roles -> roles (role_id));
|
||||
joinable!(roles_permissions -> permissions (permission_id));
|
||||
joinable!(roles_permissions -> roles (role_id));
|
||||
joinable!(vehicles -> entities (entity_id));
|
||||
joinable!(vehicles -> vehicle_categories (entity_id));
|
||||
|
||||
|
|
Loading…
Reference in New Issue