Browse Source

Initial organizations functionality: Creating orgs and inviting users

pull/7/head
Daniel García 7 years ago
parent
commit
4093bf92fe
  1. 4
      migrations/2018-02-17-205753_create_collections_and_orgs/up.sql
  2. 16
      src/api/core/accounts.rs
  3. 12
      src/api/core/ciphers.rs
  4. 9
      src/api/core/mod.rs
  5. 161
      src/api/core/organizations.rs
  6. 5
      src/api/identity.rs
  7. 4
      src/auth.rs
  8. 5
      src/db/models/collection.rs
  9. 16
      src/db/models/device.rs
  10. 8
      src/db/models/mod.rs
  11. 116
      src/db/models/org/organization.rs
  12. 214
      src/db/models/organization.rs
  13. 24
      src/db/models/user.rs
  14. 4
      src/db/schema.rs

4
migrations/2018-02-17-205753_create_collections_and_orgs/up.sql

@ -18,12 +18,14 @@ CREATE TABLE users_collections (
); );
CREATE TABLE users_organizations ( CREATE TABLE users_organizations (
uuid TEXT NOT NULL PRIMARY KEY,
user_uuid TEXT NOT NULL REFERENCES users (uuid), user_uuid TEXT NOT NULL REFERENCES users (uuid),
org_uuid TEXT NOT NULL REFERENCES organizations (uuid), org_uuid TEXT NOT NULL REFERENCES organizations (uuid),
access_all BOOLEAN NOT NULL,
key TEXT NOT NULL, key TEXT NOT NULL,
status INTEGER NOT NULL, status INTEGER NOT NULL,
type INTEGER NOT NULL, type INTEGER NOT NULL,
PRIMARY KEY (user_uuid, org_uuid) UNIQUE (user_uuid, org_uuid)
); );

16
src/api/core/accounts.rs

@ -60,8 +60,18 @@ fn register(data: Json<RegisterData>, conn: DbConn) -> EmptyResult {
} }
#[get("/accounts/profile")] #[get("/accounts/profile")]
fn profile(headers: Headers) -> JsonResult { fn profile(headers: Headers, conn: DbConn) -> JsonResult {
Ok(Json(headers.user.to_json())) Ok(Json(headers.user.to_json(&conn)))
}
#[get("/users/<uuid>/public-key")]
fn get_public_keys(uuid: String, headers: Headers, conn: DbConn) -> JsonResult {
let user = match User::find_by_uuid(&uuid, &conn) {
Some(user) => user,
None => err!("User doesn't exist")
};
Ok(Json(json!(user.public_key)))
} }
#[post("/accounts/keys", data = "<data>")] #[post("/accounts/keys", data = "<data>")]
@ -75,7 +85,7 @@ fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult
user.save(&conn); user.save(&conn);
Ok(Json(user.to_json())) Ok(Json(user.to_json(&conn)))
} }
#[derive(Deserialize)] #[derive(Deserialize)]

12
src/api/core/ciphers.rs

@ -23,7 +23,7 @@ use CONFIG;
#[get("/sync")] #[get("/sync")]
fn sync(headers: Headers, conn: DbConn) -> JsonResult { fn sync(headers: Headers, conn: DbConn) -> JsonResult {
let user_json = headers.user.to_json(); let user_json = headers.user.to_json(&conn);
let folders = Folder::find_by_user(&headers.user.uuid, &conn); let folders = Folder::find_by_user(&headers.user.uuid, &conn);
let folders_json: Vec<Value> = folders.iter().map(|c| c.to_json()).collect(); let folders_json: Vec<Value> = folders.iter().map(|c| c.to_json()).collect();
@ -188,7 +188,6 @@ fn copy_values(from: &Value, to: &mut Value) {
for (key, val) in map { for (key, val) in map {
copy_values(val, &mut to[util::upcase_first(key)]); copy_values(val, &mut to[util::upcase_first(key)]);
} }
} else if let Some(array) = from.as_array() { } else if let Some(array) = from.as_array() {
// Initialize array with null values // Initialize array with null values
*to = json!(vec![Value::Null; array.len()]); *to = json!(vec![Value::Null; array.len()]);
@ -196,7 +195,6 @@ fn copy_values(from: &Value, to: &mut Value) {
for (index, val) in array.iter().enumerate() { for (index, val) in array.iter().enumerate() {
copy_values(val, &mut to[index]); copy_values(val, &mut to[index]);
} }
} else { } else {
*to = from.clone(); *to = from.clone();
} }
@ -375,7 +373,7 @@ fn delete_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) ->
let uuids = match data.get("ids") { let uuids = match data.get("ids") {
Some(ids) => match ids.as_array() { Some(ids) => match ids.as_array() {
Some(ids) => ids.iter().filter_map(|uuid| {uuid.as_str()}), Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }),
None => err!("Posted ids field is not an array") None => err!("Posted ids field is not an array")
}, },
None => err!("Request missing ids field") None => err!("Request missing ids field")
@ -405,16 +403,16 @@ fn move_cipher_selected(data: Json<Value>, headers: Headers, conn: DbConn) -> Em
} }
None => err!("Folder doesn't exist") None => err!("Folder doesn't exist")
} }
}, }
None => err!("Folder id provided in wrong format") None => err!("Folder id provided in wrong format")
} }
}, }
None => None None => None
}; };
let uuids = match data.get("ids") { let uuids = match data.get("ids") {
Some(ids) => match ids.as_array() { Some(ids) => match ids.as_array() {
Some(ids) => ids.iter().filter_map(|uuid| {uuid.as_str()}), Some(ids) => ids.iter().filter_map(|uuid| { uuid.as_str() }),
None => err!("Posted ids field is not an array") None => err!("Posted ids field is not an array")
}, },
None => err!("Request missing ids field") None => err!("Request missing ids field")

9
src/api/core/mod.rs

@ -14,6 +14,7 @@ pub fn routes() -> Vec<Route> {
routes![ routes![
register, register,
profile, profile,
get_public_keys,
post_keys, post_keys,
post_password, post_password,
post_sstamp, post_sstamp,
@ -53,7 +54,15 @@ pub fn routes() -> Vec<Route> {
activate_authenticator, activate_authenticator,
disable_authenticator, disable_authenticator,
create_organization,
get_user_collections, get_user_collections,
get_org_collections,
get_org_details,
get_org_users,
get_collection_users,
send_invite,
confirm_invite,
delete_user,
clear_device_token, clear_device_token,
put_device_token, put_device_token,

161
src/api/core/organizations.rs

@ -8,22 +8,34 @@ use db::models::*;
use api::{JsonResult, EmptyResult}; use api::{JsonResult, EmptyResult};
use auth::Headers; use auth::Headers;
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct OrgData {
billingEmail: String,
collectionName: String,
key: String,
name: String,
planType: String,
}
#[post("/organizations", data = "<data>")] #[post("/organizations", data = "<data>")]
fn create_organization(headers: Headers, data: Json<Value>, conn: DbConn) -> JsonResult { fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn) -> JsonResult {
/* let data: OrgData = data.into_inner();
Data is a JSON Object with the following entries
billingEmail <email> let mut org = Organization::new(data.name, data.billingEmail);
collectionName <encrypted_collection_name> let mut user_org = UserOrganization::new(
key <key> headers.user.uuid, org.uuid.clone());
name <unencrypted_name>
planType free user_org.key = data.key;
*/ user_org.access_all = true;
user_org.type_ = UserOrgType::Owner as i32;
user_org.status = UserOrgStatus::Confirmed as i32;
// We need to add the following key to the users jwt claims org.save(&conn);
// orgowner: "<org-id>" user_org.save(&conn);
// This function returns organization.to_json(); Ok(Json(org.to_json()))
err!("Not implemented")
} }
@ -50,6 +62,35 @@ fn get_org_collections(org_id: String, headers: Headers, conn: DbConn) -> JsonRe
}))) })))
} }
#[derive(FromForm)]
#[allow(non_snake_case)]
struct OrgIdData {
organizationId: String
}
#[get("/ciphers/organization-details?<data>")]
fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult {
// Get list of ciphers in org?
Ok(Json(json!({
"Data": [],
"Object": "list"
})))
}
#[get("/organizations/<org_id>/users")]
fn get_org_users(org_id: String, headers: Headers, conn: DbConn) -> JsonResult {
// TODO Check if user in org
let users = UserOrganization::find_by_org(&org_id, &conn);
let users_json: Vec<Value> = users.iter().map(|c| c.to_json_details(&conn)).collect();
Ok(Json(json!({
"Data": users_json,
"Object": "list"
})))
}
#[get("/organizations/<org_id>/collections/<coll_id>/users")] #[get("/organizations/<org_id>/collections/<coll_id>/users")]
fn get_collection_users(org_id: String, coll_id: String, headers: Headers, conn: DbConn) -> JsonResult { fn get_collection_users(org_id: String, coll_id: String, headers: Headers, conn: DbConn) -> JsonResult {
@ -78,10 +119,94 @@ fn get_collection_users(org_id: String, coll_id: String, headers: Headers, conn:
}))) })))
} }
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct InviteCollectionData {
id: String,
readOnly: bool,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct InviteData {
emails: Vec<String>,
#[serde(rename = "type")]
type_: String,
collections: Vec<InviteCollectionData>,
accessAll: bool,
//******************************************************************************************** }
/*
We need to modify 'GET /api/profile' to return the users organizations, instead of [] #[post("/organizations/<org_id>/users/invite", data = "<data>")]
fn send_invite(org_id: String, data: Json<InviteData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: InviteData = data.into_inner();
// TODO Check that user is in org and admin or more
for user_opt in data.emails.iter().map(|email| User::find_by_mail(email, &conn)) {
match user_opt {
None => err!("User email does not exist"),
Some(user) => {
// TODO Check that user is not already in org
let mut user_org = UserOrganization::new(
user.uuid, org_id.clone());
if data.accessAll {
user_org.access_all = data.accessAll;
} else {
err!("Select collections unimplemented")
// TODO create Users_collections
}
The elements from that array come from organization.to_json_profile() user_org.type_ = match data.type_.as_ref() {
*/ "Owner" => UserOrgType::Owner,
"Admin" => UserOrgType::Admin,
"User" => UserOrgType::User,
_ => err!("Invalid type")
} as i32;
user_org.save(&conn);
}
}
}
Ok(())
}
#[post("/organizations/<org_id>/users/<user_id>/confirm", data = "<data>")]
fn confirm_invite(org_id: String, user_id: String, data: Json<Value>, headers: Headers, conn: DbConn) -> EmptyResult {
// TODO Check that user is in org and admin or more
let mut user_org = match UserOrganization::find_by_user_and_org(
&user_id, &org_id, &conn) {
Some(user_org) => user_org,
None => err!("Can't find user")
};
if user_org.status != UserOrgStatus::Accepted as i32 {
err!("User in invalid state")
}
user_org.status = UserOrgStatus::Confirmed as i32;
user_org.key = match data["key"].as_str() {
Some(key) => key.to_string(),
None => err!("Invalid key provided")
};
user_org.save(&conn);
Ok(())
}
#[post("/organizations/<org_id>/users/<user_id>/delete")]
fn delete_user(org_id: String, user_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
// TODO Check that user is in org and admin or more
// TODO To delete a user you need either:
// - To be yourself
// - To be of a superior type (ex. Owner can delete Admin and User, Admin can delete User)
// Delete users_organizations and users_collections from this org
unimplemented!();
}

5
src/api/identity.rs

@ -19,7 +19,6 @@ pub fn routes() -> Vec<Route> {
#[post("/connect/token", data = "<connect_data>")] #[post("/connect/token", data = "<connect_data>")]
fn login(connect_data: Form<ConnectData>, device_type: DeviceType, conn: DbConn) -> JsonResult { fn login(connect_data: Form<ConnectData>, device_type: DeviceType, conn: DbConn) -> JsonResult {
let data = connect_data.get(); let data = connect_data.get();
println!("{:#?}", data);
let mut device = match data.grant_type { let mut device = match data.grant_type {
GrantType::RefreshToken => { GrantType::RefreshToken => {
@ -98,7 +97,9 @@ fn login(connect_data: Form<ConnectData>, device_type: DeviceType, conn: DbConn)
}; };
let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap();
let (access_token, expires_in) = device.refresh_tokens(&user); let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
let (access_token, expires_in) = device.refresh_tokens(&user, orgs);
device.save(&conn); device.save(&conn);
Ok(Json(json!({ Ok(Json(json!({

4
src/auth.rs

@ -72,6 +72,10 @@ pub struct JWTClaims {
pub email: String, pub email: String,
pub email_verified: bool, pub email_verified: bool,
pub orgowner: Vec<String>,
pub orgadmin: Vec<String>,
pub orguser: Vec<String>,
// user security_stamp // user security_stamp
pub sstamp: String, pub sstamp: String,
// device uuid // device uuid

5
src/db/models/org/collection.rs → src/db/models/collection.rs

@ -1,4 +1,3 @@
use chrono::{NaiveDateTime, Utc};
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use uuid::Uuid; use uuid::Uuid;
@ -18,8 +17,6 @@ pub struct Collection {
/// Local methods /// Local methods
impl Collection { impl Collection {
pub fn new(org_uuid: String, name: String) -> Self { pub fn new(org_uuid: String, name: String) -> Self {
let now = Utc::now().naive_utc();
Self { Self {
uuid: Uuid::new_v4().to_string(), uuid: Uuid::new_v4().to_string(),
@ -46,8 +43,6 @@ use db::schema::collections;
/// Database methods /// Database methods
impl Collection { impl Collection {
pub fn save(&mut self, conn: &DbConn) -> bool { pub fn save(&mut self, conn: &DbConn) -> bool {
self.updated_at = Utc::now().naive_utc();
match diesel::replace_into(collections::table) match diesel::replace_into(collections::table)
.values(&*self) .values(&*self)
.execute(&**conn) { .execute(&**conn) {

16
src/db/models/device.rs

@ -40,7 +40,7 @@ impl Device {
} }
} }
pub fn refresh_tokens(&mut self, user: &super::User) -> (String, i64) { pub fn refresh_tokens(&mut self, user: &super::User, orgs: Vec<super::UserOrganization>) -> (String, i64) {
// If there is no refresh token, we create one // If there is no refresh token, we create one
if self.refresh_token.is_empty() { if self.refresh_token.is_empty() {
use data_encoding::BASE64URL; use data_encoding::BASE64URL;
@ -51,9 +51,14 @@ impl Device {
// Update the expiration of the device and the last update date // Update the expiration of the device and the last update date
let time_now = Utc::now().naive_utc(); let time_now = Utc::now().naive_utc();
self.updated_at = time_now; self.updated_at = time_now;
let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect();
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect();
let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();
// Create the JWT claims struct, to send to the client // Create the JWT claims struct, to send to the client
use auth::{encode_jwt, JWTClaims, DEFAULT_VALIDITY, JWT_ISSUER}; use auth::{encode_jwt, JWTClaims, DEFAULT_VALIDITY, JWT_ISSUER};
let claims = JWTClaims { let claims = JWTClaims {
@ -61,16 +66,23 @@ impl Device {
exp: (time_now + *DEFAULT_VALIDITY).timestamp(), exp: (time_now + *DEFAULT_VALIDITY).timestamp(),
iss: JWT_ISSUER.to_string(), iss: JWT_ISSUER.to_string(),
sub: user.uuid.to_string(), sub: user.uuid.to_string(),
premium: true, premium: true,
name: user.name.to_string(), name: user.name.to_string(),
email: user.email.to_string(), email: user.email.to_string(),
email_verified: true, email_verified: true,
orgowner,
orgadmin,
orguser,
sstamp: user.security_stamp.to_string(), sstamp: user.security_stamp.to_string(),
device: self.uuid.to_string(), device: self.uuid.to_string(),
scope: vec!["api".into(), "offline_access".into()], scope: vec!["api".into(), "offline_access".into()],
amr: vec!["Application".into()], amr: vec!["Application".into()],
}; };
(encode_jwt(&claims), DEFAULT_VALIDITY.num_seconds()) (encode_jwt(&claims), DEFAULT_VALIDITY.num_seconds())
} }
} }

8
src/db/models/mod.rs

@ -4,8 +4,16 @@ mod device;
mod folder; mod folder;
mod user; mod user;
mod collection;
mod organization;
pub use self::attachment::Attachment; pub use self::attachment::Attachment;
pub use self::cipher::Cipher; pub use self::cipher::Cipher;
pub use self::device::Device; pub use self::device::Device;
pub use self::folder::Folder; pub use self::folder::Folder;
pub use self::user::User; pub use self::user::User;
pub use self::collection::Collection;
pub use self::organization::Organization;
pub use self::organization::{UserOrganization, UserOrgStatus, UserOrgType};

116
src/db/models/org/organization.rs

@ -1,116 +0,0 @@
use chrono::{NaiveDateTime, Utc};
use serde_json::Value as JsonValue;
use uuid::Uuid;
#[derive(Debug, Identifiable, Queryable, Insertable)]
#[table_name = "organizations"]
#[primary_key(uuid)]
pub struct Organization {
pub uuid: String,
pub name: String,
pub billing_email: String,
pub key: String,
}
/// Local methods
impl Organization {
pub fn new(name: String, billing_email: String, key: String) -> Self {
let now = Utc::now().naive_utc();
Self {
uuid: Uuid::new_v4().to_string(),
name,
billing_email,
key,
}
}
pub fn to_json(&self) -> JsonValue {
json!({
"Id": self.uuid,
"Name": self.name,
"BusinessName": null,
"BusinessAddress1": null,
"BusinessAddress2": null,
"BusinessAddress3": null,
"BusinessCountry": null,
"BusinessTaxNumber": null,
"BillingEmail":self.billing_email,
"Plan": "Free",
"PlanType": 0, // Free plan
"Seats": 10,
"MaxCollections": 10,
"UseGroups": false,
"UseDirectory": false,
"UseEvents": false,
"UseTotp": false,
"Object": "organization",
})
}
pub fn to_json_profile(&self) -> JsonValue {
json!({
"Id": self.uuid,
"Name": self.name,
"Seats": 10,
"MaxCollections": 10,
"UseGroups": false,
"UseDirectory": false,
"UseEvents": false,
"UseTotp": false,
"MaxStorageGb": null,
// These are probably per user
"Key": self.key,
"Status": 2, // Confirmed
"Type": 0, // Owner
"Enabled": true,
"Object": "profileOrganization",
})
}
}
use diesel;
use diesel::prelude::*;
use db::DbConn;
use db::schema::organizations;
/// Database methods
impl Organization {
pub fn save(&mut self, conn: &DbConn) -> bool {
self.updated_at = Utc::now().naive_utc();
match diesel::replace_into(organizations::table)
.values(&*self)
.execute(&**conn) {
Ok(1) => true, // One row inserted
_ => false,
}
}
pub fn delete(self, conn: &DbConn) -> bool {
match diesel::delete(organizations::table.filter(
organizations::uuid.eq(self.uuid)))
.execute(&**conn) {
Ok(1) => true, // One row deleted
_ => false,
}
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
organizations::table
.filter(organizations::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
}
}

214
src/db/models/organization.rs

@ -0,0 +1,214 @@
use serde_json::Value as JsonValue;
use uuid::Uuid;
#[derive(Debug, Identifiable, Queryable, Insertable)]
#[table_name = "organizations"]
#[primary_key(uuid)]
pub struct Organization {
pub uuid: String,
pub name: String,
pub billing_email: String,
}
#[derive(Debug, Identifiable, Queryable, Insertable)]
#[table_name = "users_organizations"]
#[primary_key(uuid)]
pub struct UserOrganization {
pub uuid: String,
pub user_uuid: String,
pub org_uuid: String,
pub access_all: bool,
pub key: String,
pub status: i32,
pub type_: i32,
}
pub enum UserOrgStatus {
Invited = 0,
Accepted = 1,
Confirmed = 2,
}
pub enum UserOrgType {
Owner = 0,
Admin = 1,
User = 2,
}
/// Local methods
impl Organization {
pub fn new(name: String, billing_email: String) -> Self {
Self {
uuid: Uuid::new_v4().to_string(),
name,
billing_email,
}
}
pub fn to_json(&self) -> JsonValue {
json!({
"Id": self.uuid,
"Name": self.name,
"Seats": 10,
"MaxCollections": 10,
"Use2fa": false,
"UseDirectory": false,
"UseEvents": false,
"UseGroups": false,
"UseTotp": false,
"BusinessName": null,
"BusinessAddress1": null,
"BusinessAddress2": null,
"BusinessAddress3": null,
"BusinessCountry": null,
"BusinessTaxNumber": null,
"BillingEmail": self.billing_email,
"Plan": "Free",
"PlanType": 0, // Free plan
"Object": "organization",
})
}
}
impl UserOrganization {
pub fn new(user_uuid: String, org_uuid: String) -> Self {
Self {
uuid: Uuid::new_v4().to_string(),
user_uuid,
org_uuid,
access_all: false,
key: String::new(),
status: UserOrgStatus::Accepted as i32,
type_: UserOrgType::User as i32,
}
}
}
use diesel;
use diesel::prelude::*;
use db::DbConn;
use db::schema::organizations;
use db::schema::users_organizations;
/// Database methods
impl Organization {
pub fn save(&mut self, conn: &DbConn) -> bool {
match diesel::replace_into(organizations::table)
.values(&*self)
.execute(&**conn) {
Ok(1) => true, // One row inserted
_ => false,
}
}
pub fn delete(self, conn: &DbConn) -> bool {
match diesel::delete(organizations::table.filter(
organizations::uuid.eq(self.uuid)))
.execute(&**conn) {
Ok(1) => true, // One row deleted
_ => false,
}
}
pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
organizations::table
.filter(organizations::uuid.eq(uuid))
.first::<Self>(&**conn).ok()
}
}
impl UserOrganization {
pub fn to_json(&self, conn: &DbConn) -> JsonValue {
let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap();
json!({
"Id": self.org_uuid,
"Name": org.name,
"Seats": 10,
"MaxCollections": 10,
"Use2fa": false,
"UseDirectory": false,
"UseEvents": false,
"UseGroups": false,
"UseTotp": false,
"MaxStorageGb": null,
// These are per user
"Key": self.key,
"Status": self.status,
"Type": self.type_,
"Enabled": true,
"Object": "profileOrganization",
})
}
pub fn to_json_details(&self, conn: &DbConn) -> JsonValue {
use super::User;
let user = User::find_by_uuid(&self.user_uuid, conn).unwrap();
json!({
"Id": self.uuid,
"UserId": user.uuid,
"Name": user.name,
"Email": user.email,
"Status": self.status,
"Type": self.type_,
"AccessAll": true,
"Object": "organizationUserUserDetails",
})
}
pub fn save(&mut self, conn: &DbConn) -> bool {
match diesel::replace_into(users_organizations::table)
.values(&*self)
.execute(&**conn) {
Ok(1) => true, // One row inserted
_ => false,
}
}
pub fn delete(self, conn: &DbConn) -> bool {
match diesel::delete(users_organizations::table.filter(
users_organizations::uuid.eq(self.uuid)))
.execute(&**conn) {
Ok(1) => true, // One row deleted
_ => false,
}
}
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.load::<Self>(&**conn).expect("Error loading user organizations")
}
pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
.load::<Self>(&**conn).expect("Error loading user organizations")
}
pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::org_uuid.eq(org_uuid))
.first::<Self>(&**conn).ok()
}
}

24
src/db/models/user.rs

@ -115,8 +115,21 @@ impl User {
true true
} }
} }
}
use diesel;
use diesel::prelude::*;
use db::DbConn;
use db::schema::users;
/// Database methods
impl User {
pub fn to_json(&self, conn: &DbConn) -> JsonValue {
use super::UserOrganization;
let orgs = UserOrganization::find_by_user(&self.uuid, conn);
let orgs_json: Vec<JsonValue> = orgs.iter().map(|c| c.to_json(&conn)).collect();
pub fn to_json(&self) -> JsonValue {
json!({ json!({
"Id": self.uuid, "Id": self.uuid,
"Name": self.name, "Name": self.name,
@ -129,19 +142,12 @@ impl User {
"Key": self.key, "Key": self.key,
"PrivateKey": self.private_key, "PrivateKey": self.private_key,
"SecurityStamp": self.security_stamp, "SecurityStamp": self.security_stamp,
"Organizations": [], "Organizations": orgs_json,
"Object": "profile" "Object": "profile"
}) })
} }
}
use diesel;
use diesel::prelude::*;
use db::DbConn;
use db::schema::users;
/// Database methods
impl User {
pub fn save(&mut self, conn: &DbConn) -> bool { pub fn save(&mut self, conn: &DbConn) -> bool {
self.updated_at = Utc::now().naive_utc(); self.updated_at = Utc::now().naive_utc();

4
src/db/schema.rs

@ -95,9 +95,11 @@ table! {
} }
table! { table! {
users_organizations (user_uuid, org_uuid) { users_organizations (uuid) {
uuid -> Text,
user_uuid -> Text, user_uuid -> Text,
org_uuid -> Text, org_uuid -> Text,
access_all -> Bool,
key -> Text, key -> Text,
status -> Integer, status -> Integer,
#[sql_name = "type"] #[sql_name = "type"]

Loading…
Cancel
Save