mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
Tidy model_permissions
and command_permissions
stages (#775)
<!-- The PR description should answer 2 (maybe 3) important questions: --> ### What Much like https://github.com/hasura/v3-engine/pull/774, we split the big files into separate modules. Functional no-op. <!-- What is this PR trying to accomplish (and why, if it's not obvious)? --> <!-- Consider: do we need to add a changelog entry? --> ### How Copy pasta, `just fix-local`, mostly. <!-- How is it trying to accomplish it (what are the implementation steps)? --> V3_GIT_ORIGIN_REV_ID: ba7e40057583f98df54573c72663a4a2d2c4a4ab
This commit is contained in:
parent
9232af913a
commit
b31d6099d7
@ -0,0 +1,146 @@
|
|||||||
|
use hasura_authn_core::Role;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName};
|
||||||
|
|
||||||
|
use crate::stages::{
|
||||||
|
boolean_expressions, commands, data_connector_scalar_types, data_connectors, models_graphql,
|
||||||
|
object_boolean_expressions, relationships, scalar_types,
|
||||||
|
};
|
||||||
|
use crate::types::error::Error;
|
||||||
|
use crate::types::subgraph::Qualified;
|
||||||
|
use open_dds::arguments::ArgumentName;
|
||||||
|
|
||||||
|
use crate::helpers::argument::resolve_value_expression_for_argument;
|
||||||
|
|
||||||
|
use open_dds::permissions::CommandPermissionsV1;
|
||||||
|
|
||||||
|
use super::types::CommandPermission;
|
||||||
|
use crate::helpers::typecheck;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
// get the ndc_models::Type for an argument if it is available
|
||||||
|
fn get_command_source_argument<'a>(
|
||||||
|
argument_name: &'a ArgumentName,
|
||||||
|
command: &'a commands::Command,
|
||||||
|
) -> Option<&'a ndc_models::Type> {
|
||||||
|
command
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|source| {
|
||||||
|
source
|
||||||
|
.argument_mappings
|
||||||
|
.get(argument_name)
|
||||||
|
.map(|connector_argument_name| source.source_arguments.get(connector_argument_name))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_command_permissions(
|
||||||
|
command: &commands::Command,
|
||||||
|
permissions: &CommandPermissionsV1,
|
||||||
|
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
|
object_boolean_expression_types: &BTreeMap<
|
||||||
|
Qualified<CustomTypeName>,
|
||||||
|
object_boolean_expressions::ObjectBooleanExpressionType,
|
||||||
|
>,
|
||||||
|
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
||||||
|
data_connectors: &data_connectors::DataConnectors,
|
||||||
|
data_connector_scalars: &BTreeMap<
|
||||||
|
Qualified<DataConnectorName>,
|
||||||
|
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
||||||
|
>,
|
||||||
|
subgraph: &str,
|
||||||
|
) -> Result<BTreeMap<Role, CommandPermission>, Error> {
|
||||||
|
let mut validated_permissions = BTreeMap::new();
|
||||||
|
for command_permission in &permissions.permissions {
|
||||||
|
let mut argument_presets = BTreeMap::new();
|
||||||
|
|
||||||
|
for argument_preset in &command_permission.argument_presets {
|
||||||
|
if argument_presets.contains_key(&argument_preset.argument) {
|
||||||
|
return Err(Error::DuplicateCommandArgumentPreset {
|
||||||
|
command_name: command.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_argument_type =
|
||||||
|
get_command_source_argument(&argument_preset.argument, command);
|
||||||
|
|
||||||
|
let data_connector_name = command
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.map(|source| &source.data_connector.name)
|
||||||
|
.ok_or(Error::CommandSourceRequiredForPredicate {
|
||||||
|
command_name: command.name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_core_info =
|
||||||
|
data_connectors.0.get(data_connector_name).ok_or_else(|| {
|
||||||
|
Error::UnknownCommandDataConnector {
|
||||||
|
command_name: command.name.clone(),
|
||||||
|
data_connector: data_connector_name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_link = data_connectors::DataConnectorLink::new(
|
||||||
|
data_connector_name.clone(),
|
||||||
|
&data_connector_core_info.inner,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match command.arguments.get(&argument_preset.argument) {
|
||||||
|
Some(argument) => {
|
||||||
|
let value_expression = resolve_value_expression_for_argument(
|
||||||
|
&argument_preset.argument,
|
||||||
|
&argument_preset.value,
|
||||||
|
&argument.argument_type,
|
||||||
|
source_argument_type,
|
||||||
|
&data_connector_link,
|
||||||
|
subgraph,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
|
object_boolean_expression_types,
|
||||||
|
boolean_expression_types,
|
||||||
|
models,
|
||||||
|
data_connector_scalars,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// additionally typecheck literals
|
||||||
|
// we do this outside the argument resolve so that we can emit a command-specific error
|
||||||
|
// on typechecking failure
|
||||||
|
typecheck::typecheck_value_expression(
|
||||||
|
&argument.argument_type,
|
||||||
|
&argument_preset.value,
|
||||||
|
)
|
||||||
|
.map_err(|type_error| {
|
||||||
|
Error::CommandArgumentPresetTypeError {
|
||||||
|
command_name: command.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
type_error,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
argument_presets.insert(
|
||||||
|
argument_preset.argument.clone(),
|
||||||
|
(argument.argument_type.clone(), value_expression),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error::CommandArgumentPresetMismatch {
|
||||||
|
command_name: command.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolved_permission = CommandPermission {
|
||||||
|
allow_execution: command_permission.allow_execution,
|
||||||
|
argument_presets,
|
||||||
|
};
|
||||||
|
validated_permissions.insert(command_permission.role.clone(), resolved_permission);
|
||||||
|
}
|
||||||
|
Ok(validated_permissions)
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
mod command_permission;
|
||||||
|
|
||||||
use hasura_authn_core::Role;
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use open_dds::{
|
use open_dds::{
|
||||||
@ -13,29 +11,11 @@ use crate::stages::{
|
|||||||
object_boolean_expressions, relationships, scalar_types,
|
object_boolean_expressions, relationships, scalar_types,
|
||||||
};
|
};
|
||||||
use crate::types::error::Error;
|
use crate::types::error::Error;
|
||||||
use crate::types::permission::ValueExpression;
|
use crate::types::subgraph::Qualified;
|
||||||
use crate::types::subgraph::{Qualified, QualifiedTypeReference};
|
|
||||||
use open_dds::arguments::ArgumentName;
|
|
||||||
|
|
||||||
use crate::helpers::argument::resolve_value_expression_for_argument;
|
|
||||||
|
|
||||||
use open_dds::permissions::CommandPermissionsV1;
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
mod types;
|
||||||
use crate::helpers::typecheck;
|
pub use types::CommandWithPermissions;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
|
||||||
pub struct CommandWithPermissions {
|
|
||||||
pub command: commands::Command,
|
|
||||||
pub permissions: BTreeMap<Role, CommandPermission>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
|
||||||
pub struct CommandPermission {
|
|
||||||
pub allow_execution: bool,
|
|
||||||
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// resolve command permissions
|
/// resolve command permissions
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
@ -82,7 +62,7 @@ pub fn resolve(
|
|||||||
command_name: qualified_command_name.clone(),
|
command_name: qualified_command_name.clone(),
|
||||||
})?;
|
})?;
|
||||||
if command.permissions.is_empty() {
|
if command.permissions.is_empty() {
|
||||||
command.permissions = resolve_command_permissions(
|
command.permissions = command_permission::resolve_command_permissions(
|
||||||
&command.command,
|
&command.command,
|
||||||
command_permissions,
|
command_permissions,
|
||||||
object_types,
|
object_types,
|
||||||
@ -102,129 +82,3 @@ pub fn resolve(
|
|||||||
}
|
}
|
||||||
Ok(commands_with_permissions)
|
Ok(commands_with_permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the ndc_models::Type for an argument if it is available
|
|
||||||
fn get_command_source_argument<'a>(
|
|
||||||
argument_name: &'a ArgumentName,
|
|
||||||
command: &'a commands::Command,
|
|
||||||
) -> Option<&'a ndc_models::Type> {
|
|
||||||
command
|
|
||||||
.source
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|source| {
|
|
||||||
source
|
|
||||||
.argument_mappings
|
|
||||||
.get(argument_name)
|
|
||||||
.map(|connector_argument_name| source.source_arguments.get(connector_argument_name))
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_command_permissions(
|
|
||||||
command: &commands::Command,
|
|
||||||
permissions: &CommandPermissionsV1,
|
|
||||||
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
|
||||||
object_boolean_expression_types: &BTreeMap<
|
|
||||||
Qualified<CustomTypeName>,
|
|
||||||
object_boolean_expressions::ObjectBooleanExpressionType,
|
|
||||||
>,
|
|
||||||
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
|
||||||
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
|
||||||
data_connectors: &data_connectors::DataConnectors,
|
|
||||||
data_connector_scalars: &BTreeMap<
|
|
||||||
Qualified<DataConnectorName>,
|
|
||||||
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
|
||||||
>,
|
|
||||||
subgraph: &str,
|
|
||||||
) -> Result<BTreeMap<Role, CommandPermission>, Error> {
|
|
||||||
let mut validated_permissions = BTreeMap::new();
|
|
||||||
for command_permission in &permissions.permissions {
|
|
||||||
let mut argument_presets = BTreeMap::new();
|
|
||||||
|
|
||||||
for argument_preset in &command_permission.argument_presets {
|
|
||||||
if argument_presets.contains_key(&argument_preset.argument) {
|
|
||||||
return Err(Error::DuplicateCommandArgumentPreset {
|
|
||||||
command_name: command.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_argument_type =
|
|
||||||
get_command_source_argument(&argument_preset.argument, command);
|
|
||||||
|
|
||||||
let data_connector_name = command
|
|
||||||
.source
|
|
||||||
.as_ref()
|
|
||||||
.map(|source| &source.data_connector.name)
|
|
||||||
.ok_or(Error::CommandSourceRequiredForPredicate {
|
|
||||||
command_name: command.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_core_info =
|
|
||||||
data_connectors.0.get(data_connector_name).ok_or_else(|| {
|
|
||||||
Error::UnknownCommandDataConnector {
|
|
||||||
command_name: command.name.clone(),
|
|
||||||
data_connector: data_connector_name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_link = data_connectors::DataConnectorLink::new(
|
|
||||||
data_connector_name.clone(),
|
|
||||||
&data_connector_core_info.inner,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match command.arguments.get(&argument_preset.argument) {
|
|
||||||
Some(argument) => {
|
|
||||||
let value_expression = resolve_value_expression_for_argument(
|
|
||||||
&argument_preset.argument,
|
|
||||||
&argument_preset.value,
|
|
||||||
&argument.argument_type,
|
|
||||||
source_argument_type,
|
|
||||||
&data_connector_link,
|
|
||||||
subgraph,
|
|
||||||
object_types,
|
|
||||||
scalar_types,
|
|
||||||
object_boolean_expression_types,
|
|
||||||
boolean_expression_types,
|
|
||||||
models,
|
|
||||||
data_connector_scalars,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// additionally typecheck literals
|
|
||||||
// we do this outside the argument resolve so that we can emit a command-specific error
|
|
||||||
// on typechecking failure
|
|
||||||
typecheck::typecheck_value_expression(
|
|
||||||
&argument.argument_type,
|
|
||||||
&argument_preset.value,
|
|
||||||
)
|
|
||||||
.map_err(|type_error| {
|
|
||||||
Error::CommandArgumentPresetTypeError {
|
|
||||||
command_name: command.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
type_error,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
argument_presets.insert(
|
|
||||||
argument_preset.argument.clone(),
|
|
||||||
(argument.argument_type.clone(), value_expression),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(Error::CommandArgumentPresetMismatch {
|
|
||||||
command_name: command.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let resolved_permission = CommandPermission {
|
|
||||||
allow_execution: command_permission.allow_execution,
|
|
||||||
argument_presets,
|
|
||||||
};
|
|
||||||
validated_permissions.insert(command_permission.role.clone(), resolved_permission);
|
|
||||||
}
|
|
||||||
Ok(validated_permissions)
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
use hasura_authn_core::Role;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::stages::commands;
|
||||||
|
use crate::types::permission::ValueExpression;
|
||||||
|
use crate::types::subgraph::QualifiedTypeReference;
|
||||||
|
use open_dds::arguments::ArgumentName;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub struct CommandWithPermissions {
|
||||||
|
pub command: commands::Command,
|
||||||
|
pub permissions: BTreeMap<Role, CommandPermission>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub struct CommandPermission {
|
||||||
|
pub allow_execution: bool,
|
||||||
|
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
mod types;
|
mod types;
|
||||||
use crate::helpers::typecheck;
|
|
||||||
use crate::stages::{
|
use crate::stages::{
|
||||||
boolean_expressions, data_connector_scalar_types, data_connectors, models, models_graphql,
|
boolean_expressions, data_connector_scalar_types, data_connectors, models_graphql,
|
||||||
object_boolean_expressions, object_types, relationships, scalar_types,
|
object_boolean_expressions, relationships, scalar_types,
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName};
|
use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName};
|
||||||
@ -11,22 +10,12 @@ pub use types::{
|
|||||||
FilterPermission, ModelPredicate, ModelTargetSource, ModelWithPermissions,
|
FilterPermission, ModelPredicate, ModelTargetSource, ModelWithPermissions,
|
||||||
PredicateRelationshipInfo, SelectPermission,
|
PredicateRelationshipInfo, SelectPermission,
|
||||||
};
|
};
|
||||||
|
mod model_permission;
|
||||||
|
|
||||||
use crate::helpers::argument::{
|
use crate::types::error::Error;
|
||||||
resolve_model_predicate_with_type, resolve_value_expression_for_argument,
|
|
||||||
};
|
|
||||||
use crate::types::error::{Error, TypePredicateError};
|
|
||||||
|
|
||||||
use crate::types::subgraph::Qualified;
|
use crate::types::subgraph::Qualified;
|
||||||
|
|
||||||
use ndc_models;
|
|
||||||
use open_dds::permissions::NullableModelPredicate;
|
|
||||||
use open_dds::{
|
|
||||||
arguments::ArgumentName,
|
|
||||||
permissions::{ModelPermissionsV1, Role},
|
|
||||||
types::FieldName,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// resolve model permissions
|
/// resolve model permissions
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||||
@ -86,7 +75,7 @@ pub fn resolve(
|
|||||||
})
|
})
|
||||||
.and_then(|bool_exp| bool_exp.graphql.as_ref());
|
.and_then(|bool_exp| bool_exp.graphql.as_ref());
|
||||||
|
|
||||||
let select_permissions = resolve_model_select_permissions(
|
let select_permissions = model_permission::resolve_model_select_permissions(
|
||||||
&model.model,
|
&model.model,
|
||||||
subgraph,
|
subgraph,
|
||||||
permissions,
|
permissions,
|
||||||
@ -109,228 +98,3 @@ pub fn resolve(
|
|||||||
}
|
}
|
||||||
Ok(models_with_permissions)
|
Ok(models_with_permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_model_predicate_with_model(
|
|
||||||
model_predicate: &open_dds::permissions::ModelPredicate,
|
|
||||||
model: &models::Model,
|
|
||||||
subgraph: &str,
|
|
||||||
boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>,
|
|
||||||
data_connectors: &data_connectors::DataConnectors,
|
|
||||||
data_connector_scalars: &BTreeMap<
|
|
||||||
Qualified<DataConnectorName>,
|
|
||||||
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
|
||||||
>,
|
|
||||||
fields: &IndexMap<FieldName, object_types::FieldDefinition>,
|
|
||||||
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
|
||||||
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
|
||||||
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
|
||||||
) -> Result<ModelPredicate, Error> {
|
|
||||||
let model_source = model
|
|
||||||
.source
|
|
||||||
.clone()
|
|
||||||
.ok_or(Error::ModelSourceRequiredForPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_name = &model_source.data_connector.name;
|
|
||||||
|
|
||||||
// get available scalars defined for this data connector
|
|
||||||
let scalars =
|
|
||||||
data_connector_scalars
|
|
||||||
.get(data_connector_name)
|
|
||||||
.ok_or(Error::TypePredicateError {
|
|
||||||
type_predicate_error: TypePredicateError::UnknownTypeDataConnector {
|
|
||||||
type_name: model.data_type.clone(),
|
|
||||||
data_connector: data_connector_name.clone(),
|
|
||||||
},
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// get the type that the expression is based on
|
|
||||||
let object_type_representation =
|
|
||||||
object_types
|
|
||||||
.get(&model.data_type)
|
|
||||||
.ok_or(Error::UnknownType {
|
|
||||||
data_type: model.data_type.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get field mappings of model data type
|
|
||||||
let object_types::TypeMapping::Object { field_mappings, .. } = model_source
|
|
||||||
.type_mappings
|
|
||||||
.get(&model.data_type)
|
|
||||||
.ok_or(Error::TypeMappingRequired {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
type_name: model.data_type.clone(),
|
|
||||||
data_connector: model_source.data_connector.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_core_info = data_connectors.0.get(data_connector_name).ok_or_else(|| {
|
|
||||||
Error::UnknownModelDataConnector {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
data_connector: data_connector_name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_link = data_connectors::DataConnectorLink::new(
|
|
||||||
data_connector_name.clone(),
|
|
||||||
&data_connector_core_info.inner,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
resolve_model_predicate_with_type(
|
|
||||||
model_predicate,
|
|
||||||
&model.data_type,
|
|
||||||
object_type_representation,
|
|
||||||
boolean_expression_graphql,
|
|
||||||
field_mappings,
|
|
||||||
&data_connector_link,
|
|
||||||
subgraph,
|
|
||||||
scalars,
|
|
||||||
object_types,
|
|
||||||
scalar_types,
|
|
||||||
boolean_expression_types,
|
|
||||||
models,
|
|
||||||
fields,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the ndc_models::Type for an argument if it is available
|
|
||||||
fn get_model_source_argument<'a>(
|
|
||||||
argument_name: &'a ArgumentName,
|
|
||||||
model: &'a models::Model,
|
|
||||||
) -> Option<&'a ndc_models::Type> {
|
|
||||||
model
|
|
||||||
.source
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|source| {
|
|
||||||
source
|
|
||||||
.argument_mappings
|
|
||||||
.get(argument_name)
|
|
||||||
.map(|connector_argument_name| source.source_arguments.get(connector_argument_name))
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_model_select_permissions(
|
|
||||||
model: &models::Model,
|
|
||||||
subgraph: &str,
|
|
||||||
model_permissions: &ModelPermissionsV1,
|
|
||||||
boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>,
|
|
||||||
data_connectors: &data_connectors::DataConnectors,
|
|
||||||
data_connector_scalars: &BTreeMap<
|
|
||||||
Qualified<DataConnectorName>,
|
|
||||||
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
|
||||||
>,
|
|
||||||
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
|
||||||
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
|
||||||
object_boolean_expression_types: &BTreeMap<
|
|
||||||
Qualified<CustomTypeName>,
|
|
||||||
object_boolean_expressions::ObjectBooleanExpressionType,
|
|
||||||
>,
|
|
||||||
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
|
||||||
) -> Result<BTreeMap<Role, SelectPermission>, Error> {
|
|
||||||
let mut validated_permissions = BTreeMap::new();
|
|
||||||
for model_permission in &model_permissions.permissions {
|
|
||||||
if let Some(select) = &model_permission.select {
|
|
||||||
let resolved_predicate = match &select.filter {
|
|
||||||
NullableModelPredicate::NotNull(model_predicate) => {
|
|
||||||
resolve_model_predicate_with_model(
|
|
||||||
model_predicate,
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
boolean_expression_graphql,
|
|
||||||
data_connectors,
|
|
||||||
data_connector_scalars,
|
|
||||||
&model.type_fields,
|
|
||||||
object_types,
|
|
||||||
scalar_types,
|
|
||||||
boolean_expression_types,
|
|
||||||
models,
|
|
||||||
)
|
|
||||||
.map(FilterPermission::Filter)?
|
|
||||||
}
|
|
||||||
NullableModelPredicate::Null(()) => FilterPermission::AllowAll,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut argument_presets = BTreeMap::new();
|
|
||||||
|
|
||||||
for argument_preset in &select.argument_presets {
|
|
||||||
if argument_presets.contains_key(&argument_preset.argument) {
|
|
||||||
return Err(Error::DuplicateModelArgumentPreset {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_argument_type =
|
|
||||||
get_model_source_argument(&argument_preset.argument, model);
|
|
||||||
|
|
||||||
let data_connector_name = model
|
|
||||||
.source
|
|
||||||
.as_ref()
|
|
||||||
.map(|source| &source.data_connector.name)
|
|
||||||
.ok_or(Error::ModelSourceRequiredForPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data_connector_core_info = data_connectors.0.get(data_connector_name).unwrap();
|
|
||||||
let data_connector_link = data_connectors::DataConnectorLink::new(
|
|
||||||
data_connector_name.clone(),
|
|
||||||
&data_connector_core_info.inner,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match model.arguments.get(&argument_preset.argument) {
|
|
||||||
Some(argument) => {
|
|
||||||
let value_expression = resolve_value_expression_for_argument(
|
|
||||||
&argument_preset.argument,
|
|
||||||
&argument_preset.value,
|
|
||||||
&argument.argument_type,
|
|
||||||
source_argument_type,
|
|
||||||
&data_connector_link,
|
|
||||||
subgraph,
|
|
||||||
object_types,
|
|
||||||
scalar_types,
|
|
||||||
object_boolean_expression_types,
|
|
||||||
boolean_expression_types,
|
|
||||||
models,
|
|
||||||
data_connector_scalars,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// additionally typecheck literals
|
|
||||||
// we do this outside the argument resolve so that we can emit a model-specific error
|
|
||||||
// on typechecking failure
|
|
||||||
typecheck::typecheck_value_expression(
|
|
||||||
&argument.argument_type,
|
|
||||||
&argument_preset.value,
|
|
||||||
)
|
|
||||||
.map_err(|type_error| {
|
|
||||||
Error::ModelArgumentPresetTypeError {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
type_error,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
argument_presets.insert(
|
|
||||||
argument_preset.argument.clone(),
|
|
||||||
(argument.argument_type.clone(), value_expression),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(Error::ModelArgumentPresetMismatch {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
argument_name: argument_preset.argument.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let resolved_permission = SelectPermission {
|
|
||||||
filter: resolved_predicate.clone(),
|
|
||||||
argument_presets,
|
|
||||||
};
|
|
||||||
validated_permissions.insert(model_permission.role.clone(), resolved_permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(validated_permissions)
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,249 @@
|
|||||||
|
use super::types::{FilterPermission, ModelPredicate, SelectPermission};
|
||||||
|
use crate::helpers::typecheck;
|
||||||
|
use crate::stages::{
|
||||||
|
boolean_expressions, data_connector_scalar_types, data_connectors, models, models_graphql,
|
||||||
|
object_boolean_expressions, object_types, relationships, scalar_types,
|
||||||
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use crate::helpers::argument::{
|
||||||
|
resolve_model_predicate_with_type, resolve_value_expression_for_argument,
|
||||||
|
};
|
||||||
|
use crate::types::error::{Error, TypePredicateError};
|
||||||
|
|
||||||
|
use crate::types::subgraph::Qualified;
|
||||||
|
|
||||||
|
use ndc_models;
|
||||||
|
use open_dds::permissions::NullableModelPredicate;
|
||||||
|
use open_dds::{
|
||||||
|
arguments::ArgumentName,
|
||||||
|
permissions::{ModelPermissionsV1, Role},
|
||||||
|
types::FieldName,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn resolve_model_predicate_with_model(
|
||||||
|
model_predicate: &open_dds::permissions::ModelPredicate,
|
||||||
|
model: &models::Model,
|
||||||
|
subgraph: &str,
|
||||||
|
boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>,
|
||||||
|
data_connectors: &data_connectors::DataConnectors,
|
||||||
|
data_connector_scalars: &BTreeMap<
|
||||||
|
Qualified<DataConnectorName>,
|
||||||
|
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
||||||
|
>,
|
||||||
|
fields: &IndexMap<FieldName, object_types::FieldDefinition>,
|
||||||
|
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
|
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
||||||
|
) -> Result<ModelPredicate, Error> {
|
||||||
|
let model_source = model
|
||||||
|
.source
|
||||||
|
.clone()
|
||||||
|
.ok_or(Error::ModelSourceRequiredForPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_name = &model_source.data_connector.name;
|
||||||
|
|
||||||
|
// get available scalars defined for this data connector
|
||||||
|
let scalars =
|
||||||
|
data_connector_scalars
|
||||||
|
.get(data_connector_name)
|
||||||
|
.ok_or(Error::TypePredicateError {
|
||||||
|
type_predicate_error: TypePredicateError::UnknownTypeDataConnector {
|
||||||
|
type_name: model.data_type.clone(),
|
||||||
|
data_connector: data_connector_name.clone(),
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// get the type that the expression is based on
|
||||||
|
let object_type_representation =
|
||||||
|
object_types
|
||||||
|
.get(&model.data_type)
|
||||||
|
.ok_or(Error::UnknownType {
|
||||||
|
data_type: model.data_type.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get field mappings of model data type
|
||||||
|
let object_types::TypeMapping::Object { field_mappings, .. } = model_source
|
||||||
|
.type_mappings
|
||||||
|
.get(&model.data_type)
|
||||||
|
.ok_or(Error::TypeMappingRequired {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
type_name: model.data_type.clone(),
|
||||||
|
data_connector: model_source.data_connector.name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_core_info = data_connectors.0.get(data_connector_name).ok_or_else(|| {
|
||||||
|
Error::UnknownModelDataConnector {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
data_connector: data_connector_name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_link = data_connectors::DataConnectorLink::new(
|
||||||
|
data_connector_name.clone(),
|
||||||
|
&data_connector_core_info.inner,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
resolve_model_predicate_with_type(
|
||||||
|
model_predicate,
|
||||||
|
&model.data_type,
|
||||||
|
object_type_representation,
|
||||||
|
boolean_expression_graphql,
|
||||||
|
field_mappings,
|
||||||
|
&data_connector_link,
|
||||||
|
subgraph,
|
||||||
|
scalars,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
|
boolean_expression_types,
|
||||||
|
models,
|
||||||
|
fields,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the ndc_models::Type for an argument if it is available
|
||||||
|
fn get_model_source_argument<'a>(
|
||||||
|
argument_name: &'a ArgumentName,
|
||||||
|
model: &'a models::Model,
|
||||||
|
) -> Option<&'a ndc_models::Type> {
|
||||||
|
model
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|source| {
|
||||||
|
source
|
||||||
|
.argument_mappings
|
||||||
|
.get(argument_name)
|
||||||
|
.map(|connector_argument_name| source.source_arguments.get(connector_argument_name))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_model_select_permissions(
|
||||||
|
model: &models::Model,
|
||||||
|
subgraph: &str,
|
||||||
|
model_permissions: &ModelPermissionsV1,
|
||||||
|
boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>,
|
||||||
|
data_connectors: &data_connectors::DataConnectors,
|
||||||
|
data_connector_scalars: &BTreeMap<
|
||||||
|
Qualified<DataConnectorName>,
|
||||||
|
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
|
||||||
|
>,
|
||||||
|
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
|
||||||
|
object_boolean_expression_types: &BTreeMap<
|
||||||
|
Qualified<CustomTypeName>,
|
||||||
|
object_boolean_expressions::ObjectBooleanExpressionType,
|
||||||
|
>,
|
||||||
|
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
|
||||||
|
) -> Result<BTreeMap<Role, SelectPermission>, Error> {
|
||||||
|
let mut validated_permissions = BTreeMap::new();
|
||||||
|
for model_permission in &model_permissions.permissions {
|
||||||
|
if let Some(select) = &model_permission.select {
|
||||||
|
let resolved_predicate = match &select.filter {
|
||||||
|
NullableModelPredicate::NotNull(model_predicate) => {
|
||||||
|
resolve_model_predicate_with_model(
|
||||||
|
model_predicate,
|
||||||
|
model,
|
||||||
|
subgraph,
|
||||||
|
boolean_expression_graphql,
|
||||||
|
data_connectors,
|
||||||
|
data_connector_scalars,
|
||||||
|
&model.type_fields,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
|
boolean_expression_types,
|
||||||
|
models,
|
||||||
|
)
|
||||||
|
.map(FilterPermission::Filter)?
|
||||||
|
}
|
||||||
|
NullableModelPredicate::Null(()) => FilterPermission::AllowAll,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut argument_presets = BTreeMap::new();
|
||||||
|
|
||||||
|
for argument_preset in &select.argument_presets {
|
||||||
|
if argument_presets.contains_key(&argument_preset.argument) {
|
||||||
|
return Err(Error::DuplicateModelArgumentPreset {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_argument_type =
|
||||||
|
get_model_source_argument(&argument_preset.argument, model);
|
||||||
|
|
||||||
|
let data_connector_name = model
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.map(|source| &source.data_connector.name)
|
||||||
|
.ok_or(Error::ModelSourceRequiredForPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_connector_core_info = data_connectors.0.get(data_connector_name).unwrap();
|
||||||
|
let data_connector_link = data_connectors::DataConnectorLink::new(
|
||||||
|
data_connector_name.clone(),
|
||||||
|
&data_connector_core_info.inner,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match model.arguments.get(&argument_preset.argument) {
|
||||||
|
Some(argument) => {
|
||||||
|
let value_expression = resolve_value_expression_for_argument(
|
||||||
|
&argument_preset.argument,
|
||||||
|
&argument_preset.value,
|
||||||
|
&argument.argument_type,
|
||||||
|
source_argument_type,
|
||||||
|
&data_connector_link,
|
||||||
|
subgraph,
|
||||||
|
object_types,
|
||||||
|
scalar_types,
|
||||||
|
object_boolean_expression_types,
|
||||||
|
boolean_expression_types,
|
||||||
|
models,
|
||||||
|
data_connector_scalars,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// additionally typecheck literals
|
||||||
|
// we do this outside the argument resolve so that we can emit a model-specific error
|
||||||
|
// on typechecking failure
|
||||||
|
typecheck::typecheck_value_expression(
|
||||||
|
&argument.argument_type,
|
||||||
|
&argument_preset.value,
|
||||||
|
)
|
||||||
|
.map_err(|type_error| {
|
||||||
|
Error::ModelArgumentPresetTypeError {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
type_error,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
argument_presets.insert(
|
||||||
|
argument_preset.argument.clone(),
|
||||||
|
(argument.argument_type.clone(), value_expression),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error::ModelArgumentPresetMismatch {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
argument_name: argument_preset.argument.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolved_permission = SelectPermission {
|
||||||
|
filter: resolved_predicate.clone(),
|
||||||
|
argument_presets,
|
||||||
|
};
|
||||||
|
validated_permissions.insert(model_permission.role.clone(), resolved_permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(validated_permissions)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user