From 2312b18dcf714bc0dac5b48149e57c2291081d0b Mon Sep 17 00:00:00 2001 From: kjdev Date: Mon, 1 Jun 2026 13:01:32 +0900 Subject: [PATCH 1/4] chore: re-import Cedar evaluator sources from nxe-cedar v0.3.0 Re-snapshot src/cedar/ from upstream tag v0.3.0 (4f120bd1d1c05868b09e307f5636c5d52a604a2b), applying the established mechanical rewrites (nxe_cedar_ -> php_cedar_, ngx_ types/functions -> php_cedar_compat equivalents). No new ngx dependencies are introduced, so php_cedar_compat.* and config.m4 are unchanged. Brings in the datetime / duration extension types, bare-context record materialization, attribute/has/in resolution on request entity literals, total == / != type-mismatch evaluation, lexer error-path initialization, a make_ip NULL guard, and const-qualified input APIs. --- src/cedar/UPSTREAM.md | 5 +- src/cedar/php_cedar_eval.c | 347 ++++++++++++++---- src/cedar/php_cedar_eval.h | 158 +++++--- src/cedar/php_cedar_expr.c | 674 +++++++++++++++++++++++++++++++++-- src/cedar/php_cedar_expr.h | 26 +- src/cedar/php_cedar_lexer.c | 7 + src/cedar/php_cedar_parser.c | 78 +++- src/cedar/php_cedar_types.h | 104 ++++-- src/cedar/php_cedar_util.h | 2 +- 9 files changed, 1198 insertions(+), 203 deletions(-) diff --git a/src/cedar/UPSTREAM.md b/src/cedar/UPSTREAM.md index d1edc62..970a8ce 100644 --- a/src/cedar/UPSTREAM.md +++ b/src/cedar/UPSTREAM.md @@ -8,9 +8,9 @@ symbols and dependencies fit a PHP extension. ## Snapshot commit - Upstream repository: -- Commit SHA: `cd3d1df5c5642a75b27f40fa502022c864272ed3` +- Commit SHA: `4f120bd1d1c05868b09e307f5636c5d52a604a2b` (tag `v0.3.0`) - Source path: `src/` -- Snapshot date: 2026-05-27 +- Snapshot date: 2026-06-01 ## File mapping @@ -63,7 +63,6 @@ needed, it should go to upstream first and come back via a fresh import. The features below follow whatever the snapshot supports; gaps listed here are upstream limitations that this extension does **not** plug: -- No `datetime` / `duration` types or their methods - No entity tag operators (`hasTag` / `getTag`) - No policy templates (`?principal`, `?resource`) - No schema validation diff --git a/src/cedar/php_cedar_eval.c b/src/cedar/php_cedar_eval.c index 2b63023..90c52e2 100644 --- a/src/cedar/php_cedar_eval.c +++ b/src/cedar/php_cedar_eval.c @@ -25,9 +25,9 @@ * handled inline without registration. */ php_cedar_int_t -php_cedar_entity_in_target(php_cedar_str_t *entity_type, php_cedar_str_t *entity_id, - php_cedar_array_t *parents, - php_cedar_str_t *target_type, php_cedar_str_t *target_id) +php_cedar_entity_in_target(const php_cedar_str_t *entity_type, + const php_cedar_str_t *entity_id, php_cedar_array_t *parents, + const php_cedar_str_t *target_type, const php_cedar_str_t *target_id) { php_cedar_entity_ref_t *elts; php_cedar_uint_t i; @@ -91,7 +91,7 @@ php_cedar_eval_ctx_lookup_parents(php_cedar_eval_ctx_t *ctx, static php_cedar_int_t php_cedar_scope_matches(php_cedar_scope_t *scope, - php_cedar_str_t *entity_type, php_cedar_str_t *entity_id, + const php_cedar_str_t *entity_type, const php_cedar_str_t *entity_id, php_cedar_array_t *parents) { php_cedar_node_t *target, **elts; @@ -218,7 +218,7 @@ php_cedar_condition_matches(php_cedar_condition_t *cond, * reject), 0 otherwise. Tolerates a NULL attrs (treated as empty). */ static php_cedar_int_t -php_cedar_attrs_has_name(php_cedar_array_t *attrs, php_cedar_str_t *name) +php_cedar_attrs_has_name(php_cedar_array_t *attrs, const php_cedar_str_t *name) { php_cedar_attr_t *elts; php_cedar_uint_t i; @@ -240,7 +240,7 @@ php_cedar_attrs_has_name(php_cedar_array_t *attrs, php_cedar_str_t *name) static php_cedar_int_t php_cedar_eval_ctx_add_str_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { php_cedar_attr_t *attr; @@ -263,7 +263,7 @@ php_cedar_eval_ctx_add_str_attr(php_cedar_array_t *attrs, static php_cedar_int_t php_cedar_eval_ctx_add_long_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, int64_t value) + const php_cedar_str_t *name, int64_t value) { php_cedar_attr_t *attr; @@ -286,7 +286,7 @@ php_cedar_eval_ctx_add_long_attr(php_cedar_array_t *attrs, static php_cedar_int_t php_cedar_eval_ctx_add_bool_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, php_cedar_flag_t value) + const php_cedar_str_t *name, php_cedar_flag_t value) { php_cedar_attr_t *attr; @@ -315,7 +315,7 @@ php_cedar_eval_ctx_add_bool_attr(php_cedar_array_t *attrs, */ static php_cedar_int_t php_cedar_eval_ctx_add_ip_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { php_cedar_attr_t *attr; php_cedar_value_t ip_val; @@ -348,7 +348,7 @@ php_cedar_eval_ctx_add_ip_attr(php_cedar_array_t *attrs, */ static php_cedar_int_t php_cedar_eval_ctx_add_decimal_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { php_cedar_attr_t *attr; php_cedar_value_t dec_val; @@ -374,6 +374,73 @@ php_cedar_eval_ctx_add_decimal_attr(php_cedar_array_t *attrs, } +/* + * Datetime attributes are eagerly parsed at injection time, mirroring + * the IP / decimal paths: malformed ISO 8601 input is rejected with + * PHP_CEDAR_ERROR up front rather than surfacing as a silent evaluation error + * later. + */ +static php_cedar_int_t +php_cedar_eval_ctx_add_datetime_attr(php_cedar_array_t *attrs, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + php_cedar_attr_t *attr; + php_cedar_value_t dt_val; + + if (php_cedar_attrs_has_name(attrs, name)) { + return PHP_CEDAR_ERROR; + } + + dt_val = php_cedar_make_datetime(value); + if (dt_val.type == PHP_CEDAR_RVAL_ERROR) { + return PHP_CEDAR_ERROR; + } + + attr = php_cedar_array_push(attrs); + if (attr == NULL) { + return PHP_CEDAR_ERROR; + } + + attr->name = *name; + attr->value = dt_val; + + return PHP_CEDAR_OK; +} + + +/* + * Duration attributes are eagerly parsed at injection time, mirroring + * the datetime path: malformed duration strings are rejected with + * PHP_CEDAR_ERROR up front. + */ +static php_cedar_int_t +php_cedar_eval_ctx_add_duration_attr(php_cedar_array_t *attrs, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + php_cedar_attr_t *attr; + php_cedar_value_t dur_val; + + if (php_cedar_attrs_has_name(attrs, name)) { + return PHP_CEDAR_ERROR; + } + + dur_val = php_cedar_make_duration(value); + if (dur_val.type == PHP_CEDAR_RVAL_ERROR) { + return PHP_CEDAR_ERROR; + } + + attr = php_cedar_array_push(attrs); + if (attr == NULL) { + return PHP_CEDAR_ERROR; + } + + attr->name = *name; + attr->value = dur_val; + + return PHP_CEDAR_OK; +} + + /* * Record handle. * @@ -420,7 +487,7 @@ php_cedar_record_create(php_cedar_pool_t *pool, php_cedar_uint_t depth) */ static php_cedar_record_t * php_cedar_eval_ctx_add_record_attr(php_cedar_array_t *attrs, php_cedar_pool_t *pool, - php_cedar_str_t *name) + const php_cedar_str_t *name) { php_cedar_attr_t *attr; php_cedar_record_t *rec; @@ -496,7 +563,7 @@ php_cedar_eval_ctx_create(php_cedar_pool_t *pool) void php_cedar_eval_ctx_set_principal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { ctx->principal_type = *type; ctx->principal_id = *id; @@ -505,7 +572,7 @@ php_cedar_eval_ctx_set_principal(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_str_attr(ctx->principal_attrs, name, value); @@ -514,7 +581,7 @@ php_cedar_eval_ctx_add_principal_attr(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_long(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, int64_t value) + const php_cedar_str_t *name, int64_t value) { return php_cedar_eval_ctx_add_long_attr(ctx->principal_attrs, name, value); @@ -523,7 +590,7 @@ php_cedar_eval_ctx_add_principal_attr_long(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_bool(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_flag_t value) + const php_cedar_str_t *name, php_cedar_flag_t value) { return php_cedar_eval_ctx_add_bool_attr(ctx->principal_attrs, name, value); @@ -532,7 +599,7 @@ php_cedar_eval_ctx_add_principal_attr_bool(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_ip(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_ip_attr(ctx->principal_attrs, name, value); @@ -541,16 +608,34 @@ php_cedar_eval_ctx_add_principal_attr_ip(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_decimal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_decimal_attr(ctx->principal_attrs, name, value); } +php_cedar_int_t +php_cedar_eval_ctx_add_principal_attr_datetime(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_datetime_attr(ctx->principal_attrs, + name, value); +} + + +php_cedar_int_t +php_cedar_eval_ctx_add_principal_attr_duration(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_duration_attr(ctx->principal_attrs, + name, value); +} + + void php_cedar_eval_ctx_set_action(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { ctx->action_type = *type; ctx->action_id = *id; @@ -559,7 +644,7 @@ php_cedar_eval_ctx_set_action(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_str_attr(ctx->action_attrs, name, value); @@ -568,7 +653,7 @@ php_cedar_eval_ctx_add_action_attr(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr_long(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, int64_t value) + const php_cedar_str_t *name, int64_t value) { return php_cedar_eval_ctx_add_long_attr(ctx->action_attrs, name, value); @@ -577,7 +662,7 @@ php_cedar_eval_ctx_add_action_attr_long(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr_bool(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_flag_t value) + const php_cedar_str_t *name, php_cedar_flag_t value) { return php_cedar_eval_ctx_add_bool_attr(ctx->action_attrs, name, value); @@ -586,7 +671,7 @@ php_cedar_eval_ctx_add_action_attr_bool(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr_ip(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_ip_attr(ctx->action_attrs, name, value); @@ -595,16 +680,34 @@ php_cedar_eval_ctx_add_action_attr_ip(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr_decimal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_decimal_attr(ctx->action_attrs, name, value); } +php_cedar_int_t +php_cedar_eval_ctx_add_action_attr_datetime(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_datetime_attr(ctx->action_attrs, + name, value); +} + + +php_cedar_int_t +php_cedar_eval_ctx_add_action_attr_duration(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_duration_attr(ctx->action_attrs, + name, value); +} + + void php_cedar_eval_ctx_set_resource(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { ctx->resource_type = *type; ctx->resource_id = *id; @@ -613,7 +716,7 @@ php_cedar_eval_ctx_set_resource(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_str_attr(ctx->resource_attrs, name, value); @@ -622,7 +725,7 @@ php_cedar_eval_ctx_add_resource_attr(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_long(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, int64_t value) + const php_cedar_str_t *name, int64_t value) { return php_cedar_eval_ctx_add_long_attr(ctx->resource_attrs, name, value); @@ -631,7 +734,7 @@ php_cedar_eval_ctx_add_resource_attr_long(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_bool(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_flag_t value) + const php_cedar_str_t *name, php_cedar_flag_t value) { return php_cedar_eval_ctx_add_bool_attr(ctx->resource_attrs, name, value); @@ -640,7 +743,7 @@ php_cedar_eval_ctx_add_resource_attr_bool(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_ip(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_ip_attr(ctx->resource_attrs, name, value); @@ -649,16 +752,34 @@ php_cedar_eval_ctx_add_resource_attr_ip(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_decimal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_decimal_attr(ctx->resource_attrs, name, value); } +php_cedar_int_t +php_cedar_eval_ctx_add_resource_attr_datetime(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_datetime_attr(ctx->resource_attrs, + name, value); +} + + +php_cedar_int_t +php_cedar_eval_ctx_add_resource_attr_duration(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_duration_attr(ctx->resource_attrs, + name, value); +} + + php_cedar_int_t php_cedar_eval_ctx_add_context_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_str_attr(ctx->context_attrs, name, value); @@ -667,7 +788,7 @@ php_cedar_eval_ctx_add_context_attr(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_context_attr_long(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, int64_t value) + const php_cedar_str_t *name, int64_t value) { return php_cedar_eval_ctx_add_long_attr(ctx->context_attrs, name, value); @@ -676,7 +797,7 @@ php_cedar_eval_ctx_add_context_attr_long(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_context_attr_bool(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_flag_t value) + const php_cedar_str_t *name, php_cedar_flag_t value) { return php_cedar_eval_ctx_add_bool_attr(ctx->context_attrs, name, value); @@ -685,7 +806,7 @@ php_cedar_eval_ctx_add_context_attr_bool(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_context_attr_ip(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_ip_attr(ctx->context_attrs, name, value); @@ -694,16 +815,34 @@ php_cedar_eval_ctx_add_context_attr_ip(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_context_attr_decimal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value) + const php_cedar_str_t *name, const php_cedar_str_t *value) { return php_cedar_eval_ctx_add_decimal_attr(ctx->context_attrs, name, value); } +php_cedar_int_t +php_cedar_eval_ctx_add_context_attr_datetime(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_datetime_attr(ctx->context_attrs, + name, value); +} + + +php_cedar_int_t +php_cedar_eval_ctx_add_context_attr_duration(php_cedar_eval_ctx_t *ctx, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + return php_cedar_eval_ctx_add_duration_attr(ctx->context_attrs, + name, value); +} + + php_cedar_record_t * php_cedar_eval_ctx_add_principal_attr_record(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { return php_cedar_eval_ctx_add_record_attr(ctx->principal_attrs, ctx->pool, name); @@ -712,7 +851,7 @@ php_cedar_eval_ctx_add_principal_attr_record(php_cedar_eval_ctx_t *ctx, php_cedar_record_t * php_cedar_eval_ctx_add_action_attr_record(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { return php_cedar_eval_ctx_add_record_attr(ctx->action_attrs, ctx->pool, name); @@ -721,7 +860,7 @@ php_cedar_eval_ctx_add_action_attr_record(php_cedar_eval_ctx_t *ctx, php_cedar_record_t * php_cedar_eval_ctx_add_resource_attr_record(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { return php_cedar_eval_ctx_add_record_attr(ctx->resource_attrs, ctx->pool, name); @@ -730,7 +869,7 @@ php_cedar_eval_ctx_add_resource_attr_record(php_cedar_eval_ctx_t *ctx, php_cedar_record_t * php_cedar_eval_ctx_add_context_attr_record(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { return php_cedar_eval_ctx_add_record_attr(ctx->context_attrs, ctx->pool, name); @@ -738,8 +877,8 @@ php_cedar_eval_ctx_add_context_attr_record(php_cedar_eval_ctx_t *ctx, php_cedar_int_t -php_cedar_record_add_str(php_cedar_record_t *rec, php_cedar_str_t *name, - php_cedar_str_t *value) +php_cedar_record_add_str(php_cedar_record_t *rec, const php_cedar_str_t *name, + const php_cedar_str_t *value) { if (rec == NULL) { return PHP_CEDAR_ERROR; @@ -749,7 +888,7 @@ php_cedar_record_add_str(php_cedar_record_t *rec, php_cedar_str_t *name, php_cedar_int_t -php_cedar_record_add_long(php_cedar_record_t *rec, php_cedar_str_t *name, +php_cedar_record_add_long(php_cedar_record_t *rec, const php_cedar_str_t *name, int64_t value) { if (rec == NULL) { @@ -760,7 +899,7 @@ php_cedar_record_add_long(php_cedar_record_t *rec, php_cedar_str_t *name, php_cedar_int_t -php_cedar_record_add_bool(php_cedar_record_t *rec, php_cedar_str_t *name, +php_cedar_record_add_bool(php_cedar_record_t *rec, const php_cedar_str_t *name, php_cedar_flag_t value) { if (rec == NULL) { @@ -771,8 +910,8 @@ php_cedar_record_add_bool(php_cedar_record_t *rec, php_cedar_str_t *name, php_cedar_int_t -php_cedar_record_add_ip(php_cedar_record_t *rec, php_cedar_str_t *name, - php_cedar_str_t *value) +php_cedar_record_add_ip(php_cedar_record_t *rec, const php_cedar_str_t *name, + const php_cedar_str_t *value) { if (rec == NULL) { return PHP_CEDAR_ERROR; @@ -782,8 +921,8 @@ php_cedar_record_add_ip(php_cedar_record_t *rec, php_cedar_str_t *name, php_cedar_int_t -php_cedar_record_add_decimal(php_cedar_record_t *rec, php_cedar_str_t *name, - php_cedar_str_t *value) +php_cedar_record_add_decimal(php_cedar_record_t *rec, + const php_cedar_str_t *name, const php_cedar_str_t *value) { if (rec == NULL) { return PHP_CEDAR_ERROR; @@ -792,8 +931,30 @@ php_cedar_record_add_decimal(php_cedar_record_t *rec, php_cedar_str_t *name, } +php_cedar_int_t +php_cedar_record_add_datetime(php_cedar_record_t *rec, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + if (rec == NULL) { + return PHP_CEDAR_ERROR; + } + return php_cedar_eval_ctx_add_datetime_attr(rec->attrs, name, value); +} + + +php_cedar_int_t +php_cedar_record_add_duration(php_cedar_record_t *rec, + const php_cedar_str_t *name, const php_cedar_str_t *value) +{ + if (rec == NULL) { + return PHP_CEDAR_ERROR; + } + return php_cedar_eval_ctx_add_duration_attr(rec->attrs, name, value); +} + + php_cedar_record_t * -php_cedar_record_add_record(php_cedar_record_t *rec, php_cedar_str_t *name) +php_cedar_record_add_record(php_cedar_record_t *rec, const php_cedar_str_t *name) { php_cedar_attr_t *attr; php_cedar_record_t *child; @@ -883,7 +1044,7 @@ php_cedar_set_create(php_cedar_pool_t *pool, php_cedar_uint_t depth) */ static php_cedar_set_t * php_cedar_eval_ctx_add_set_attr(php_cedar_array_t *attrs, php_cedar_pool_t *pool, - php_cedar_str_t *name, php_cedar_uint_t depth) + const php_cedar_str_t *name, php_cedar_uint_t depth) { php_cedar_attr_t *attr; php_cedar_set_t *set; @@ -930,7 +1091,7 @@ php_cedar_eval_ctx_add_set_attr(php_cedar_array_t *attrs, php_cedar_pool_t *pool */ static php_cedar_int_t php_cedar_eval_ctx_add_entity_attr(php_cedar_array_t *attrs, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id) { php_cedar_attr_t *attr; @@ -958,7 +1119,7 @@ php_cedar_eval_ctx_add_entity_attr(php_cedar_array_t *attrs, php_cedar_int_t -php_cedar_set_add_str(php_cedar_set_t *set, php_cedar_str_t *value) +php_cedar_set_add_str(php_cedar_set_t *set, const php_cedar_str_t *value) { php_cedar_value_t *v; @@ -1024,7 +1185,7 @@ php_cedar_set_add_bool(php_cedar_set_t *set, php_cedar_flag_t value) php_cedar_int_t -php_cedar_set_add_ip(php_cedar_set_t *set, php_cedar_str_t *value) +php_cedar_set_add_ip(php_cedar_set_t *set, const php_cedar_str_t *value) { php_cedar_value_t *v, ip_val; @@ -1049,7 +1210,7 @@ php_cedar_set_add_ip(php_cedar_set_t *set, php_cedar_str_t *value) php_cedar_int_t -php_cedar_set_add_decimal(php_cedar_set_t *set, php_cedar_str_t *value) +php_cedar_set_add_decimal(php_cedar_set_t *set, const php_cedar_str_t *value) { php_cedar_value_t *v, dec_val; @@ -1073,9 +1234,59 @@ php_cedar_set_add_decimal(php_cedar_set_t *set, php_cedar_str_t *value) } +php_cedar_int_t +php_cedar_set_add_datetime(php_cedar_set_t *set, const php_cedar_str_t *value) +{ + php_cedar_value_t *v, dt_val; + + if (set == NULL || value == NULL) { + return PHP_CEDAR_ERROR; + } + + dt_val = php_cedar_make_datetime(value); + if (dt_val.type == PHP_CEDAR_RVAL_ERROR) { + return PHP_CEDAR_ERROR; + } + + v = php_cedar_array_push(set->elts); + if (v == NULL) { + return PHP_CEDAR_ERROR; + } + + *v = dt_val; + + return PHP_CEDAR_OK; +} + + +php_cedar_int_t +php_cedar_set_add_duration(php_cedar_set_t *set, const php_cedar_str_t *value) +{ + php_cedar_value_t *v, dur_val; + + if (set == NULL || value == NULL) { + return PHP_CEDAR_ERROR; + } + + dur_val = php_cedar_make_duration(value); + if (dur_val.type == PHP_CEDAR_RVAL_ERROR) { + return PHP_CEDAR_ERROR; + } + + v = php_cedar_array_push(set->elts); + if (v == NULL) { + return PHP_CEDAR_ERROR; + } + + *v = dur_val; + + return PHP_CEDAR_OK; +} + + php_cedar_int_t php_cedar_set_add_entity(php_cedar_set_t *set, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { php_cedar_value_t *v; @@ -1177,7 +1388,7 @@ php_cedar_set_add_record(php_cedar_set_t *set) php_cedar_set_t * php_cedar_eval_ctx_add_principal_attr_set(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { if (ctx == NULL) { return NULL; @@ -1189,7 +1400,7 @@ php_cedar_eval_ctx_add_principal_attr_set(php_cedar_eval_ctx_t *ctx, php_cedar_set_t * php_cedar_eval_ctx_add_action_attr_set(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { if (ctx == NULL) { return NULL; @@ -1201,7 +1412,7 @@ php_cedar_eval_ctx_add_action_attr_set(php_cedar_eval_ctx_t *ctx, php_cedar_set_t * php_cedar_eval_ctx_add_resource_attr_set(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { if (ctx == NULL) { return NULL; @@ -1213,7 +1424,7 @@ php_cedar_eval_ctx_add_resource_attr_set(php_cedar_eval_ctx_t *ctx, php_cedar_set_t * php_cedar_eval_ctx_add_context_attr_set(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name) + const php_cedar_str_t *name) { if (ctx == NULL) { return NULL; @@ -1225,7 +1436,7 @@ php_cedar_eval_ctx_add_context_attr_set(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_entity(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1237,7 +1448,7 @@ php_cedar_eval_ctx_add_principal_attr_entity(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_attr_entity(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1249,7 +1460,7 @@ php_cedar_eval_ctx_add_action_attr_entity(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_entity(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1261,7 +1472,7 @@ php_cedar_eval_ctx_add_resource_attr_entity(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_context_attr_entity(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1272,8 +1483,8 @@ php_cedar_eval_ctx_add_context_attr_entity(php_cedar_eval_ctx_t *ctx, php_cedar_int_t -php_cedar_record_add_entity(php_cedar_record_t *rec, php_cedar_str_t *name, - php_cedar_str_t *type, php_cedar_str_t *id) +php_cedar_record_add_entity(php_cedar_record_t *rec, const php_cedar_str_t *name, + const php_cedar_str_t *type, const php_cedar_str_t *id) { if (rec == NULL) { return PHP_CEDAR_ERROR; @@ -1283,7 +1494,7 @@ php_cedar_record_add_entity(php_cedar_record_t *rec, php_cedar_str_t *name, php_cedar_set_t * -php_cedar_record_add_set(php_cedar_record_t *rec, php_cedar_str_t *name) +php_cedar_record_add_set(php_cedar_record_t *rec, const php_cedar_str_t *name) { if (rec == NULL) { return NULL; @@ -1304,7 +1515,7 @@ php_cedar_record_add_set(php_cedar_record_t *rec, php_cedar_str_t *name) static php_cedar_int_t php_cedar_eval_ctx_add_parent(php_cedar_array_t *parents, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { php_cedar_entity_ref_t *ref; @@ -1326,7 +1537,7 @@ php_cedar_eval_ctx_add_parent(php_cedar_array_t *parents, php_cedar_int_t php_cedar_eval_ctx_add_principal_parent(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1337,7 +1548,7 @@ php_cedar_eval_ctx_add_principal_parent(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_action_parent(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; @@ -1348,7 +1559,7 @@ php_cedar_eval_ctx_add_action_parent(php_cedar_eval_ctx_t *ctx, php_cedar_int_t php_cedar_eval_ctx_add_resource_parent(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id) + const php_cedar_str_t *type, const php_cedar_str_t *id) { if (ctx == NULL) { return PHP_CEDAR_ERROR; diff --git a/src/cedar/php_cedar_eval.h b/src/cedar/php_cedar_eval.h index 7a1bcd6..80763ae 100644 --- a/src/cedar/php_cedar_eval.h +++ b/src/cedar/php_cedar_eval.h @@ -70,54 +70,86 @@ php_cedar_str_t *php_cedar_policy_get_annotation(php_cedar_policy_t *policy, php_cedar_eval_ctx_t *php_cedar_eval_ctx_create(php_cedar_pool_t *pool); void php_cedar_eval_ctx_set_principal(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id); + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_long( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, int64_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, int64_t value); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_bool( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_flag_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, php_cedar_flag_t value); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_ip( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_decimal( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_datetime( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_duration( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); void php_cedar_eval_ctx_set_action(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id); + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_action_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_action_attr_long( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, int64_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, int64_t value); php_cedar_int_t php_cedar_eval_ctx_add_action_attr_bool( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_flag_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, php_cedar_flag_t value); php_cedar_int_t php_cedar_eval_ctx_add_action_attr_ip( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_action_attr_decimal( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_action_attr_datetime( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_action_attr_duration( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); void php_cedar_eval_ctx_set_resource(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *type, php_cedar_str_t *id); + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_long( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, int64_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, int64_t value); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_bool( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_flag_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, php_cedar_flag_t value); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_ip( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_decimal( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_datetime( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_duration( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_context_attr(php_cedar_eval_ctx_t *ctx, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_context_attr_long( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, int64_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, int64_t value); php_cedar_int_t php_cedar_eval_ctx_add_context_attr_bool( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_flag_t value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, php_cedar_flag_t value); php_cedar_int_t php_cedar_eval_ctx_add_context_attr_ip( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_eval_ctx_add_context_attr_decimal( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, php_cedar_str_t *value); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_context_attr_datetime( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_eval_ctx_add_context_attr_duration( + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *value); /* * Record-valued attribute constructors. @@ -137,30 +169,34 @@ php_cedar_int_t php_cedar_eval_ctx_add_context_attr_decimal( * Returns NULL on allocation failure as well. */ php_cedar_record_t *php_cedar_eval_ctx_add_principal_attr_record( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_record_t *php_cedar_eval_ctx_add_action_attr_record( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_record_t *php_cedar_eval_ctx_add_resource_attr_record( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_record_t *php_cedar_eval_ctx_add_context_attr_record( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_int_t php_cedar_record_add_str(php_cedar_record_t *rec, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_record_add_long(php_cedar_record_t *rec, - php_cedar_str_t *name, int64_t value); + const php_cedar_str_t *name, int64_t value); php_cedar_int_t php_cedar_record_add_bool(php_cedar_record_t *rec, - php_cedar_str_t *name, php_cedar_flag_t value); + const php_cedar_str_t *name, php_cedar_flag_t value); php_cedar_int_t php_cedar_record_add_ip(php_cedar_record_t *rec, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_int_t php_cedar_record_add_decimal(php_cedar_record_t *rec, - php_cedar_str_t *name, php_cedar_str_t *value); + const php_cedar_str_t *name, const php_cedar_str_t *value); +php_cedar_int_t php_cedar_record_add_datetime(php_cedar_record_t *rec, + const php_cedar_str_t *name, const php_cedar_str_t *value); +php_cedar_int_t php_cedar_record_add_duration(php_cedar_record_t *rec, + const php_cedar_str_t *name, const php_cedar_str_t *value); php_cedar_record_t *php_cedar_record_add_record(php_cedar_record_t *rec, - php_cedar_str_t *name); + const php_cedar_str_t *name); php_cedar_int_t php_cedar_record_add_entity(php_cedar_record_t *rec, - php_cedar_str_t *name, php_cedar_str_t *type, php_cedar_str_t *id); + const php_cedar_str_t *name, const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_set_t *php_cedar_record_add_set(php_cedar_record_t *rec, - php_cedar_str_t *name); + const php_cedar_str_t *name); /* @@ -172,26 +208,26 @@ php_cedar_set_t *php_cedar_record_add_set(php_cedar_record_t *rec, * Returns NULL on allocation failure. */ php_cedar_set_t *php_cedar_eval_ctx_add_principal_attr_set( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_set_t *php_cedar_eval_ctx_add_action_attr_set( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_set_t *php_cedar_eval_ctx_add_resource_attr_set( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_set_t *php_cedar_eval_ctx_add_context_attr_set( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name); php_cedar_int_t php_cedar_eval_ctx_add_principal_attr_entity( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, - php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_action_attr_entity( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, - php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_resource_attr_entity( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, - php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_context_attr_entity( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *name, - php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *name, + const php_cedar_str_t *type, const php_cedar_str_t *id); /* @@ -204,14 +240,20 @@ php_cedar_int_t php_cedar_eval_ctx_add_context_attr_entity( * and PHP_CEDAR_MAX_RECORD_DEPTH for record values placed inside a * set; exceeding either ceiling returns NULL. */ -php_cedar_int_t php_cedar_set_add_str(php_cedar_set_t *set, php_cedar_str_t *value); +php_cedar_int_t php_cedar_set_add_str(php_cedar_set_t *set, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_set_add_long(php_cedar_set_t *set, int64_t value); php_cedar_int_t php_cedar_set_add_bool(php_cedar_set_t *set, php_cedar_flag_t value); -php_cedar_int_t php_cedar_set_add_ip(php_cedar_set_t *set, php_cedar_str_t *value); +php_cedar_int_t php_cedar_set_add_ip(php_cedar_set_t *set, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_set_add_decimal(php_cedar_set_t *set, - php_cedar_str_t *value); + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_set_add_datetime(php_cedar_set_t *set, + const php_cedar_str_t *value); +php_cedar_int_t php_cedar_set_add_duration(php_cedar_set_t *set, + const php_cedar_str_t *value); php_cedar_int_t php_cedar_set_add_entity(php_cedar_set_t *set, - php_cedar_str_t *type, php_cedar_str_t *id); + const php_cedar_str_t *type, const php_cedar_str_t *id); php_cedar_set_t *php_cedar_set_add_set(php_cedar_set_t *set); php_cedar_record_t *php_cedar_set_add_record(php_cedar_set_t *set); @@ -230,11 +272,14 @@ php_cedar_record_t *php_cedar_set_add_record(php_cedar_set_t *set); * Returns PHP_CEDAR_OK on success, PHP_CEDAR_ERROR on allocation failure. */ php_cedar_int_t php_cedar_eval_ctx_add_principal_parent( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *type, + const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_action_parent( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *type, + const php_cedar_str_t *id); php_cedar_int_t php_cedar_eval_ctx_add_resource_parent( - php_cedar_eval_ctx_t *ctx, php_cedar_str_t *type, php_cedar_str_t *id); + php_cedar_eval_ctx_t *ctx, const php_cedar_str_t *type, + const php_cedar_str_t *id); /* @@ -248,8 +293,9 @@ php_cedar_int_t php_cedar_eval_ctx_add_resource_parent( php_cedar_array_t *php_cedar_eval_ctx_lookup_parents( php_cedar_eval_ctx_t *ctx, php_cedar_uint_t slot); php_cedar_int_t php_cedar_entity_in_target( - php_cedar_str_t *entity_type, php_cedar_str_t *entity_id, php_cedar_array_t *parents, - php_cedar_str_t *target_type, php_cedar_str_t *target_id); + const php_cedar_str_t *entity_type, const php_cedar_str_t *entity_id, + php_cedar_array_t *parents, + const php_cedar_str_t *target_type, const php_cedar_str_t *target_id); #endif /* PHP_CEDAR_EVAL_H */ diff --git a/src/cedar/php_cedar_expr.c b/src/cedar/php_cedar_expr.c index 6d3bb88..fd1c2ca 100644 --- a/src/cedar/php_cedar_expr.c +++ b/src/cedar/php_cedar_expr.c @@ -83,6 +83,32 @@ php_cedar_make_long(int64_t n) } +/* build a datetime runtime value from UTC epoch milliseconds */ +static php_cedar_value_t +php_cedar_make_datetime_ms(int64_t ms) +{ + php_cedar_value_t val; + + php_cedar_memzero(&val, sizeof(php_cedar_value_t)); + val.type = PHP_CEDAR_RVAL_DATETIME; + val.v.datetime_val = ms; + return val; +} + + +/* build a duration runtime value from a signed millisecond count */ +static php_cedar_value_t +php_cedar_make_duration_ms(int64_t ms) +{ + php_cedar_value_t val; + + php_cedar_memzero(&val, sizeof(php_cedar_value_t)); + val.type = PHP_CEDAR_RVAL_DURATION; + val.v.duration_val = ms; + return val; +} + + static php_cedar_value_t php_cedar_make_entity(php_cedar_str_t type, php_cedar_str_t id) { @@ -110,10 +136,10 @@ php_cedar_make_record(php_cedar_array_t *attrs) /* parse bounded decimal: overflow-safe with leading-zero rejection */ static php_cedar_int_t -php_cedar_parse_bounded_dec(unsigned char **pp, unsigned char *end, php_cedar_uint_t max, - php_cedar_uint_t *out) +php_cedar_parse_bounded_dec(const unsigned char **pp, const unsigned char *end, + php_cedar_uint_t max, php_cedar_uint_t *out) { - unsigned char *p, *start; + const unsigned char *p, *start; php_cedar_uint_t val, digit; p = *pp; @@ -147,7 +173,7 @@ php_cedar_parse_bounded_dec(unsigned char **pp, unsigned char *end, php_cedar_ui /* parse CIDR prefix length: digits after '/' with leading-zero rejection */ static php_cedar_int_t -php_cedar_parse_cidr_prefix(unsigned char **pp, unsigned char *end, +php_cedar_parse_cidr_prefix(const unsigned char **pp, const unsigned char *end, php_cedar_uint_t max_prefix, php_cedar_uint_t *prefix_len) { return php_cedar_parse_bounded_dec(pp, end, max_prefix, prefix_len); @@ -156,10 +182,10 @@ php_cedar_parse_cidr_prefix(unsigned char **pp, unsigned char *end, /* parse IPv4 address: "a.b.c.d" with optional "/prefix" */ static php_cedar_int_t -php_cedar_parse_ipv4(unsigned char *data, size_t len, +php_cedar_parse_ipv4(const unsigned char *data, size_t len, unsigned char *addr, php_cedar_uint_t *prefix_len) { - unsigned char *p, *end; + const unsigned char *p, *end; php_cedar_uint_t octet, i; p = data; @@ -204,10 +230,10 @@ php_cedar_parse_ipv4(unsigned char *data, size_t len, /* parse IPv6 address with optional "/prefix" */ static php_cedar_int_t -php_cedar_parse_ipv6(unsigned char *data, size_t len, +php_cedar_parse_ipv6(const unsigned char *data, size_t len, unsigned char *addr, php_cedar_uint_t *prefix_len) { - unsigned char *p, *end, *slash; + const unsigned char *p, *end, *slash; php_cedar_uint_t groups[8], n_groups, gap_pos, i, val; size_t addr_len; @@ -373,10 +399,10 @@ php_cedar_parse_ipv6(unsigned char *data, size_t len, * reference parser. */ php_cedar_value_t -php_cedar_make_decimal(php_cedar_str_t *s) +php_cedar_make_decimal(const php_cedar_str_t *s) { php_cedar_value_t val; - unsigned char *p, *end; + const unsigned char *p, *end; php_cedar_flag_t negative; int64_t int_part, frac_part, scaled; php_cedar_uint_t frac_digits; @@ -470,9 +496,337 @@ php_cedar_make_decimal(php_cedar_str_t *s) } +/* number of days in the given month, accounting for leap years */ +static php_cedar_uint_t +php_cedar_days_in_month(int year, int month) +{ + static const php_cedar_uint_t dim[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + if (month == 2 + && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) + { + return 29; + } + + return dim[month - 1]; +} + + +/* + * Days since 1970-01-01 for a proleptic Gregorian date (Howard + * Hinnant's days_from_civil). Valid across the full datetime + * construction range (years 0000-9999) and computed in int64_t. + */ +static int64_t +php_cedar_days_from_civil(int64_t y, int64_t m, int64_t d) +{ + int64_t era, yoe, doy, doe; + + y -= (m <= 2); + era = (y >= 0 ? y : y - 399) / 400; + yoe = y - era * 400; /* [0, 399] */ + doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; /* [0, 365] */ + doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; /* [0, 146096] */ + + return era * 146097 + doe - 719468; +} + + +/* + * Read exactly `ndigits` ASCII decimal digits from *pp into *out, + * advancing *pp. Returns PHP_CEDAR_ERROR if fewer than ndigits digits are + * available or a non-digit is encountered. + */ +static php_cedar_int_t +php_cedar_read_uint(const unsigned char **pp, const unsigned char *end, + php_cedar_uint_t ndigits, int *out) +{ + const unsigned char *p = *pp; + int v = 0; + php_cedar_uint_t i; + + for (i = 0; i < ndigits; i++) { + if (p >= end || *p < '0' || *p > '9') { + return PHP_CEDAR_ERROR; + } + v = v * 10 + (*p - '0'); + p++; + } + + *pp = p; + *out = v; + return PHP_CEDAR_OK; +} + + +/* + * Parse a Cedar datetime literal into UTC epoch milliseconds (i64). + * Accepts the date-only form "YYYY-MM-DD", which denotes 00:00:00 UTC, + * and the full form + * "YYYY-MM-DDThh:mm:ss(.SSS)?(Z|±hhmm)" where the timezone designator is + * mandatory for any form carrying a time. A bare trailing "T" without a + * time component is rejected, matching the cedar-policy reference. + * Fractional seconds are exactly + * three digits when present. Field ranges are validated (month 1-12, day + * per month with leap years, hour 0-23, minute/second 0-59, offset hour + * 0-23 / minute 0-59); malformed input or trailing garbage yields + * RVAL_ERROR. The 4-digit year keeps the result within int64_t. + */ +php_cedar_value_t +php_cedar_make_datetime(const php_cedar_str_t *s) +{ + php_cedar_value_t val; + const unsigned char *p, *end; + int year, month, day, hh, mm, ss, frac; + int64_t days, ms, offset_min; + + php_cedar_memzero(&val, sizeof(php_cedar_value_t)); + val.type = PHP_CEDAR_RVAL_ERROR; + + if (s == NULL || s->len < 10) { + return val; + } + + p = s->data; + end = p + s->len; + + /* date: YYYY-MM-DD */ + if (php_cedar_read_uint(&p, end, 4, &year) != PHP_CEDAR_OK) { + return val; + } + if (p >= end || *p != '-') { + return val; + } + p++; + if (php_cedar_read_uint(&p, end, 2, &month) != PHP_CEDAR_OK) { + return val; + } + if (p >= end || *p != '-') { + return val; + } + p++; + if (php_cedar_read_uint(&p, end, 2, &day) != PHP_CEDAR_OK) { + return val; + } + + if (month < 1 || month > 12) { + return val; + } + if (day < 1 || (php_cedar_uint_t) day > php_cedar_days_in_month(year, month)) { + return val; + } + + hh = mm = ss = frac = 0; + offset_min = 0; + + if (p != end) { + /* + * A time component is present; it must start with 'T' and carry + * a full hh:mm:ss plus a timezone designator. A bare trailing + * 'T' is rejected, matching the reference implementation. + */ + if (*p != 'T') { + return val; + } + p++; + + /* time: hh:mm:ss */ + if (php_cedar_read_uint(&p, end, 2, &hh) != PHP_CEDAR_OK) { + return val; + } + if (p >= end || *p != ':') { + return val; + } + p++; + if (php_cedar_read_uint(&p, end, 2, &mm) != PHP_CEDAR_OK) { + return val; + } + if (p >= end || *p != ':') { + return val; + } + p++; + if (php_cedar_read_uint(&p, end, 2, &ss) != PHP_CEDAR_OK) { + return val; + } + + if (hh > 23 || mm > 59 || ss > 59) { + return val; + } + + /* optional .SSS (exactly three digits) */ + if (p < end && *p == '.') { + p++; + if (php_cedar_read_uint(&p, end, 3, &frac) != PHP_CEDAR_OK) { + return val; + } + } + + /* timezone designator is mandatory for time forms */ + if (p >= end) { + return val; + } + + if (*p == 'Z') { + p++; + if (p != end) { + return val; + } + + } else if (*p == '+' || *p == '-') { + int oh, om, sign; + + sign = (*p == '-') ? -1 : 1; + p++; + if (php_cedar_read_uint(&p, end, 2, &oh) != PHP_CEDAR_OK) { + return val; + } + if (php_cedar_read_uint(&p, end, 2, &om) != PHP_CEDAR_OK) { + return val; + } + if (p != end || oh > 23 || om > 59) { + return val; + } + offset_min = (int64_t) sign * ((int64_t) oh * 60 + om); + + } else { + return val; + } + } + + days = php_cedar_days_from_civil(year, month, day); + ms = ((((days * 24 + hh) * 60 + mm) * 60 + ss) * 1000) + frac; + ms -= offset_min * 60000; + + val.type = PHP_CEDAR_RVAL_DATETIME; + val.v.datetime_val = ms; + return val; +} + + +/* + * Parse a Cedar duration literal into a signed millisecond count (i64). + * Grammar: an optional leading '-' followed by one or more + * "" groups in strictly descending unit order + * (d > h > m > s > ms), each unit appearing at most once and at least + * one unit present. 'm' (minutes) and 'ms' (milliseconds) are + * distinguished by a trailing 's'. Overflow at any accumulation step, + * an empty string, a missing unit, a repeated/out-of-order unit, or + * trailing garbage yields RVAL_ERROR. + */ +php_cedar_value_t +php_cedar_make_duration(const php_cedar_str_t *s) +{ + php_cedar_value_t val; + const unsigned char *p, *end; + php_cedar_flag_t negative; + int64_t total; + php_cedar_uint_t stage, nunits; + + php_cedar_memzero(&val, sizeof(php_cedar_value_t)); + val.type = PHP_CEDAR_RVAL_ERROR; + + if (s == NULL || s->len == 0) { + return val; + } + + p = s->data; + end = p + s->len; + + negative = 0; + if (*p == '-') { + negative = 1; + p++; + } + + total = 0; + stage = 0; /* lowest accepted unit index: d=0,h=1,m=2,s=3,ms=4 */ + nunits = 0; + + while (p < end) { + int64_t qty, factor, contrib; + php_cedar_uint_t unit_stage; + + /* quantity: at least one digit */ + if (*p < '0' || *p > '9') { + return val; + } + qty = 0; + while (p < end && *p >= '0' && *p <= '9') { + int64_t d = *p - '0'; + if (qty > (INT64_MAX - d) / 10) { + return val; + } + qty = qty * 10 + d; + p++; + } + + /* unit */ + if (p >= end) { + return val; + } + if (*p == 'd') { + unit_stage = 0; + factor = 86400000; + p++; + } else if (*p == 'h') { + unit_stage = 1; + factor = 3600000; + p++; + } else if (*p == 'm') { + if (p + 1 < end && *(p + 1) == 's') { + unit_stage = 4; + factor = 1; + p += 2; + } else { + unit_stage = 2; + factor = 60000; + p++; + } + } else if (*p == 's') { + unit_stage = 3; + factor = 1000; + p++; + } else { + return val; + } + + /* enforce strictly descending order; rejects repeats too */ + if (unit_stage < stage) { + return val; + } + stage = unit_stage + 1; + + if (__builtin_mul_overflow(qty, factor, &contrib)) { + return val; + } + if (__builtin_add_overflow(total, contrib, &total)) { + return val; + } + + nunits++; + } + + if (nunits == 0) { + return val; + } + + if (negative) { + if (__builtin_sub_overflow((int64_t) 0, total, &total)) { + return val; + } + } + + val.type = PHP_CEDAR_RVAL_DURATION; + val.v.duration_val = total; + return val; +} + + /* parse IP string to binary runtime value */ php_cedar_value_t -php_cedar_make_ip(php_cedar_str_t *s) +php_cedar_make_ip(const php_cedar_str_t *s) { php_cedar_value_t val; @@ -482,7 +836,7 @@ php_cedar_make_ip(php_cedar_str_t *s) * actual OOB protection lives in parse_ipv4 / parse_ipv6 which * clamp to data + len. */ - if (s->len == 0 || s->len > 43) { + if (s == NULL || s->len == 0 || s->len > 43) { return php_cedar_make_error(); } @@ -617,6 +971,12 @@ php_cedar_value_equals(php_cedar_value_t *a, php_cedar_value_t *b, case PHP_CEDAR_RVAL_DECIMAL: return (a->v.decimal_val == b->v.decimal_val); + case PHP_CEDAR_RVAL_DATETIME: + return (a->v.datetime_val == b->v.datetime_val); + + case PHP_CEDAR_RVAL_DURATION: + return (a->v.duration_val == b->v.duration_val); + case PHP_CEDAR_RVAL_SET: if (a->v.set_elts == NULL || b->v.set_elts == NULL) { return 0; @@ -779,6 +1139,66 @@ php_cedar_resolve_var_attrs(php_cedar_var_type_t var_type, } +/* resolve an entity slot tag to its attribute array */ +static php_cedar_array_t * +php_cedar_resolve_slot_attrs(php_cedar_uint_t slot, php_cedar_eval_ctx_t *ctx) +{ + switch (slot) { + + case PHP_CEDAR_ENTITY_SLOT_PRINCIPAL: + return ctx->principal_attrs; + + case PHP_CEDAR_ENTITY_SLOT_ACTION: + return ctx->action_attrs; + + case PHP_CEDAR_ENTITY_SLOT_RESOURCE: + return ctx->resource_attrs; + + default: + return NULL; + } +} + + +/* + * Return the request slot whose (type, id) matches the given entity, or + * PHP_CEDAR_ENTITY_SLOT_NONE if none do. Lets an entity literal + * Foo::"id" that names the principal / action / resource resolve its + * attributes and ancestors through the corresponding per-slot arrays, + * just like the bare keyword would. Principal is checked first so a + * deliberate (type, id) collision across slots resolves deterministically + * (the same tie-break the slot tag uses elsewhere). + */ +static php_cedar_uint_t +php_cedar_entity_request_slot(php_cedar_eval_ctx_t *ctx, + php_cedar_str_t *type, php_cedar_str_t *id) +{ + if (ctx == NULL) { + return PHP_CEDAR_ENTITY_SLOT_NONE; + } + + if (php_cedar_str_eq(type, &ctx->principal_type) + && php_cedar_str_eq(id, &ctx->principal_id)) + { + return PHP_CEDAR_ENTITY_SLOT_PRINCIPAL; + } + + if (php_cedar_str_eq(type, &ctx->action_type) + && php_cedar_str_eq(id, &ctx->action_id)) + { + return PHP_CEDAR_ENTITY_SLOT_ACTION; + } + + if (php_cedar_str_eq(type, &ctx->resource_type) + && php_cedar_str_eq(id, &ctx->resource_id)) + { + return PHP_CEDAR_ENTITY_SLOT_RESOURCE; + } + + return PHP_CEDAR_ENTITY_SLOT_NONE; +} + + /* * Evaluate attribute access expr.attr. * @@ -820,6 +1240,29 @@ php_cedar_eval_attr_access(php_cedar_node_t *node, if (obj_val.type == PHP_CEDAR_RVAL_ERROR) { return obj_val; } + + /* + * Entity literal naming the principal / action / resource: resolve + * the attribute through the matching per-slot array. The slot is + * stamped when the literal's (type, id) matches a request entity + * (see PHP_CEDAR_NODE_ENTITY_REF); a literal that names no request + * entity carries SLOT_NONE and has no attribute store, so it errors + * (the policy is not applicable), matching the bare-keyword path. + */ + if (obj_val.type == PHP_CEDAR_RVAL_ENTITY) { + attrs = php_cedar_resolve_slot_attrs(obj_val.v.entity.slot, ctx); + if (attrs == NULL) { + return php_cedar_make_error(); + } + + attr = php_cedar_find_attr(attrs, &node->u.attr_access.attr); + if (attr == NULL) { + return php_cedar_make_error(); + } + + return attr->value; + } + if (obj_val.type != PHP_CEDAR_RVAL_RECORD) { return php_cedar_make_error(); } @@ -868,6 +1311,24 @@ php_cedar_eval_has(php_cedar_node_t *node, if (obj_val.type == PHP_CEDAR_RVAL_ERROR) { return obj_val; } + + /* + * Entity literal naming the principal / action / resource: report + * whether the attribute exists in the matching per-slot array. A + * literal that names no request entity (SLOT_NONE) has no attribute + * store, so `has` is an error, matching the slow-path treatment of + * any non-record value. + */ + if (obj_val.type == PHP_CEDAR_RVAL_ENTITY) { + attrs = php_cedar_resolve_slot_attrs(obj_val.v.entity.slot, ctx); + if (attrs == NULL) { + return php_cedar_make_error(); + } + + return php_cedar_make_bool( + php_cedar_find_attr(attrs, &node->u.has.attr) != NULL); + } + if (obj_val.type != PHP_CEDAR_RVAL_RECORD) { return php_cedar_make_error(); } @@ -1031,6 +1492,66 @@ php_cedar_eval_method_call(php_cedar_node_t *node, return php_cedar_make_bool(obj.v.set_elts->nelts == 0); } + /* datetime zero-arg methods: receiver must be datetime */ + if (obj.type == PHP_CEDAR_RVAL_DATETIME) { + int64_t ms = obj.v.datetime_val; + int64_t r = ms % 86400000; + + /* floor the remainder so truncation works for ms < 0 */ + if (r < 0) { + r += 86400000; + } + + /* toDate: truncate to 00:00:00 UTC of the same day */ + if (method->len == 6 + && php_cedar_memcmp(method->data, "toDate", 6) == 0) + { + return php_cedar_make_datetime_ms(ms - r); + } + + /* toTime: milliseconds elapsed since toDate(), as a duration */ + if (method->len == 6 + && php_cedar_memcmp(method->data, "toTime", 6) == 0) + { + return php_cedar_make_duration_ms(r); + } + + return php_cedar_make_error(); + } + + /* duration zero-arg conversion methods (result is Long) */ + if (obj.type == PHP_CEDAR_RVAL_DURATION) { + int64_t d = obj.v.duration_val; + + if (method->len == 14 + && php_cedar_memcmp(method->data, "toMilliseconds", 14) == 0) + { + return php_cedar_make_long(d); + } + if (method->len == 9 + && php_cedar_memcmp(method->data, "toSeconds", 9) == 0) + { + return php_cedar_make_long(d / 1000); + } + if (method->len == 9 + && php_cedar_memcmp(method->data, "toMinutes", 9) == 0) + { + return php_cedar_make_long(d / 60000); + } + if (method->len == 7 + && php_cedar_memcmp(method->data, "toHours", 7) == 0) + { + return php_cedar_make_long(d / 3600000); + } + if (method->len == 6 + && php_cedar_memcmp(method->data, "toDays", 6) == 0) + { + return php_cedar_make_long(d / 86400000); + } + + return php_cedar_make_error(); + } + /* IP inspection methods: receiver must be IP */ if (obj.type != PHP_CEDAR_RVAL_IP) { return php_cedar_make_error(); @@ -1220,6 +1741,45 @@ php_cedar_eval_method_call(php_cedar_node_t *node, php_cedar_ip_cidr_contains(&obj, &arg)); } + /* + * datetime one-arg methods. offset(duration) shifts a datetime and + * durationSince(datetime) returns the signed difference; both error + * on i64 overflow or argument type mismatch. + */ + if (obj.type == PHP_CEDAR_RVAL_DATETIME) { + int64_t result; + + if (method->len == 6 + && php_cedar_memcmp(method->data, "offset", 6) == 0) + { + if (arg.type != PHP_CEDAR_RVAL_DURATION) { + return php_cedar_make_error(); + } + if (__builtin_add_overflow(obj.v.datetime_val, + arg.v.duration_val, &result)) + { + return php_cedar_make_error(); + } + return php_cedar_make_datetime_ms(result); + } + + if (method->len == 13 + && php_cedar_memcmp(method->data, "durationSince", 13) == 0) + { + if (arg.type != PHP_CEDAR_RVAL_DATETIME) { + return php_cedar_make_error(); + } + if (__builtin_sub_overflow(obj.v.datetime_val, + arg.v.datetime_val, &result)) + { + return php_cedar_make_error(); + } + return php_cedar_make_duration_ms(result); + } + + return php_cedar_make_error(); + } + /* * Decimal comparison methods. Cedar exposes ordering on decimals * only via these four methods; the binary <, <=, >, >= operators @@ -1399,9 +1959,25 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, case PHP_CEDAR_NODE_DECIMAL_LITERAL: return php_cedar_make_decimal(&node->u.decimal_literal.text); + case PHP_CEDAR_NODE_DATETIME_LITERAL: + return php_cedar_make_datetime(&node->u.datetime_literal.text); + + case PHP_CEDAR_NODE_DURATION_LITERAL: + return php_cedar_make_duration(&node->u.duration_literal.text); + case PHP_CEDAR_NODE_ENTITY_REF: - return php_cedar_make_entity(node->u.entity_ref.entity_type, - node->u.entity_ref.entity_id); + val = php_cedar_make_entity(node->u.entity_ref.entity_type, + node->u.entity_ref.entity_id); + /* + * Tag the literal with a request slot when it names the + * principal / action / resource so attribute, has, and `in` + * resolution reach the matching per-slot arrays, exactly as the + * bare keyword does. Literals that name no request entity keep + * SLOT_NONE and match reflexively only. + */ + val.v.entity.slot = php_cedar_entity_request_slot( + ctx, &val.v.entity.type, &val.v.entity.id); + return val; case PHP_CEDAR_NODE_VAR: switch (node->u.var_type) { @@ -1421,8 +1997,13 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, val.v.entity.slot = PHP_CEDAR_ENTITY_SLOT_RESOURCE; return val; case PHP_CEDAR_VAR_CONTEXT: - /* context alone is not a value; only context.attr */ - return php_cedar_make_error(); + /* + * context is an ordinary record value, usable as an operand + * of ==, !=, has, etc. ctx->context_attrs is always a + * (possibly empty) array, so an unset context materialises + * as the empty record, matching Cedar semantics. + */ + return php_cedar_make_record(ctx->context_attrs); default: return php_cedar_make_error(); } @@ -1561,9 +2142,11 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, if (right.type == PHP_CEDAR_RVAL_ERROR) { return right; } - if (left.type != right.type) { - return php_cedar_make_error(); - } + /* + * `==` is a total function: a type mismatch is not an error but + * simply "not equal" (false). php_cedar_value_equals() already + * returns 0 for mismatched types, so no early guard is needed. + */ { php_cedar_int_t r = php_cedar_value_equals(&left, &right, 0); if (r == PHP_CEDAR_ERROR) { @@ -1583,9 +2166,11 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, if (right.type == PHP_CEDAR_RVAL_ERROR) { return right; } - if (left.type != right.type) { - return php_cedar_make_error(); - } + /* + * `!=` is a total function: a type mismatch is not an error but + * simply "not equal" (true). php_cedar_value_equals() already + * returns 0 for mismatched types, so no early guard is needed. + */ { php_cedar_int_t r = php_cedar_value_equals(&left, &right, 0); if (r == PHP_CEDAR_ERROR) { @@ -1610,7 +2195,9 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, case PHP_CEDAR_OP_LT: case PHP_CEDAR_OP_GT: case PHP_CEDAR_OP_LE: - case PHP_CEDAR_OP_GE: + case PHP_CEDAR_OP_GE: { + int64_t lv, rv; + left = php_cedar_expr_eval(node->u.binop.left, ctx, pool, log); if (left.type == PHP_CEDAR_RVAL_ERROR) { @@ -1621,28 +2208,45 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, if (right.type == PHP_CEDAR_RVAL_ERROR) { return right; } - if (left.type != PHP_CEDAR_RVAL_LONG - || right.type != PHP_CEDAR_RVAL_LONG) - { + + /* + * Ordering is defined on Long, datetime, and duration, but + * only between two operands of the same type. The scaled i64 + * representation is order-preserving for all three. + */ + if (left.type != right.type) { + return php_cedar_make_error(); + } + switch (left.type) { + case PHP_CEDAR_RVAL_LONG: + lv = left.v.long_val; + rv = right.v.long_val; + break; + case PHP_CEDAR_RVAL_DATETIME: + lv = left.v.datetime_val; + rv = right.v.datetime_val; + break; + case PHP_CEDAR_RVAL_DURATION: + lv = left.v.duration_val; + rv = right.v.duration_val; + break; + default: return php_cedar_make_error(); } switch (node->u.binop.op) { case PHP_CEDAR_OP_LT: - return php_cedar_make_bool( - left.v.long_val < right.v.long_val); + return php_cedar_make_bool(lv < rv); case PHP_CEDAR_OP_GT: - return php_cedar_make_bool( - left.v.long_val > right.v.long_val); + return php_cedar_make_bool(lv > rv); case PHP_CEDAR_OP_LE: - return php_cedar_make_bool( - left.v.long_val <= right.v.long_val); + return php_cedar_make_bool(lv <= rv); case PHP_CEDAR_OP_GE: - return php_cedar_make_bool( - left.v.long_val >= right.v.long_val); + return php_cedar_make_bool(lv >= rv); default: return php_cedar_make_error(); } + } case PHP_CEDAR_OP_PLUS: case PHP_CEDAR_OP_MINUS: @@ -1703,7 +2307,7 @@ php_cedar_expr_eval_body(php_cedar_node_t *node, } return php_cedar_make_long(-left.v.long_val); - /* Phase 2 */ + /* conditionals, like, method calls */ case PHP_CEDAR_NODE_HAS: return php_cedar_eval_has(node, ctx, pool, log); diff --git a/src/cedar/php_cedar_expr.h b/src/cedar/php_cedar_expr.h index bf65fed..245e085 100644 --- a/src/cedar/php_cedar_expr.h +++ b/src/cedar/php_cedar_expr.h @@ -26,7 +26,7 @@ php_cedar_value_t php_cedar_expr_eval(php_cedar_node_t *node, * Shared with eval.c so the injection API can eagerly materialize * IP attribute values at insertion time. */ -php_cedar_value_t php_cedar_make_ip(php_cedar_str_t *s); +php_cedar_value_t php_cedar_make_ip(const php_cedar_str_t *s); /* @@ -36,7 +36,29 @@ php_cedar_value_t php_cedar_make_ip(php_cedar_str_t *s); * fit in int64_t. Shared with eval.c so the injection API can eagerly * materialize decimal attribute values at insertion time. */ -php_cedar_value_t php_cedar_make_decimal(php_cedar_str_t *s); +php_cedar_value_t php_cedar_make_decimal(const php_cedar_str_t *s); + + +/* + * Parse a Cedar datetime string ("YYYY-MM-DD" or + * "YYYY-MM-DDThh:mm:ss(.SSS)?(Z|±hhmm)") into UTC epoch milliseconds. + * Returns an RVAL_ERROR value when the input is malformed, denotes a + * non-existent date, or carries a time without a timezone designator. + * Shared with eval.c so the injection API can eagerly materialize + * datetime attribute values at insertion time. + */ +php_cedar_value_t php_cedar_make_datetime(const php_cedar_str_t *s); + + +/* + * Parse a Cedar duration string ("[-]?(\d+d)?(\d+h)?(\d+m)?(\d+s)? + * (\d+ms)?", at least one unit) into a signed millisecond count. + * Returns an RVAL_ERROR value on malformed input, an out-of-order or + * repeated unit, or i64 overflow. Shared with eval.c so the injection + * API can eagerly materialize duration attribute values at insertion + * time. + */ +php_cedar_value_t php_cedar_make_duration(const php_cedar_str_t *s); #endif /* PHP_CEDAR_EXPR_H */ diff --git a/src/cedar/php_cedar_lexer.c b/src/cedar/php_cedar_lexer.c index 3e5c92f..97c1170 100644 --- a/src/cedar/php_cedar_lexer.c +++ b/src/cedar/php_cedar_lexer.c @@ -39,6 +39,8 @@ static php_cedar_keyword_t php_cedar_keywords[] = { { php_cedar_string("ip"), PHP_CEDAR_TOKEN_IP }, { php_cedar_string("decimal"), PHP_CEDAR_TOKEN_DECIMAL }, { php_cedar_string("is"), PHP_CEDAR_TOKEN_IS }, + { php_cedar_string("datetime"), PHP_CEDAR_TOKEN_DATETIME }, + { php_cedar_string("duration"), PHP_CEDAR_TOKEN_DURATION }, { php_cedar_null_string, 0 } }; @@ -331,6 +333,8 @@ php_cedar_lexer_read_string(php_cedar_lexer_t *lexer) token.type = PHP_CEDAR_TOKEN_ERROR; token.value.data = (unsigned char *) "alloc failed"; token.value.len = 12; + token.raw.data = NULL; + token.raw.len = 0; token.has_star_escape = 0; return token; } @@ -365,6 +369,9 @@ php_cedar_lexer_read_string(php_cedar_lexer_t *lexer) token.value.data = (unsigned char *) "invalid escape sequence"; token.value.len = 23; + token.raw.data = NULL; + token.raw.len = 0; + token.has_star_escape = 0; return token; } diff --git a/src/cedar/php_cedar_parser.c b/src/cedar/php_cedar_parser.c index bef5a14..da0318c 100644 --- a/src/cedar/php_cedar_parser.c +++ b/src/cedar/php_cedar_parser.c @@ -57,7 +57,9 @@ php_cedar_token_is_ident(php_cedar_token_type_t type) { return (type == PHP_CEDAR_TOKEN_IDENT || type == PHP_CEDAR_TOKEN_IP - || type == PHP_CEDAR_TOKEN_DECIMAL); + || type == PHP_CEDAR_TOKEN_DECIMAL + || type == PHP_CEDAR_TOKEN_DATETIME + || type == PHP_CEDAR_TOKEN_DURATION); } @@ -854,6 +856,78 @@ php_cedar_parse_primary(php_cedar_parser_ctx_t *ctx) } return node; + case PHP_CEDAR_TOKEN_DATETIME: + php_cedar_parser_advance(ctx); + if (php_cedar_parser_expect(ctx, PHP_CEDAR_TOKEN_LPAREN) + != PHP_CEDAR_OK) + { + return NULL; + } + if (ctx->current.type != PHP_CEDAR_TOKEN_STRING) { + php_cedar_log_error(PHP_CEDAR_LOG_ERR, ctx->log, 0, + "php_cedar_parse: " + "datetime() requires a string argument"); + ctx->error = 1; + return NULL; + } + if (ctx->current.has_star_escape) { + php_cedar_log_error(PHP_CEDAR_LOG_ERR, ctx->log, 0, + "php_cedar_parse: " + "invalid escape sequence \\*: " + "only valid in like patterns"); + ctx->error = 1; + return NULL; + } + node = php_cedar_parser_alloc_node(ctx, + PHP_CEDAR_NODE_DATETIME_LITERAL); + if (node == NULL) { + return NULL; + } + node->u.datetime_literal.text = ctx->current.value; + php_cedar_parser_advance(ctx); + if (php_cedar_parser_expect(ctx, PHP_CEDAR_TOKEN_RPAREN) + != PHP_CEDAR_OK) + { + return NULL; + } + return node; + + case PHP_CEDAR_TOKEN_DURATION: + php_cedar_parser_advance(ctx); + if (php_cedar_parser_expect(ctx, PHP_CEDAR_TOKEN_LPAREN) + != PHP_CEDAR_OK) + { + return NULL; + } + if (ctx->current.type != PHP_CEDAR_TOKEN_STRING) { + php_cedar_log_error(PHP_CEDAR_LOG_ERR, ctx->log, 0, + "php_cedar_parse: " + "duration() requires a string argument"); + ctx->error = 1; + return NULL; + } + if (ctx->current.has_star_escape) { + php_cedar_log_error(PHP_CEDAR_LOG_ERR, ctx->log, 0, + "php_cedar_parse: " + "invalid escape sequence \\*: " + "only valid in like patterns"); + ctx->error = 1; + return NULL; + } + node = php_cedar_parser_alloc_node(ctx, + PHP_CEDAR_NODE_DURATION_LITERAL); + if (node == NULL) { + return NULL; + } + node->u.duration_literal.text = ctx->current.value; + php_cedar_parser_advance(ctx); + if (php_cedar_parser_expect(ctx, PHP_CEDAR_TOKEN_RPAREN) + != PHP_CEDAR_OK) + { + return NULL; + } + return node; + case PHP_CEDAR_TOKEN_IDENT: ident = ctx->current.value; php_cedar_parser_advance(ctx); @@ -2001,7 +2075,7 @@ php_cedar_parse(php_cedar_pool_t *pool, php_cedar_log_t *log, const php_cedar_st php_cedar_memzero(policy, sizeof(php_cedar_policy_t)); - /* parse annotations before effect (Phase 4) */ + /* parse annotations before effect */ if (php_cedar_parse_annotations(&ctx, policy) != PHP_CEDAR_OK) { return NULL; } diff --git a/src/cedar/php_cedar_types.h b/src/cedar/php_cedar_types.h index 99ea272..7d05f63 100644 --- a/src/cedar/php_cedar_types.h +++ b/src/cedar/php_cedar_types.h @@ -38,14 +38,16 @@ typedef enum { PHP_CEDAR_TOKEN_TRUE, PHP_CEDAR_TOKEN_FALSE, PHP_CEDAR_TOKEN_IN, - PHP_CEDAR_TOKEN_IF, /* Phase 2 */ - PHP_CEDAR_TOKEN_THEN, /* Phase 2 */ - PHP_CEDAR_TOKEN_ELSE, /* Phase 2 */ - PHP_CEDAR_TOKEN_HAS, /* Phase 2 */ - PHP_CEDAR_TOKEN_LIKE, /* Phase 2 */ - PHP_CEDAR_TOKEN_IP, /* Phase 3 */ - PHP_CEDAR_TOKEN_DECIMAL, /* Phase 3 */ - PHP_CEDAR_TOKEN_IS, /* Phase 4 */ + PHP_CEDAR_TOKEN_IF, + PHP_CEDAR_TOKEN_THEN, + PHP_CEDAR_TOKEN_ELSE, + PHP_CEDAR_TOKEN_HAS, + PHP_CEDAR_TOKEN_LIKE, + PHP_CEDAR_TOKEN_IP, + PHP_CEDAR_TOKEN_DECIMAL, + PHP_CEDAR_TOKEN_IS, + PHP_CEDAR_TOKEN_DATETIME, /* datetime() extension constructor */ + PHP_CEDAR_TOKEN_DURATION, /* duration() extension constructor */ /* operators */ PHP_CEDAR_TOKEN_EQ, /* == */ @@ -53,13 +55,13 @@ typedef enum { PHP_CEDAR_TOKEN_AND, /* && */ PHP_CEDAR_TOKEN_OR, /* || */ PHP_CEDAR_TOKEN_NOT, /* ! */ - PHP_CEDAR_TOKEN_MINUS, /* - (binary and unary; Phase 4) */ - PHP_CEDAR_TOKEN_PLUS, /* + (Phase 4) */ - PHP_CEDAR_TOKEN_STAR, /* * (Phase 4) */ - PHP_CEDAR_TOKEN_LT, /* < (Phase 2) */ - PHP_CEDAR_TOKEN_GT, /* > (Phase 2) */ - PHP_CEDAR_TOKEN_LE, /* <= (Phase 2) */ - PHP_CEDAR_TOKEN_GE, /* >= (Phase 2) */ + PHP_CEDAR_TOKEN_MINUS, /* - (binary and unary) */ + PHP_CEDAR_TOKEN_PLUS, /* + */ + PHP_CEDAR_TOKEN_STAR, /* * */ + PHP_CEDAR_TOKEN_LT, /* < */ + PHP_CEDAR_TOKEN_GT, /* > */ + PHP_CEDAR_TOKEN_LE, /* <= */ + PHP_CEDAR_TOKEN_GE, /* >= */ /* delimiters */ PHP_CEDAR_TOKEN_DOT, /* . */ @@ -72,8 +74,8 @@ typedef enum { PHP_CEDAR_TOKEN_LBRACKET, /* [ */ PHP_CEDAR_TOKEN_RBRACKET, /* ] */ PHP_CEDAR_TOKEN_COLONCOLON, /* :: */ - PHP_CEDAR_TOKEN_COLON, /* : (Phase 4 record literal) */ - PHP_CEDAR_TOKEN_AT, /* @ (Phase 4) */ + PHP_CEDAR_TOKEN_COLON, /* : (record literal) */ + PHP_CEDAR_TOKEN_AT, /* @ */ /* literals */ PHP_CEDAR_TOKEN_STRING, /* "..." */ @@ -105,13 +107,13 @@ typedef enum { PHP_CEDAR_OP_AND, /* && */ PHP_CEDAR_OP_OR, /* || */ PHP_CEDAR_OP_IN, /* in */ - PHP_CEDAR_OP_LT, /* < (Phase 2) */ - PHP_CEDAR_OP_GT, /* > (Phase 2) */ - PHP_CEDAR_OP_LE, /* <= (Phase 2) */ - PHP_CEDAR_OP_GE, /* >= (Phase 2) */ - PHP_CEDAR_OP_PLUS, /* + (Phase 4) */ - PHP_CEDAR_OP_MINUS, /* - (Phase 4) */ - PHP_CEDAR_OP_MUL /* * (Phase 4) */ + PHP_CEDAR_OP_LT, /* < */ + PHP_CEDAR_OP_GT, /* > */ + PHP_CEDAR_OP_LE, /* <= */ + PHP_CEDAR_OP_GE, /* >= */ + PHP_CEDAR_OP_PLUS, /* + */ + PHP_CEDAR_OP_MINUS, /* - */ + PHP_CEDAR_OP_MUL /* * */ } php_cedar_op_t; @@ -141,23 +143,27 @@ typedef enum { /* operations */ PHP_CEDAR_NODE_ATTR_ACCESS, /* expr.ident */ PHP_CEDAR_NODE_BINOP, /* ==, !=, <, >, <=, >=, &&, ||, in, - +, -, * (Phase 4) */ + +, -, * */ PHP_CEDAR_NODE_UNOP, /* ! */ PHP_CEDAR_NODE_NEGATE, /* - (unary) */ - /* Phase 2 */ + /* conditionals, like, method calls */ PHP_CEDAR_NODE_HAS, /* expr has ident */ PHP_CEDAR_NODE_LIKE, /* expr like "pattern" */ PHP_CEDAR_NODE_IF_THEN_ELSE, /* if expr then expr else expr */ PHP_CEDAR_NODE_METHOD_CALL, /* expr.method(args) */ - /* Phase 3 */ + /* extension literals */ PHP_CEDAR_NODE_IP_LITERAL, /* ip("addr") */ PHP_CEDAR_NODE_DECIMAL_LITERAL, /* decimal("1.23") */ - /* Phase 4 */ + /* is / record */ PHP_CEDAR_NODE_IS, /* expr is type_name [in expr] */ - PHP_CEDAR_NODE_RECORD /* { key: expr, ... } */ + PHP_CEDAR_NODE_RECORD, /* { key: expr, ... } */ + + /* datetime / duration extension types */ + PHP_CEDAR_NODE_DATETIME_LITERAL, /* datetime("2024-01-01T00:00:00Z") */ + PHP_CEDAR_NODE_DURATION_LITERAL /* duration("1d2h3m") */ } php_cedar_node_type_t; @@ -231,7 +237,13 @@ struct php_cedar_node_s { "lessThan", "lessThanOrEqual", "greaterThan", - "greaterThanOrEqual" */ + "greaterThanOrEqual", + "offset", + "durationSince", + "toDate", "toTime", + "toMilliseconds", + "toSeconds", "toMinutes", + "toHours", "toDays" */ php_cedar_node_t *arg; /* NULL for zero-arg methods (isIpv4 etc.) */ } method_call; @@ -244,7 +256,15 @@ struct php_cedar_node_s { php_cedar_str_t text; } decimal_literal; - struct { /* IS (Phase 4) */ + struct { /* DATETIME_LITERAL */ + php_cedar_str_t text; + } datetime_literal; + + struct { /* DURATION_LITERAL */ + php_cedar_str_t text; + } duration_literal; + + struct { /* IS */ php_cedar_node_t *object; /* expression under test */ php_cedar_str_t entity_type; /* type_name ("User", "Ns::User", ...) */ @@ -265,9 +285,8 @@ typedef enum { PHP_CEDAR_SCOPE_NONE, /* no constraint (matches all) */ PHP_CEDAR_SCOPE_EQ, /* == entity_ref */ PHP_CEDAR_SCOPE_IN, /* in entity_ref | set */ - PHP_CEDAR_SCOPE_IS, /* is type_name (Phase 4) */ - PHP_CEDAR_SCOPE_IS_IN /* is type_name in entity_ref - (Phase 4) */ + PHP_CEDAR_SCOPE_IS, /* is type_name */ + PHP_CEDAR_SCOPE_IS_IN /* is type_name in entity_ref */ } php_cedar_scope_constraint_t; /* scope constraint */ @@ -279,7 +298,7 @@ typedef struct { only; empty otherwise) */ } php_cedar_scope_t; -/* annotation (Phase 4) */ +/* annotation */ typedef struct { php_cedar_str_t key; /* annotation name (e.g. "id", "advice") */ php_cedar_str_t value; /* annotation value; empty if valueless */ @@ -295,7 +314,7 @@ typedef struct { typedef struct { unsigned is_forbid:1; /* 0 = permit, 1 = forbid */ php_cedar_array_t *annotations; /* array of php_cedar_annotation_t - (Phase 4, NULL if none) */ + (NULL if none) */ php_cedar_scope_t principal; php_cedar_scope_t action; php_cedar_scope_t resource; @@ -406,6 +425,8 @@ typedef struct { #define PHP_CEDAR_RVAL_IP 6 #define PHP_CEDAR_RVAL_RECORD 7 #define PHP_CEDAR_RVAL_DECIMAL 8 +#define PHP_CEDAR_RVAL_DATETIME 9 +#define PHP_CEDAR_RVAL_DURATION 10 /* @@ -449,6 +470,17 @@ typedef struct { * scaled value would overflow. */ int64_t decimal_val; + /* + * Cedar datetime: UTC milliseconds since the Unix epoch + * (1970-01-01T00:00:00Z), signed i64. A distinct type from Long + * with no implicit conversion. + */ + int64_t datetime_val; + /* + * Cedar duration: signed i64 count of milliseconds. Negative + * values are valid. A distinct type from Long. + */ + int64_t duration_val; } v; } php_cedar_value_t; diff --git a/src/cedar/php_cedar_util.h b/src/cedar/php_cedar_util.h index 82e11d7..43ef59e 100644 --- a/src/cedar/php_cedar_util.h +++ b/src/cedar/php_cedar_util.h @@ -17,7 +17,7 @@ /* string equality (shared across parser/expr/eval layers) */ static inline php_cedar_int_t -php_cedar_str_eq(php_cedar_str_t *a, php_cedar_str_t *b) +php_cedar_str_eq(const php_cedar_str_t *a, const php_cedar_str_t *b) { return (a->len == b->len && (a->len == 0 From 12d29655e4112e47ec8fdbffcc8b8bdb1972fecc Mon Sep 17 00:00:00 2001 From: kjdev Date: Mon, 1 Jun 2026 13:01:39 +0900 Subject: [PATCH 2/4] feat: expose datetime and duration AttributeValue members Wire the "datetime" and "duration" union kinds into the three AttributeValue injection paths (top-level eval context attribute, record member, set element), mirroring the existing "decimal" handling. Values are materialized at insertion time; a malformed value makes the injection API return an error, so the entry is skipped and surfaced in the response errors[] just like any other unsupported AttributeValue. --- cedar.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/cedar.c b/cedar.c index 30b2b47..f31132c 100644 --- a/cedar.c +++ b/cedar.c @@ -520,6 +520,26 @@ cedar_apply_top_attr(php_cedar_eval_ctx_t *ctx, cedar_attr_target_t tgt, } return PHP_CEDAR_ERROR; } + if (zend_string_equals_literal(kind, "datetime")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + switch (tgt) { + case CEDAR_TARGET_PRINCIPAL: return php_cedar_eval_ctx_add_principal_attr_datetime(ctx, name, &v); + case CEDAR_TARGET_ACTION: return php_cedar_eval_ctx_add_action_attr_datetime (ctx, name, &v); + case CEDAR_TARGET_RESOURCE: return php_cedar_eval_ctx_add_resource_attr_datetime (ctx, name, &v); + case CEDAR_TARGET_CONTEXT: return php_cedar_eval_ctx_add_context_attr_datetime (ctx, name, &v); + } + return PHP_CEDAR_ERROR; + } + if (zend_string_equals_literal(kind, "duration")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + switch (tgt) { + case CEDAR_TARGET_PRINCIPAL: return php_cedar_eval_ctx_add_principal_attr_duration(ctx, name, &v); + case CEDAR_TARGET_ACTION: return php_cedar_eval_ctx_add_action_attr_duration (ctx, name, &v); + case CEDAR_TARGET_RESOURCE: return php_cedar_eval_ctx_add_resource_attr_duration (ctx, name, &v); + case CEDAR_TARGET_CONTEXT: return php_cedar_eval_ctx_add_context_attr_duration (ctx, name, &v); + } + return PHP_CEDAR_ERROR; + } if (zend_string_equals_literal(kind, "entityIdentifier")) { php_cedar_str_t et, eid; if (cedar_pick_entity_ids(inner, @@ -558,7 +578,6 @@ cedar_apply_top_attr(php_cedar_eval_ctx_t *ctx, cedar_attr_target_t tgt, if (set == NULL) return PHP_CEDAR_ERROR; return cedar_apply_set_children(set, inner); } - /* datetime / duration are not supported (upstream gap). */ return PHP_CEDAR_ERROR; } @@ -598,6 +617,14 @@ cedar_apply_record_attr(php_cedar_record_t *rec, if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; return php_cedar_record_add_decimal(rec, name, &v); } + if (zend_string_equals_literal(kind, "datetime")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + return php_cedar_record_add_datetime(rec, name, &v); + } + if (zend_string_equals_literal(kind, "duration")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + return php_cedar_record_add_duration(rec, name, &v); + } if (zend_string_equals_literal(kind, "entityIdentifier")) { php_cedar_str_t et, eid; if (cedar_pick_entity_ids(inner, @@ -656,6 +683,14 @@ cedar_apply_set_element(php_cedar_set_t *set, zval *attr_val) if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; return php_cedar_set_add_decimal(set, &v); } + if (zend_string_equals_literal(kind, "datetime")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + return php_cedar_set_add_datetime(set, &v); + } + if (zend_string_equals_literal(kind, "duration")) { + if (cedar_zval_to_cedar_str(inner, &v) != PHP_CEDAR_OK) return PHP_CEDAR_ERROR; + return php_cedar_set_add_duration(set, &v); + } if (zend_string_equals_literal(kind, "entityIdentifier")) { php_cedar_str_t et, eid; if (cedar_pick_entity_ids(inner, From bf99c809f7758b05e412c588603ebc966ccb67bf Mon Sep 17 00:00:00 2001 From: kjdev Date: Mon, 1 Jun 2026 13:01:46 +0900 Subject: [PATCH 3/4] docs: document datetime and duration as supported AttributeValues Move datetime / duration out of the Unsupported features list, add them to the AttributeValue union examples, and update the malformed-value note to reference a genuinely unsupported case. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3323fa5..3da677d 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,8 @@ and the rest of the request payload is byte-for-byte identical. See [AVP compatibility](#avp-compatibility) for the per-key compatibility matrix and [Unsupported features](#unsupported-features) for the known -gaps (`datetime` / `duration`, entity tags, policy templates, schema -validation, dynamic identity sources). +gaps (entity tags, policy templates, schema validation, dynamic identity +sources). ## API overview @@ -249,6 +249,8 @@ Both methods accept the same keys as the corresponding AVP API: ['boolean' => true] ['ipaddr' => '10.0.0.1'] ['decimal' => '12.3400'] +['datetime' => '2026-01-01T00:00:00Z'] +['duration' => '1d12h'] ['entityIdentifier' => ['entityType' => 'MyApp::User', 'entityId' => 'bob']] ['set' => [AttributeValue, ...]] ['record' => [name => AttributeValue, ...]] @@ -340,9 +342,6 @@ The Cedar evaluator follows the feature set bundled from upstream nxe-cedar. The following features are **not** available in this release: -- `datetime` / `duration` `AttributeValue` types and their methods - (`<.`, `≤.`, `≥.`, `>.`, `toDate`, ...). Pass them as `long` (Unix - timestamps) and use `<`, `<=`, `>=`, `>` instead. - Entity tags (`.hasTag()` / `.getTag()`). - Policy templates (`?principal`, `?resource`) and template-linked policies. @@ -352,9 +351,10 @@ release: extension delegates that to the caller (see [Token verification](#token-verification-is-callers-responsibility)). -A malformed or unsupported `AttributeValue` (for example -`['datetime' => '...']`) does not abort the request: the entry is -skipped and an entry is appended to the response's `errors[]`. This +A malformed or unsupported `AttributeValue` (for example an unknown +union key, or `['datetime' => 'not-a-date']`) does not abort the +request: the entry is skipped and an entry is appended to the +response's `errors[]`. This matches AVP's behavior of returning a successful response with populated `errors` when a single attribute is broken. From ce6b211617f9b725cf8ae8192eff0a23f2d2c6e3 Mon Sep 17 00:00:00 2001 From: kjdev Date: Mon, 1 Jun 2026 13:01:46 +0900 Subject: [PATCH 4/4] test: cover datetime and duration AttributeValue injection Add a test exercising datetime / duration through the top-level, record-member, and set-element paths plus duration method dispatch. Update the malformed-attribute test to use an unknown union key and an invalid datetime string now that valid datetime values are accepted. --- tests/019-isauthorized-attr-malformed.phpt | 6 +- ...6-isauthorized-attr-datetime-duration.phpt | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/036-isauthorized-attr-datetime-duration.phpt diff --git a/tests/019-isauthorized-attr-malformed.phpt b/tests/019-isauthorized-attr-malformed.phpt index 0605b05..21a9c51 100644 --- a/tests/019-isauthorized-attr-malformed.phpt +++ b/tests/019-isauthorized-attr-malformed.phpt @@ -15,8 +15,8 @@ $res = $client->isAuthorized([ "action" => ["actionType" => "Action", "actionId" => "x"], "resource" => ["entityType" => "Doc", "entityId" => "d"], "context" => ["contextMap" => [ - "okScalar" => ["string" => "hello"], - "badUnknown" => ["datetime" => "2026-01-01T00:00:00Z"], + "okScalar" => ["string" => "hello"], + "badUnknown" => ["mysteryType" => "whatever"], "badEmpty" => [], ]], ]); @@ -36,7 +36,7 @@ $res = $client->isAuthorized([ "identifier" => ["entityType" => "User", "entityId" => "alice"], "attributes" => [ "okScalar" => ["string" => "hello"], - "broken" => ["datetime" => "2026-01-01T00:00:00Z"], + "broken" => ["datetime" => "not-a-valid-datetime"], ], ]]], ]); diff --git a/tests/036-isauthorized-attr-datetime-duration.phpt b/tests/036-isauthorized-attr-datetime-duration.phpt new file mode 100644 index 0000000..b492d37 --- /dev/null +++ b/tests/036-isauthorized-attr-datetime-duration.phpt @@ -0,0 +1,59 @@ +--TEST-- +AuthorizationClient::isAuthorized: AttributeValue Union 'datetime' and 'duration' members +--SKIPIF-- + +--FILE-- +loadString("p1", + 'permit(principal, action, resource) when {' + . ' context.now < datetime("2026-06-01T00:00:00Z")' + . ' && context.window > duration("30m")' + . ' && context.profile.createdAt < context.now' + . ' && context.windows.contains(duration("1h"))' + . ' };'); +$c = new Cedar\AuthorizationClient($store); + +$req = function (string $now) { + return [ + "policyStoreId" => "dt", + "principal" => ["entityType" => "User", "entityId" => "a"], + "action" => ["actionType" => "Action", "actionId" => "x"], + "resource" => ["entityType" => "Doc", "entityId" => "d"], + "context" => ["contextMap" => [ + "now" => ["datetime" => $now], + "window" => ["duration" => "1h"], + "profile" => ["record" => [ + "createdAt" => ["datetime" => "2026-01-01T00:00:00Z"], + ]], + "windows" => ["set" => [ + ["duration" => "1h"], + ["duration" => "2h"], + ]], + ]], + ]; +}; + +echo "allow: ", $c->isAuthorized($req("2026-05-01T12:00:00Z"))["decision"], PHP_EOL; +echo "deny: ", $c->isAuthorized($req("2026-12-01T00:00:00Z"))["decision"], PHP_EOL; + +/* duration methods dispatch on the injected value. */ +$m = new Cedar\PolicyStore("dur"); +$m->loadString("p1", 'permit(principal, action, resource) when { context.window.toHours() == 1 };'); +$cm = new Cedar\AuthorizationClient($m); +echo "method:", $cm->isAuthorized([ + "policyStoreId" => "dur", + "principal" => ["entityType" => "User", "entityId" => "a"], + "action" => ["actionType" => "Action", "actionId" => "x"], + "resource" => ["entityType" => "Doc", "entityId" => "d"], + "context" => ["contextMap" => ["window" => ["duration" => "60m"]]], +])["decision"], PHP_EOL; +?> +--EXPECT-- +allow: ALLOW +deny: DENY +method:ALLOW