فهرست منبع

Initial version of policies

Daniel García 5 سال پیش
والد
کامیت
3fa78e7bb1

+ 1 - 0
migrations/mysql/2020-03-13-205045_add_policy_table/down.sql

@@ -0,0 +1 @@
+DROP TABLE org_policies;

+ 9 - 0
migrations/mysql/2020-03-13-205045_add_policy_table/up.sql

@@ -0,0 +1,9 @@
+CREATE TABLE org_policies (
+  uuid      CHAR(36) NOT NULL PRIMARY KEY,
+  org_uuid  CHAR(36) NOT NULL REFERENCES organizations (uuid),
+  atype     INTEGER  NOT NULL,
+  enabled   BOOLEAN  NOT NULL,
+  data      TEXT     NOT NULL,
+
+  UNIQUE (org_uuid, atype)
+);

+ 1 - 0
migrations/postgresql/2020-03-13-205045_add_policy_table/down.sql

@@ -0,0 +1 @@
+DROP TABLE org_policies;

+ 9 - 0
migrations/postgresql/2020-03-13-205045_add_policy_table/up.sql

@@ -0,0 +1,9 @@
+CREATE TABLE org_policies (
+  uuid      CHAR(36) NOT NULL PRIMARY KEY,
+  org_uuid  CHAR(36) NOT NULL REFERENCES organizations (uuid),
+  atype     INTEGER  NOT NULL,
+  enabled   BOOLEAN  NOT NULL,
+  data      TEXT     NOT NULL,
+  
+  UNIQUE (org_uuid, atype)
+);

+ 1 - 0
migrations/sqlite/2020-03-13-205045_add_policy_table/down.sql

@@ -0,0 +1 @@
+DROP TABLE org_policies;

+ 9 - 0
migrations/sqlite/2020-03-13-205045_add_policy_table/up.sql

@@ -0,0 +1,9 @@
+CREATE TABLE org_policies (
+  uuid      TEXT     NOT NULL PRIMARY KEY,
+  org_uuid  TEXT     NOT NULL REFERENCES organizations (uuid),
+  atype     INTEGER  NOT NULL,
+  enabled   BOOLEAN  NOT NULL,
+  data      TEXT     NOT NULL,
+
+  UNIQUE (org_uuid, atype)
+);

+ 5 - 1
src/api/core/ciphers.rs

@@ -79,6 +79,9 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
     let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn);
     let collections_json: Vec<Value> = collections.iter().map(Collection::to_json).collect();
 
+    let policies = OrgPolicy::find_by_user(&headers.user.uuid, &conn);
+    let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
+
     let ciphers = Cipher::find_by_user(&headers.user.uuid, &conn);
     let ciphers_json: Vec<Value> = ciphers
         .iter()
@@ -95,6 +98,7 @@ fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> JsonResult {
         "Profile": user_json,
         "Folders": folders_json,
         "Collections": collections_json,
+        "Policies": policies_json,
         "Ciphers": ciphers_json,
         "Domains": domains_json,
         "Object": "sync"
@@ -648,7 +652,7 @@ fn post_attachment(
     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
         err_discard!("Cipher is not write accessible", data)
     }
-    
+
     let mut params = content_type.params();
     let boundary_pair = params.next().expect("No boundary provided");
     let boundary = boundary_pair.1;

+ 63 - 10
src/api/core/organizations.rs

@@ -2,6 +2,7 @@ use rocket::request::Form;
 use rocket::Route;
 use rocket_contrib::json::Json;
 use serde_json::Value;
+use num_traits::FromPrimitive;
 
 use crate::api::{
     EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, Notify, NumberOrString, PasswordData, UpdateType,
@@ -45,6 +46,9 @@ pub fn routes() -> Vec<Route> {
         delete_user,
         post_delete_user,
         post_org_import,
+        list_policies,
+        get_policy,
+        put_policy,
     ]
 }
 
@@ -830,22 +834,13 @@ struct RelationsData {
 fn post_org_import(
     query: Form<OrgIdData>,
     data: JsonUpcase<ImportData>,
-    headers: Headers,
+    headers: AdminHeaders,
     conn: DbConn,
     nt: Notify,
 ) -> EmptyResult {
     let data: ImportData = data.into_inner().data;
     let org_id = query.into_inner().organization_id;
 
-    let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
-        Some(user) => user,
-        None => err!("User is not part of the organization"),
-    };
-
-    if org_user.atype < UserOrgType::Admin {
-        err!("Only admins or owners can import into an organization")
-    }
-
     // Read and create the collections
     let collections: Vec<_> = data
         .Collections
@@ -866,6 +861,8 @@ fn post_org_import(
         relations.push((relation.Key, relation.Value));
     }
 
+    let headers: Headers = headers.into();
+
     // Read and create the ciphers
     let ciphers: Vec<_> = data
         .Ciphers
@@ -901,3 +898,59 @@ fn post_org_import(
     let mut user = headers.user;
     user.update_revision(&conn)
 }
+
+#[get("/organizations/<org_id>/policies")]
+fn list_policies(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
+    let policies = OrgPolicy::find_by_org(&org_id, &conn);
+    let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
+
+    Ok(Json(json!({
+        "Data": policies_json,
+        "Object": "list",
+        "ContinuationToken": null
+    })))
+}
+
+#[get("/organizations/<org_id>/policies/<pol_type>")]
+fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
+    let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
+        Some(pt) => pt,
+        None => err!("Invalid policy type"),
+    };
+
+    let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
+        Some(p) => p,
+        None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
+    };
+
+    Ok(Json(policy.to_json()))
+}
+
+#[derive(Deserialize)]
+struct PolicyData {
+    enabled: bool,
+    #[serde(rename = "type")]
+    _type: i32,
+    data: Value,
+}
+
+#[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")]
+fn put_policy(org_id: String, pol_type: i32, data: Json<PolicyData>, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
+    let data: PolicyData = data.into_inner();
+
+    let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
+        Some(pt) => pt,
+        None => err!("Invalid policy type"),
+    };
+
+    let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) {
+        Some(p) => p,
+        None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
+    };
+
+    policy.enabled = data.enabled;
+    policy.data = serde_json::to_string(&data.data)?;
+    policy.save(&conn)?;
+
+    Ok(Json(policy.to_json()))
+}

+ 11 - 0
src/auth.rs

@@ -4,6 +4,7 @@
 use crate::util::read_file;
 use chrono::{Duration, Utc};
 use once_cell::sync::Lazy;
+use num_traits::FromPrimitive;
 
 use jsonwebtoken::{self, Algorithm, Header};
 use serde::de::DeserializeOwned;
@@ -385,6 +386,16 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
     }
 }
 
+impl Into<Headers> for AdminHeaders {    
+    fn into(self) -> Headers { 
+        Headers {
+            host: self.host,
+            device: self.device,
+            user: self.user
+        }
+     }
+}
+
 pub struct OwnerHeaders {
     pub host: String,
     pub device: Device,

+ 2 - 0
src/db/models/mod.rs

@@ -7,6 +7,7 @@ mod user;
 mod collection;
 mod organization;
 mod two_factor;
+mod org_policy;
 
 pub use self::attachment::Attachment;
 pub use self::cipher::Cipher;
@@ -17,3 +18,4 @@ pub use self::organization::Organization;
 pub use self::organization::{UserOrgStatus, UserOrgType, UserOrganization};
 pub use self::two_factor::{TwoFactor, TwoFactorType};
 pub use self::user::{Invitation, User};
+pub use self::org_policy::{OrgPolicy, OrgPolicyType};

+ 142 - 0
src/db/models/org_policy.rs

@@ -0,0 +1,142 @@
+use diesel;
+use diesel::prelude::*;
+use serde_json::Value;
+
+use crate::api::EmptyResult;
+use crate::db::schema::org_policies;
+use crate::db::DbConn;
+use crate::error::MapResult;
+
+use super::Organization;
+
+#[derive(Debug, Identifiable, Queryable, Insertable, Associations, AsChangeset)]
+#[table_name = "org_policies"]
+#[belongs_to(Organization, foreign_key = "org_uuid")]
+#[primary_key(uuid)]
+pub struct OrgPolicy {
+    pub uuid: String,
+    pub org_uuid: String,
+    pub atype: i32,
+    pub enabled: bool,
+    pub data: String,
+}
+
+#[allow(dead_code)]
+#[derive(FromPrimitive)]
+pub enum OrgPolicyType {
+    TwoFactorAuthentication = 0,
+    MasterPassword = 1,
+    PasswordGenerator = 2,
+}
+
+/// Local methods
+impl OrgPolicy {
+    pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self {
+        Self {
+            uuid: crate::util::get_uuid(),
+            org_uuid,
+            atype: atype as i32,
+            enabled: false,
+            data,
+        }
+    }
+
+    pub fn to_json(&self) -> Value {
+        let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
+        json!({
+            "Id": self.uuid,
+            "OrganizationId": self.org_uuid,
+            "Type": self.atype,
+            "Data": data_json,
+            "Enabled": self.enabled,
+            "Object": "policy",
+        })
+    }
+}
+
+/// Database methods
+impl OrgPolicy {
+    #[cfg(feature = "postgresql")]
+    pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
+        // We need to make sure we're not going to violate the unique constraint on org_uuid and atype.
+        // This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
+        // not support multiple constraints on ON CONFLICT clauses.
+        diesel::delete(
+            org_policies::table
+                .filter(org_policies::org_uuid.eq(&self.org_uuid))
+                .filter(org_policies::atype.eq(&self.atype)),
+        )
+        .execute(&**conn)
+        .map_res("Error deleting org_policy for insert")?;
+
+        diesel::insert_into(org_policies::table)
+            .values(self)
+            .on_conflict(org_policies::uuid)
+            .do_update()
+            .set(self)
+            .execute(&**conn)
+            .map_res("Error saving org_policy")
+    }
+
+    #[cfg(not(feature = "postgresql"))]
+    pub fn save(&mut self, conn: &DbConn) -> EmptyResult {
+        diesel::replace_into(org_policies::table)
+            .values(&*self)
+            .execute(&**conn)
+            .map_res("Error saving org_policy")
+    }
+
+    pub fn delete(self, conn: &DbConn) -> EmptyResult {
+        diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid)))
+            .execute(&**conn)
+            .map_res("Error deleting org_policy")
+    }
+
+    pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
+        org_policies::table
+            .filter(org_policies::uuid.eq(uuid))
+            .first::<Self>(&**conn)
+            .ok()
+    }
+
+    pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
+        org_policies::table
+            .filter(org_policies::org_uuid.eq(org_uuid))
+            .load::<Self>(&**conn)
+            .expect("Error loading org_policy")
+    }
+
+    pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
+        use crate::db::schema::users_organizations;
+
+        org_policies::table
+            .left_join(
+                users_organizations::table.on(
+                    users_organizations::org_uuid.eq(org_policies::org_uuid)
+                        .and(users_organizations::user_uuid.eq(user_uuid)))
+            )
+            .select(org_policies::all_columns)
+            .load::<Self>(&**conn)
+            .expect("Error loading org_policy")
+    }
+
+    pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> {
+        org_policies::table
+            .filter(org_policies::org_uuid.eq(org_uuid))
+            .filter(org_policies::atype.eq(atype))
+            .first::<Self>(&**conn)
+            .ok()
+    }
+
+    pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
+        diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid)))
+            .execute(&**conn)
+            .map_res("Error deleting org_policy")
+    }
+
+    /*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
+        diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
+            .execute(&**conn)
+            .map_res("Error deleting twofactors")
+    }*/
+}

+ 7 - 12
src/db/models/organization.rs

@@ -1,7 +1,8 @@
 use serde_json::Value;
 use std::cmp::Ordering;
+use num_traits::FromPrimitive;
 
-use super::{CollectionUser, User};
+use super::{CollectionUser, User, OrgPolicy};
 
 #[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset)]
 #[table_name = "organizations"]
@@ -33,6 +34,7 @@ pub enum UserOrgStatus {
 }
 
 #[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(FromPrimitive)]
 pub enum UserOrgType {
     Owner = 0,
     Admin = 1,
@@ -135,16 +137,6 @@ impl UserOrgType {
             _ => None,
         }
     }
-
-    pub fn from_i32(i: i32) -> Option<Self> {
-        match i {
-            0 => Some(UserOrgType::Owner),
-            1 => Some(UserOrgType::Admin),
-            2 => Some(UserOrgType::User),
-            3 => Some(UserOrgType::Manager),
-            _ => None,
-        }
-    }
 }
 
 /// Local methods
@@ -170,6 +162,7 @@ impl Organization {
             "UseEvents": false,
             "UseGroups": false,
             "UseTotp": true,
+            "UsePolicies": true,
 
             "BusinessName": null,
             "BusinessAddress1":	null,
@@ -250,6 +243,7 @@ impl Organization {
         Cipher::delete_all_by_organization(&self.uuid, &conn)?;
         Collection::delete_all_by_organization(&self.uuid, &conn)?;
         UserOrganization::delete_all_by_organization(&self.uuid, &conn)?;
+        OrgPolicy::delete_all_by_organization(&self.uuid, &conn)?;
 
         diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
             .execute(&**conn)
@@ -267,7 +261,7 @@ impl Organization {
 impl UserOrganization {
     pub fn to_json(&self, conn: &DbConn) -> Value {
         let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap();
-
+        
         json!({
             "Id": self.org_uuid,
             "Name": org.name,
@@ -280,6 +274,7 @@ impl UserOrganization {
             "UseEvents": false,
             "UseGroups": false,
             "UseTotp": true,
+            "UsePolicies": true,
 
             "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
 

+ 12 - 0
src/db/schemas/mysql/schema.rs

@@ -77,6 +77,16 @@ table! {
     }
 }
 
+table! {
+    org_policies (uuid) {
+        uuid -> Varchar,
+        org_uuid -> Varchar,
+        atype -> Integer,
+        enabled -> Bool,
+        data -> Text,
+    }
+}
+
 table! {
     organizations (uuid) {
         uuid -> Varchar,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
 joinable!(folders -> users (user_uuid));
 joinable!(folders_ciphers -> ciphers (cipher_uuid));
 joinable!(folders_ciphers -> folders (folder_uuid));
+joinable!(org_policies -> organizations (org_uuid));
 joinable!(twofactor -> users (user_uuid));
 joinable!(users_collections -> collections (collection_uuid));
 joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
     folders,
     folders_ciphers,
     invitations,
+    org_policies,
     organizations,
     twofactor,
     users,

+ 12 - 0
src/db/schemas/postgresql/schema.rs

@@ -77,6 +77,16 @@ table! {
     }
 }
 
+table! {
+    org_policies (uuid) {
+        uuid -> Text,
+        org_uuid -> Text,
+        atype -> Integer,
+        enabled -> Bool,
+        data -> Text,
+    }
+}
+
 table! {
     organizations (uuid) {
         uuid -> Text,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
 joinable!(folders -> users (user_uuid));
 joinable!(folders_ciphers -> ciphers (cipher_uuid));
 joinable!(folders_ciphers -> folders (folder_uuid));
+joinable!(org_policies -> organizations (org_uuid));
 joinable!(twofactor -> users (user_uuid));
 joinable!(users_collections -> collections (collection_uuid));
 joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
     folders,
     folders_ciphers,
     invitations,
+    org_policies,
     organizations,
     twofactor,
     users,

+ 12 - 0
src/db/schemas/sqlite/schema.rs

@@ -77,6 +77,16 @@ table! {
     }
 }
 
+table! {
+    org_policies (uuid) {
+        uuid -> Text,
+        org_uuid -> Text,
+        atype -> Integer,
+        enabled -> Bool,
+        data -> Text,
+    }
+}
+
 table! {
     organizations (uuid) {
         uuid -> Text,
@@ -155,6 +165,7 @@ joinable!(devices -> users (user_uuid));
 joinable!(folders -> users (user_uuid));
 joinable!(folders_ciphers -> ciphers (cipher_uuid));
 joinable!(folders_ciphers -> folders (folder_uuid));
+joinable!(org_policies -> organizations (org_uuid));
 joinable!(twofactor -> users (user_uuid));
 joinable!(users_collections -> collections (collection_uuid));
 joinable!(users_collections -> users (user_uuid));
@@ -170,6 +181,7 @@ allow_tables_to_appear_in_same_query!(
     folders,
     folders_ciphers,
     invitations,
+    org_policies,
     organizations,
     twofactor,
     users,