Skip to content

Commit

Permalink
add basic role based permissions for commands (hasura#79)
Browse files Browse the repository at this point in the history
* initial commit

* lint

* review comments

* update tests

* lint

GitOrigin-RevId: 82211ef7913a1aedd10ffd72b3a813d2101c39d1
  • Loading branch information
purugupta99 authored and hgiasac committed Dec 19, 2023
1 parent c8829bf commit 4ebd702
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 126 deletions.
41 changes: 41 additions & 0 deletions v3/gds/src/metadata/resolved.rs
Expand Up @@ -245,6 +245,10 @@ pub enum Error {
model_name: ModelName,
operator_name: OperatorName,
},
#[error("unknown command used in command permissions definition: {command_name:}")]
UnknownCommandInCommandPermissions { command_name: CommandName },
#[error("multiple permissions defined for command: {command_name:}")]
DuplicateCommandPermission { command_name: CommandName },

#[error("{message:}")]
UnsupportedFeature { message: String },
Expand Down Expand Up @@ -582,6 +586,7 @@ pub struct Command {
pub arguments: IndexMap<ArgumentName, Type>,
pub graphql_api: Option<CommandGraphQlDefinition>,
pub source: Option<CommandSource>,
pub permissions: Option<HashMap<Role, CommandPermission>>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
Expand All @@ -592,6 +597,11 @@ pub struct CommandSource {
pub argument_mappings: HashMap<ArgumentName, String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct CommandPermission {
pub allow_execution: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct SelectPermission {
// Missing filter implies all rows are selectable
Expand Down Expand Up @@ -827,6 +837,22 @@ pub fn resolve_metadata(metadata: &open_dds::Metadata) -> Result<Metadata, Error
}
}

for command_permissions in &metadata_accessor.command_permissions {
let command_name = &command_permissions.command_name;
let command = commands.get_mut(command_name).ok_or_else(|| {
Error::UnknownCommandInCommandPermissions {
command_name: command_name.clone(),
}
})?;
if command.permissions.is_none() {
command.permissions = Some(resolve_command_permissions(command_permissions)?);
} else {
return Err(Error::DuplicateCommandPermission {
command_name: command_name.clone(),
});
}
}

for select_permissions in &metadata_accessor.select_permissions {
let model_name = &select_permissions.model_name;
let model = models.get_mut(model_name).ok_or_else(|| {
Expand Down Expand Up @@ -1036,6 +1062,7 @@ fn resolve_command(
arguments,
graphql_api: command.graphql.clone(),
source: None,
permissions: None,
})
}

Expand Down Expand Up @@ -1202,6 +1229,20 @@ fn resolve_command_source(
Ok(())
}

fn resolve_command_permissions(
permissions: &open_dds::data_specification::CommandPermissions,
) -> Result<HashMap<Role, CommandPermission>, Error> {
let mut validated_permissions = HashMap::new();
for (role, permission) in &permissions.permissions {
// TODO: Use the permission predicates/presets
let resolved_permission = CommandPermission {
allow_execution: permission.allow_execution,
};
validated_permissions.insert(role.clone(), resolved_permission);
}
Ok(validated_permissions)
}

fn resolve_model_select_permissions(
model: &Model,
select_permissions: &open_dds::data_specification::ModelSelectPermissions,
Expand Down
5 changes: 3 additions & 2 deletions v3/gds/src/schema/operations/commands.rs
Expand Up @@ -23,6 +23,7 @@ use tracing_util::FutureTracing;
use super::response_processing::process_command_rows;
use super::Error;
use crate::metadata::resolved;
use crate::schema::operations::permissions;
use crate::schema::types::command_arguments;
use crate::schema::types::{self, output_type::get_output_type, Annotation};
use crate::schema::GDS;
Expand Down Expand Up @@ -104,7 +105,7 @@ pub(crate) fn command_field(
arguments.insert(field_name, input_field);
}

let field = gql_schema::Namespaced::new_allow_all(
let field = gql_schema::Namespaced::new_conditional(
gql_schema::Field::new(
command_field_name.clone(),
None,
Expand All @@ -117,7 +118,7 @@ pub(crate) fn command_field(
arguments,
gql_schema::DeprecationStatus::NotDeprecated,
),
None,
permissions::get_command_namespace_annotations(command),
);
Ok((command_field_name, field))
}
Expand Down
18 changes: 18 additions & 0 deletions v3/gds/src/schema/operations/permissions.rs
Expand Up @@ -220,3 +220,21 @@ pub(crate) fn get_select_namespace_annotations(
})
.unwrap_or_else(HashMap::new)
}

/// Build namespace annotation for commands
pub(crate) fn get_command_namespace_annotations(
command: &resolved::Command,
) -> HashMap<Role, Option<types::NamespaceAnnotation>> {
let mut permissions = HashMap::new();
match &command.permissions {
Some(command_permissions) => {
for (role, permission) in command_permissions {
if permission.allow_execution {
permissions.insert(role.clone(), None);
}
}
}
None => {}
}
permissions
}
129 changes: 5 additions & 124 deletions v3/gds/tests/execute/introspection_user_1/expected.json
Expand Up @@ -257,29 +257,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CommandArticle",
"description": null,
"fields": [
{
"name": "_no_fields_accessible",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
Expand Down Expand Up @@ -316,46 +293,13 @@
"description": null,
"fields": [
{
"name": "updateArticleTitleById",
"name": "_no_fields_accessible",
"description": null,
"args": [
{
"name": "article_id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "title",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CommandArticle",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
Expand Down Expand Up @@ -495,69 +439,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "getArticleById",
"description": null,
"args": [
{
"name": "article_id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CommandArticle",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "getLatestArticle",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CommandArticle",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "getLatestArticleId",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": null,
Expand Down
46 changes: 46 additions & 0 deletions v3/gds/tests/schema.json
Expand Up @@ -895,6 +895,25 @@
"title",
"author_id"
]
},
"user": {
"fields": [
"article_id",
"title",
"author_id"
]
}
}
},
{
"kind": "CommandPermissions",
"commandName": "get_article_by_id",
"permissions": {
"admin": {
"allowExecution": true
},
"user": {
"allowExecution": true
}
}
},
Expand Down Expand Up @@ -935,6 +954,15 @@
"rootFieldKind": "Query"
}
},
{
"kind": "CommandPermissions",
"commandName": "get_latest_article",
"permissions": {
"admin": {
"allowExecution": true
}
}
},
{
"kind": "Command",
"name": "get_latest_article",
Expand Down Expand Up @@ -964,6 +992,15 @@
"rootFieldKind": "Query"
}
},
{
"kind": "CommandPermissions",
"commandName": "get_latest_article_id",
"permissions": {
"admin": {
"allowExecution": true
}
}
},
{
"kind": "Command",
"name": "get_latest_article_id",
Expand All @@ -978,6 +1015,15 @@
"rootFieldKind": "Query"
}
},
{
"kind": "CommandPermissions",
"commandName": "update_article_title_by_id",
"permissions": {
"admin": {
"allowExecution": true
}
}
},
{
"kind": "Command",
"name": "update_article_title_by_id",
Expand Down

0 comments on commit 4ebd702

Please sign in to comment.