commit 3fa78e7bb141979d6f6fdfa20aecc70493b80842
parent 70f3ab8ec3d6ccfd8ec8c71c888459de484d9b43
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Sat, 14 Mar 2020 13:22:30 +0100
Initial version of policies
Diffstat:
15 files changed, 298 insertions(+), 23 deletions(-)
diff --git a/migrations/mysql/2020-03-13-205045_add_policy_table/down.sql b/migrations/mysql/2020-03-13-205045_add_policy_table/down.sql
@@ -0,0 +1 @@
+DROP TABLE org_policies;
diff --git a/migrations/mysql/2020-03-13-205045_add_policy_table/up.sql b/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)
+);
diff --git a/migrations/postgresql/2020-03-13-205045_add_policy_table/down.sql b/migrations/postgresql/2020-03-13-205045_add_policy_table/down.sql
@@ -0,0 +1 @@
+DROP TABLE org_policies;
diff --git a/migrations/postgresql/2020-03-13-205045_add_policy_table/up.sql b/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)
+);
diff --git a/migrations/sqlite/2020-03-13-205045_add_policy_table/down.sql b/migrations/sqlite/2020-03-13-205045_add_policy_table/down.sql
@@ -0,0 +1 @@
+DROP TABLE org_policies;
diff --git a/migrations/sqlite/2020-03-13-205045_add_policy_table/up.sql b/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)
+);
diff --git a/src/api/core/ciphers.rs b/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;
diff --git a/src/api/core/organizations.rs b/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()))
+}
+\ No newline at end of file
diff --git a/src/auth.rs b/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,
diff --git a/src/db/models/mod.rs b/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};
+\ No newline at end of file
diff --git a/src/db/models/org_policy.rs b/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")
+ }*/
+}
diff --git a/src/db/models/organization.rs b/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
diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs
@@ -78,6 +78,16 @@ table! {
}
table! {
+ org_policies (uuid) {
+ uuid -> Varchar,
+ org_uuid -> Varchar,
+ atype -> Integer,
+ enabled -> Bool,
+ data -> Text,
+ }
+}
+
+table! {
organizations (uuid) {
uuid -> Varchar,
name -> 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,
diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs
@@ -78,6 +78,16 @@ table! {
}
table! {
+ org_policies (uuid) {
+ uuid -> Text,
+ org_uuid -> Text,
+ atype -> Integer,
+ enabled -> Bool,
+ data -> Text,
+ }
+}
+
+table! {
organizations (uuid) {
uuid -> Text,
name -> 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,
diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs
@@ -78,6 +78,16 @@ table! {
}
table! {
+ org_policies (uuid) {
+ uuid -> Text,
+ org_uuid -> Text,
+ atype -> Integer,
+ enabled -> Bool,
+ data -> Text,
+ }
+}
+
+table! {
organizations (uuid) {
uuid -> Text,
name -> 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,