Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ CREATE OR REPLACE FUNCTION can_principal_get_resource(

ELSIF selected_resource_type = 'Group' THEN

-- Group -> (Group | Server)
-- Group -> Server
-- Check if the group has an associated access policy.
SELECT
permission_level,
Expand Down Expand Up @@ -827,46 +827,8 @@ CREATE OR REPLACE FUNCTION can_principal_get_resource(

-- Use the parent resource type.
needs_inheritance := TRUE;

SELECT
parent_resource_type
INTO
selected_resource_parent_type
FROM
groups
WHERE
groups.id = selected_resource_id;

IF selected_resource_parent_type = 'Group' THEN

SELECT
parent_group_id
INTO
selected_resource_parent_id
FROM
groups
WHERE
groups.id = selected_resource_id;

IF selected_resource_parent_id IS NULL THEN

RAISE EXCEPTION 'Couldn''t find a parent group for group %.', selected_resource_id;

END IF;

selected_resource_type := 'Group';
selected_resource_id := selected_resource_parent_id;

ELSIF selected_resource_parent_type = 'Server' THEN

selected_resource_type := 'Server';
selected_resource_id := NULL;

ELSE

RAISE EXCEPTION 'Unknown parent resource type % for group %.', selected_resource_parent_type, selected_resource_id;

END IF;
selected_resource_type := 'Server';
selected_resource_id := NULL;

ELSIF selected_resource_type = 'HTTPTransaction' THEN

Expand Down
4 changes: 1 addition & 3 deletions src/queries/groups/initialize_groups_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ BEGIN
id UUID DEFAULT uuidv7() PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
description TEXT,
parent_resource_type group_parent_resource_type NOT NULL,
parent_group_id UUID references groups(id) ON DELETE CASCADE
description TEXT
);

END
Expand Down
8 changes: 2 additions & 6 deletions src/queries/groups/insert_group_row.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
INSERT INTO groups (
name,
display_name,
description,
parent_resource_type,
parent_group_id
description
) VALUES (
$1,
$2,
$3,
$4,
$5
$3
) RETURNING *;
65 changes: 42 additions & 23 deletions src/resources/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ pub const ALLOWED_QUERY_KEYS: &[&str] = &[
"id",
"name",
"display_name",
"description",
"parent_resource_type",
"parent_group_id"
"description"
];
pub const UUID_QUERY_KEYS: &[&str] = &[
"id",
"parent_group_id"
"id"
];
pub const RESOURCE_NAME: &str = "Group";
pub const DATABASE_TABLE_NAME: &str = "groups";
Expand All @@ -42,13 +39,21 @@ pub struct InitialGroupProperties {
pub display_name: String,

/// The group's description, if applicable.
pub description: Option<String>,
pub description: Option<String>

/// The group's parent resource type.
pub parent_resource_type: GroupParentResourceType,
}

/// The group's parent group ID, if applicable.
pub parent_group_id: Option<Uuid>
#[derive(Debug, Clone, ToSql, FromSql, Default, Serialize, Deserialize)]
pub struct EditableGroupProperties {

/// The group's name.
pub name: Option<String>,

/// The group's display name.
pub display_name: Option<String>,

/// The group's description, if applicable.
pub description: Option<Option<String>>

}

Expand All @@ -65,13 +70,7 @@ pub struct Group {
pub display_name: String,

/// The group's description, if applicable.
pub description: Option<String>,

/// The group's parent resource type.
pub parent_resource_type: GroupParentResourceType,

/// The group's parent group ID, if applicable.
pub parent_group_id: Option<Uuid>
pub description: Option<String>

}

Expand Down Expand Up @@ -134,9 +133,7 @@ impl Group {
id: row.get("id"),
name: row.get("name"),
display_name: row.get("display_name"),
description: row.get("description"),
parent_resource_type: row.get("parent_resource_type"),
parent_group_id: row.get("parent_group_id")
description: row.get("description")
};

}
Expand All @@ -158,9 +155,7 @@ impl Group {
let parameters: &[&(dyn ToSql + Sync)] = &[
&initial_properties.name,
&initial_properties.display_name,
&initial_properties.description,
&initial_properties.parent_resource_type,
&initial_properties.parent_group_id
&initial_properties.description
];
let database_client = database_pool.get().await?;
let row = database_client.query_one(query, parameters).await.map_err(|error| {
Expand Down Expand Up @@ -219,6 +214,30 @@ impl Group {

}

/// Updates this group and returns a new instance of the group.
pub async fn update(&self, properties: &EditableGroupProperties, database_pool: &deadpool_postgres::Pool) -> Result<Self, ResourceError> {

let query = String::from("UPDATE groups SET ");
let parameter_boxes: Vec<Box<dyn ToSql + Sync + Send>> = Vec::new();
let database_client = database_pool.get().await?;

database_client.query("BEGIN;", &[]).await?;
let (parameter_boxes, query) = slashstepql::add_parameter_to_query(parameter_boxes, query, "name", properties.name.as_ref());
let (parameter_boxes, query) = slashstepql::add_parameter_to_query(parameter_boxes, query, "display_name", properties.display_name.as_ref());
let (parameter_boxes, query) = slashstepql::add_parameter_to_query(parameter_boxes, query, "description", properties.description.as_ref());
let (mut parameter_boxes, mut query) = (parameter_boxes, query);

query.push_str(format!(" WHERE id = ${} RETURNING *;", parameter_boxes.len() + 1).as_str());
parameter_boxes.push(Box::new(&self.id));
let parameters: Vec<&(dyn ToSql + Sync)> = parameter_boxes.iter().map(|parameter| parameter.as_ref() as &(dyn ToSql + Sync)).collect();
let row = database_client.query_one(&query, &parameters).await?;
database_client.query("COMMIT;", &[]).await?;

let group = Self::convert_from_row(&row);
return Ok(group);

}

}

impl DeletableResource for Group {
Expand Down
12 changes: 3 additions & 9 deletions src/resources/group/tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use uuid::Uuid;

use crate::{
initialize_required_tables, predefinitions::initialize_predefined_actions, initialize_predefined_configurations, resources::{
initialize_required_tables, predefinitions::initialize_predefined_actions, resources::{
DeletableResource, ResourceError, access_policy::{AccessPolicy, InitialAccessPolicyProperties}, action::{
Action, DEFAULT_ACTION_LIST_LIMIT
}, group::GroupParentResourceType
}
}, tests::{TestEnvironment, TestSlashstepServerError}
};
use super::{DEFAULT_RESOURCE_LIST_LIMIT, GET_RESOURCE_ACTION_NAME, Group, InitialGroupProperties};
Expand All @@ -15,8 +15,6 @@ fn assert_groups_are_equal(group_1: &Group, group_2: &Group) {
assert_eq!(group_1.name, group_2.name);
assert_eq!(group_1.display_name, group_2.display_name);
assert_eq!(group_1.description, group_2.description);
assert_eq!(group_1.parent_resource_type, group_2.parent_resource_type);
assert_eq!(group_1.parent_group_id, group_2.parent_group_id);

}

Expand All @@ -25,8 +23,6 @@ fn assert_group_is_equal_to_initial_properties(group: &Group, initial_properties
assert_eq!(group.name, initial_properties.name);
assert_eq!(group.display_name, initial_properties.display_name);
assert_eq!(group.description, initial_properties.description);
assert_eq!(group.parent_resource_type, initial_properties.parent_resource_type);
assert_eq!(group.parent_group_id, initial_properties.parent_group_id);

}

Expand Down Expand Up @@ -62,9 +58,7 @@ async fn verify_creation() -> Result<(), TestSlashstepServerError> {
let group_properties = InitialGroupProperties {
name: Uuid::now_v7().to_string(),
display_name: Uuid::now_v7().to_string(),
description: Some(Uuid::now_v7().to_string()),
parent_resource_type: GroupParentResourceType::Server,
parent_group_id: None
description: Some(Uuid::now_v7().to_string())
};
let group = Group::create(&group_properties, &test_environment.database_pool).await?;

Expand Down
123 changes: 48 additions & 75 deletions src/routes/groups/{group_id}/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
*/

use std::sync::Arc;
use axum::{Extension, Json, Router, extract::{Path, State}};
use axum::{Extension, Json, Router, extract::{Path, State, rejection::JsonRejection}};
use reqwest::StatusCode;
use crate::{
AppState,
HTTPError,
middleware::{authentication_middleware, http_request_middleware},
resources::{
access_policy::{AccessPolicyResourceType, ActionPermissionLevel}, action_log_entry::{ActionLogEntry, ActionLogEntryActorType, ActionLogEntryTargetResourceType, InitialActionLogEntryProperties}, app::App, app_authorization::AppAuthorization, group::Group, http_transaction::HTTPTransaction, server_log_entry::ServerLogEntry, user::User
access_policy::{AccessPolicyResourceType, ActionPermissionLevel}, action_log_entry::{ActionLogEntry, ActionLogEntryActorType, ActionLogEntryTargetResourceType, InitialActionLogEntryProperties}, app::App, app_authorization::AppAuthorization, group::{EditableGroupProperties, Group}, http_transaction::HTTPTransaction, server_log_entry::ServerLogEntry, user::User
},
utilities::{reusable_route_handlers::delete_resource, route_handler_utilities::{AuthenticatedPrincipal, get_action_by_name, get_action_log_entry_expiration_timestamp, get_authenticated_principal, get_group_by_id, get_resource_hierarchy, get_uuid_from_string, verify_delegate_permissions, verify_principal_permissions}}
utilities::{reusable_route_handlers::delete_resource, route_handler_utilities::{AuthenticatedPrincipal, get_action_by_name, get_action_log_entry_expiration_timestamp, get_authenticated_principal, get_group_by_id, get_request_body_without_json_rejection, get_resource_hierarchy, get_uuid_from_string, verify_delegate_permissions, verify_principal_permissions}}
};

#[path = "./access-policies/mod.rs"]
Expand Down Expand Up @@ -99,94 +99,67 @@ async fn handle_delete_group_request(

}

// /// PATCH /groups/{group_id}
// ///
// /// Updates an app by its ID.
// #[axum::debug_handler]
// async fn handle_patch_app_request(
// Path(group_id): Path<String>,
// State(state): State<AppState>,
// Extension(http_transaction): Extension<Arc<HTTPTransaction>>,
// Extension(authenticated_user): Extension<Option<Arc<User>>>,
// Extension(authenticated_app): Extension<Option<Arc<App>>>,
// Extension(authenticated_app_authorization): Extension<Option<Arc<AppAuthorization>>>,
// body: Result<Json<EditableAppProperties>, JsonRejection>
// ) -> Result<Json<App>, HTTPError> {

// let http_transaction = http_transaction.clone();

// ServerLogEntry::trace("Verifying request body...", Some(&http_transaction.id), &state.database_pool).await.ok();
// let updated_app_properties = match body {

// Ok(updated_app_properties) => updated_app_properties,

// Err(error) => {

// let http_error = match error {

// JsonRejection::JsonDataError(error) => HTTPError::BadRequestError(Some(error.to_string())),

// JsonRejection::JsonSyntaxError(_) => HTTPError::BadRequestError(Some(format!("Failed to parse request body. Ensure the request body is valid JSON."))),

// JsonRejection::MissingJsonContentType(_) => HTTPError::BadRequestError(Some(format!("Missing request body content type. It should be \"application/json\"."))),

// JsonRejection::BytesRejection(error) => HTTPError::InternalServerError(Some(format!("Failed to parse request body: {:?}", error))),

// _ => HTTPError::InternalServerError(Some(error.to_string()))

// };

// ServerLogEntry::from_http_error(&http_error, Some(&http_transaction.id), &state.database_pool).await.ok();
// return Err(http_error);

// }

// };
/// PATCH /groups/{group_id}
///
/// Updates a group by its ID.
#[axum::debug_handler]
async fn handle_patch_group_request(
Path(group_id): Path<String>,
State(state): State<AppState>,
Extension(http_transaction): Extension<Arc<HTTPTransaction>>,
Extension(authenticated_user): Extension<Option<Arc<User>>>,
Extension(authenticated_app): Extension<Option<Arc<App>>>,
Extension(authenticated_app_authorization): Extension<Option<Arc<AppAuthorization>>>,
body: Result<Json<EditableGroupProperties>, JsonRejection>
) -> Result<Json<Group>, HTTPError> {

// let original_target_field = get_app_by_id(&group_id, &http_transaction, &state.database_pool).await?;
// let resource_hierarchy = get_resource_hierarchy(&original_target_field, &AccessPolicyResourceType::App, &original_target_field.id, &http_transaction, &state.database_pool).await?;
// let update_access_policy_action = get_action_by_name("apps.update", &http_transaction, &state.database_pool).await?;
// verify_delegate_permissions(authenticated_app_authorization.as_ref().map(|app_authorization| &app_authorization.id), &update_access_policy_action.id, &http_transaction.id, &ActionPermissionLevel::User, &state.database_pool).await?;
// let authenticated_principal = get_authenticated_principal(authenticated_user.as_ref(), authenticated_app.as_ref())?;
// verify_principal_permissions(&authenticated_principal, &update_access_policy_action, &resource_hierarchy, &http_transaction, &ActionPermissionLevel::User, &state.database_pool).await?;
let http_transaction = http_transaction.clone();
let updated_group_properties = get_request_body_without_json_rejection(body, &http_transaction, &state.database_pool).await?;
let group_id = get_uuid_from_string(&group_id, "group", &http_transaction, &state.database_pool).await?;
let original_target_group = get_group_by_id(&group_id, &http_transaction, &state.database_pool).await?;
let resource_hierarchy = get_resource_hierarchy(&original_target_group, &AccessPolicyResourceType::Group, &original_target_group.id, &http_transaction, &state.database_pool).await?;
let update_access_policy_action = get_action_by_name("groups.update", &http_transaction, &state.database_pool).await?;
verify_delegate_permissions(authenticated_app_authorization.as_ref().map(|app_authorization| &app_authorization.id), &update_access_policy_action.id, &http_transaction.id, &ActionPermissionLevel::User, &state.database_pool).await?;
let authenticated_principal = get_authenticated_principal(authenticated_user.as_ref(), authenticated_app.as_ref())?;
verify_principal_permissions(&authenticated_principal, &update_access_policy_action, &resource_hierarchy, &http_transaction, &ActionPermissionLevel::User, &state.database_pool).await?;

// ServerLogEntry::trace(&format!("Updating authenticated_app {}...", original_target_field.id), Some(&http_transaction.id), &state.database_pool).await.ok();
// let updated_target_action = match original_target_field.update(&updated_app_properties, &state.database_pool).await {
ServerLogEntry::trace(&format!("Updating group {}...", original_target_group.id), Some(&http_transaction.id), &state.database_pool).await.ok();
let updated_target_group = match original_target_group.update(&updated_group_properties, &state.database_pool).await {

// Ok(updated_target_action) => updated_target_action,
Ok(updated_target_group) => updated_target_group,

// Err(error) => {
Err(error) => {

// let http_error = HTTPError::InternalServerError(Some(format!("Failed to update authenticated_app: {:?}", error)));
// ServerLogEntry::from_http_error(&http_error, Some(&http_transaction.id), &state.database_pool).await.ok();
// return Err(http_error);
let http_error = HTTPError::InternalServerError(Some(format!("Failed to update group {}: {:?}", original_target_group.id, error)));
ServerLogEntry::from_http_error(&http_error, Some(&http_transaction.id), &state.database_pool).await.ok();
return Err(http_error);

// }
}

// };
};

// ActionLogEntry::create(&InitialActionLogEntryProperties {
// action_id: update_access_policy_action.id,
// http_transaction_id: Some(http_transaction.id),
// actor_type: if let AuthenticatedPrincipal::User(_) = &authenticated_principal { ActionLogEntryActorType::User } else { ActionLogEntryActorType::App },
// actor_user_id: if let AuthenticatedPrincipal::User(authenticated_user) = &authenticated_principal { Some(authenticated_user.id.clone()) } else { None },
// actor_group_id: if let AuthenticatedPrincipal::App(authenticated_app) = &authenticated_principal { Some(authenticated_app.id.clone()) } else { None },
// target_resource_type: ActionLogEntryTargetResourceType::Action,
// target_action_id: Some(updated_target_action.id),
// ..Default::default()
// }, &state.database_pool).await.ok();
// ServerLogEntry::success(&format!("Successfully updated action {}.", updated_target_action.id), Some(&http_transaction.id), &state.database_pool).await.ok();
ActionLogEntry::create(&InitialActionLogEntryProperties {
action_id: update_access_policy_action.id,
http_transaction_id: Some(http_transaction.id),
actor_type: if let AuthenticatedPrincipal::User(_) = &authenticated_principal { ActionLogEntryActorType::User } else { ActionLogEntryActorType::App },
actor_user_id: if let AuthenticatedPrincipal::User(authenticated_user) = &authenticated_principal { Some(authenticated_user.id.clone()) } else { None },
actor_app_id: if let AuthenticatedPrincipal::App(authenticated_app) = &authenticated_principal { Some(authenticated_app.id.clone()) } else { None },
target_resource_type: ActionLogEntryTargetResourceType::Group,
target_group_id: Some(updated_target_group.id),
..Default::default()
}, &state.database_pool).await.ok();
ServerLogEntry::success(&format!("Successfully updated group {}.", updated_target_group.id), Some(&http_transaction.id), &state.database_pool).await.ok();

// return Ok(Json(updated_target_action));
return Ok(Json(updated_target_group));

// }
}

pub fn get_router(state: AppState) -> Router<AppState> {

let router = Router::<AppState>::new()
.route("/groups/{group_id}", axum::routing::get(handle_get_group_request))
.route("/groups/{group_id}", axum::routing::delete(handle_delete_group_request))
// .route("/groups/{group_id}", axum::routing::patch(handle_patch_app_request))
.route("/groups/{group_id}", axum::routing::patch(handle_patch_group_request))
.layer(axum::middleware::from_fn_with_state(state.clone(), authentication_middleware::authenticate_user))
.layer(axum::middleware::from_fn_with_state(state.clone(), authentication_middleware::authenticate_app))
.layer(axum::middleware::from_fn_with_state(state.clone(), http_request_middleware::create_http_request))
Expand Down
Loading