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
136 changes: 129 additions & 7 deletions crates/context_aware_config/src/api/context/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@ use service_utils::{
helpers::{
WebhookData, execute_webhook_call, fetch_dimensions_info_map, parse_config_tags,
},
middlewares::auth_z::{Action as AuthZAction, AuthZ},
service::types::{
AppHeader, AppState, CustomHeaders, DbConnection, WorkspaceContext,
AppHeader, AppState, CustomHeaders, DbConnection, SchemaName, WorkspaceContext,
},
};
use superposition_core::helpers::{calculate_context_weight, hash};
use superposition_derives::{authorized, declare_resource};
use superposition_macros::{bad_argument, db_error, unexpected_error};
use superposition_types::{
Contextual, InternalUserContext, ListResponse, Overridden, Overrides,
Contextual, DBConnection, InternalUserContext, ListResponse, Overridden, Overrides,
PaginatedResponse, Resource, SortBy, User,
api::{
DimensionMatchStrategy,
context::{
BulkOperation, BulkOperationResponse, ContextAction, ContextBulkResponse,
ContextListFilters, ContextValidationRequest, MoveRequest, PutRequest,
SortOn, UpdateRequest, WeightRecomputeResponse,
ContextListFilters, ContextValidationRequest, Identifier, MoveRequest,
PutRequest, SortOn, UpdateRequest, WeightRecomputeResponse,
},
webhook::Action,
},
Expand All @@ -50,7 +51,7 @@ use superposition_types::{

use crate::{
api::context::{
helpers::{query_description, validate_ctx},
helpers::{changed_keys, query_description, validate_ctx},
operations,
},
helpers::{add_config_version, put_config_in_redis, validate_change_reason},
Expand All @@ -72,6 +73,16 @@ pub fn endpoints() -> Scope {
.service(validate_handler)
}

async fn create_authorized<A: AuthZAction>(
auth_z: &AuthZ<A>,
override_map: &Overrides,
) -> superposition::Result<()> {
let keys = override_map.keys().collect::<Vec<_>>();
auth_z
.action_authorized(&AuthZActionCreate::get(), &keys)
.await
}
Comment thread
ayushjain17 marked this conversation as resolved.

#[allow(clippy::too_many_arguments)]
#[authorized]
#[put("")]
Expand All @@ -84,6 +95,9 @@ async fn create_handler(
user: User,
internal_user: InternalUserContext,
) -> superposition::Result<HttpResponse> {
let req = req.into_inner();
create_authorized(&_auth_z, &req.r#override).await?;

let tags = parse_config_tags(custom_headers.config_tags)?;
let description = match req.description.clone() {
Some(val) => val,
Expand Down Expand Up @@ -117,7 +131,7 @@ async fn create_handler(
let (put_response, version_id) = db_conn
.transaction::<_, superposition::AppError, _>(|transaction_conn| {
let put_response = operations::upsert(
req.into_inner(),
req,
description,
transaction_conn,
true,
Expand Down Expand Up @@ -179,6 +193,24 @@ async fn create_handler(
Ok(http_resp.json(put_response))
}

async fn update_authorized<A: AuthZAction>(
auth_z: &AuthZ<A>,
context: &Identifier,
new_overrides: &Overrides,
schema_name: &SchemaName,
conn: &mut DBConnection,
) -> superposition::Result<()> {
let overrides =
operations::get_overrides_from_identifier(context, schema_name, conn)?;

auth_z
.action_authorized(
&AuthZActionUpdate::get(),
&changed_keys(&overrides, new_overrides),
)
.await
}

#[authorized]
#[routes]
#[put("/overrides")]
Expand All @@ -194,6 +226,15 @@ async fn update_handler(
let tags = parse_config_tags(custom_headers.config_tags)?;
let req_change_reason = req.change_reason.clone();

update_authorized(
&_auth_z,
&req.context,
&req.override_,
&workspace_context.schema_name,
&mut db_conn,
)
.await?;

validate_change_reason(
&workspace_context,
&req_change_reason,
Expand Down Expand Up @@ -262,6 +303,21 @@ async fn update_handler(
Ok(http_resp.json(override_resp))
}

async fn move_authorized<A: AuthZAction>(
auth_z: &AuthZ<A>,
ctx_id: &str,
schema_name: &SchemaName,
conn: &mut DBConnection,
) -> superposition::Result<()> {
let overrides = operations::get_overrides_from_ctx_id(ctx_id, schema_name, conn)?;
auth_z
.action_authorized(
&AuthZActionMove::get(),
&overrides.keys().collect::<Vec<_>>(),
)
.await
}

#[allow(clippy::too_many_arguments)]
#[authorized]
#[put("/move/{ctx_id}")]
Expand All @@ -275,6 +331,15 @@ async fn move_handler(
user: User,
internal_user: InternalUserContext,
) -> superposition::Result<HttpResponse> {
let ctx_id = path.into_inner();
move_authorized(
&_auth_z,
&ctx_id,
&workspace_context.schema_name,
&mut db_conn,
)
.await?;

let tags = parse_config_tags(custom_headers.config_tags)?;

let description = match req.description.clone() {
Expand Down Expand Up @@ -309,7 +374,7 @@ async fn move_handler(
.transaction::<_, superposition::AppError, _>(|transaction_conn| {
let move_response = operations::r#move(
&workspace_context,
path.into_inner(),
ctx_id,
req,
description,
transaction_conn,
Expand Down Expand Up @@ -556,6 +621,21 @@ async fn list_handler(
Ok(Json(paginated_response))
}

async fn delete_authorized<A: AuthZAction>(
auth_z: &AuthZ<A>,
ctx_id: &str,
schema_name: &SchemaName,
conn: &mut DBConnection,
) -> superposition::Result<()> {
let overrides = operations::get_overrides_from_ctx_id(ctx_id, schema_name, conn)?;
auth_z
.action_authorized(
&AuthZActionDelete::get(),
&overrides.keys().collect::<Vec<_>>(),
)
.await
}

#[authorized]
#[delete("/{ctx_id}")]
async fn delete_handler(
Expand All @@ -570,6 +650,14 @@ async fn delete_handler(
contexts as contexts_table, id as context_id,
};
let ctx_id = path.into_inner();
delete_authorized(
&_auth_z,
&ctx_id,
&workspace_context.schema_name,
&mut db_conn,
)
.await?;

let tags = parse_config_tags(custom_headers.config_tags)?;
let (version_id, deleted_ctx) = db_conn
.transaction::<_, superposition::AppError, _>(|transaction_conn| {
Expand Down Expand Up @@ -632,6 +720,38 @@ async fn delete_handler(
.finish())
}

async fn bulk_authorized<A: AuthZAction>(
Comment thread
ayushjain17 marked this conversation as resolved.
auth_z: &AuthZ<A>,
operations: &Vec<ContextAction>,
schema_name: &SchemaName,
conn: &mut DBConnection,
) -> superposition::Result<()> {
for op in operations {
match op {
ContextAction::Put(put_req) => {
create_authorized(auth_z, &put_req.r#override).await?;
}
ContextAction::Replace(update_req) => {
update_authorized(
auth_z,
&update_req.context,
&update_req.override_,
schema_name,
conn,
)
.await?;
}
ContextAction::Delete(ctx_id) => {
delete_authorized(auth_z, ctx_id, schema_name, conn).await?;
}
ContextAction::Move { id: ctx_id, .. } => {
move_authorized(auth_z, ctx_id, schema_name, conn).await?;
}
}
}
Ok(())
}

#[allow(clippy::too_many_arguments)]
#[authorized]
#[put("/bulk-operations")]
Expand All @@ -655,6 +775,8 @@ async fn bulk_operations_handler(
bo.into_inner().operations
}
};
bulk_authorized(&_auth_z, &ops, &workspace_context.schema_name, &mut conn).await?;

// Marking immutable.
let is_v2 = is_v2;
let mut all_change_reasons = Vec::new();
Expand Down
18 changes: 18 additions & 0 deletions crates/context_aware_config/src/api/context/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,21 @@ pub fn validate_ctx(
)?;
Ok(dimension_info_map)
}

pub fn changed_keys<'a>(
old_values: &'a Map<String, Value>,
new_values: &'a Map<String, Value>,
) -> Vec<&'a String> {
let keys = old_values
.keys()
.chain(new_values.keys())
.collect::<std::collections::HashSet<_>>();

keys.into_iter()
.filter(|key| {
let old_value = old_values.get(*key).cloned();
let new_value = new_values.get(*key).cloned();
old_value != new_value
})
.collect()
Comment thread
ayushjain17 marked this conversation as resolved.
}
31 changes: 31 additions & 0 deletions crates/context_aware_config/src/api/context/operations.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ops::Deref;

use actix_web::web::Json;
use chrono::Utc;
use diesel::{
Expand Down Expand Up @@ -94,6 +96,35 @@ pub fn upsert(
}
}

pub fn get_overrides_from_ctx_id(
ctx_id: &str,
schema_name: &SchemaName,
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
) -> result::Result<Overrides> {
let overrides = dsl::contexts
.filter(dsl::id.eq(ctx_id))
.schema_name(schema_name)
.select(dsl::override_)
.first::<Overrides>(conn)?;

Ok(overrides)
}

pub fn get_overrides_from_identifier(
identifier: &Identifier,
schema_name: &SchemaName,
conn: &mut PooledConnection<ConnectionManager<PgConnection>>,
) -> result::Result<Overrides> {
let context_id = match identifier {
Identifier::Context(context) => {
&hash(&Value::Object((**context.deref()).clone()))
}
Identifier::Id(id) => id,
};

get_overrides_from_ctx_id(context_id, schema_name, conn)
Comment thread
ayushjain17 marked this conversation as resolved.
}
Comment thread
ayushjain17 marked this conversation as resolved.

pub fn update(
workspace_context: &WorkspaceContext,
req: UpdateRequest,
Expand Down
16 changes: 13 additions & 3 deletions crates/context_aware_config/src/api/default_config/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ops::Deref;

use actix_web::{
HttpResponse, Scope, delete, get, post, routes,
web::{Data, Json, Path, Query},
Expand Down Expand Up @@ -75,8 +77,10 @@ async fn create_handler(
db_conn: DbConnection,
user: User,
) -> superposition::Result<HttpResponse> {
let DbConnection(mut conn) = db_conn;
let req = request.into_inner();
_auth_z.authorized(&[req.key.deref()]).await?;

let DbConnection(mut conn) = db_conn;
let key = req.key;
let tags = parse_config_tags(custom_headers.config_tags)?;

Expand Down Expand Up @@ -228,9 +232,12 @@ async fn update_handler(
db_conn: DbConnection,
user: User,
) -> superposition::Result<HttpResponse> {
let key = key.into_inner();
_auth_z.authorized(&[key.deref()]).await?;

let DbConnection(mut conn) = db_conn;
let req = request.into_inner();
let key_str = key.into_inner().into();
let key_str = key.into();
let tags = parse_config_tags(custom_headers.config_tags)?;

let existing = fetch_default_key(&key_str, &mut conn, &workspace_context.schema_name)
Expand Down Expand Up @@ -504,10 +511,13 @@ async fn delete_handler(
db_conn: DbConnection,
user: User,
) -> superposition::Result<HttpResponse> {
let key = path.into_inner();
_auth_z.authorized(&[key.deref()]).await?;

let DbConnection(mut conn) = db_conn;
let tags = parse_config_tags(custom_headers.config_tags)?;

let key: String = path.into_inner().into();
let key: String = key.into();

let context_ids =
get_key_usage_context_ids(&key, &mut conn, &workspace_context.schema_name)
Expand Down
1 change: 0 additions & 1 deletion crates/experimentation_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ impl Client {
.filter_map(|experiment| {
let variants: Vec<_> = experiment
.variants
.into_inner()
.into_iter()
.filter_map(|mut variant| {
Variant::filter_keys_by_prefix(&variant, &prefix_list)
Expand Down
Loading
Loading