From 62b54e8f71874e663c61ac284c6d3cee28421018 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Sun, 21 Apr 2024 15:52:16 +0200 Subject: [PATCH] fixup! oc_acl: refactor and add tests --- security/oc_ace.c | 173 +++++++++++-- security/oc_ace_internal.h | 14 +- security/oc_acl.c | 117 +-------- security/oc_acl_internal.h | 3 - security/unittest/acetest.cpp | 441 ++++++++++++++++++++++++++++++++++ 5 files changed, 615 insertions(+), 133 deletions(-) create mode 100644 security/unittest/acetest.cpp diff --git a/security/oc_ace.c b/security/oc_ace.c index 9336028fe..e990b4820 100644 --- a/security/oc_ace.c +++ b/security/oc_ace.c @@ -28,6 +28,18 @@ #include +#define OC_ACE_PROP_SUBJECT "subject" +#define OC_ACE_PROP_SUBJECT_UUID "uuid" +#define OC_ACE_PROP_SUBJECT_ROLE "role" +#define OC_ACE_PROP_SUBJECT_AUTHORITY "authority" +#define OC_ACE_PROP_SUBJECT_CONNTYPE "conntype" +#define OC_ACE_PROP_SUBJECT_PERMISSION "permission" +#define OC_ACE_PROP_SUBJECT_ACEID "aceid" +#define OC_ACE_PROP_TAG "tag" +#define OC_ACE_PROP_RESOURCES "resources" +#define OC_ACE_PROP_RESOURCE_HREF "href" +#define OC_ACE_PROP_RESOURCE_WILDCARD "wc" + #define MAX_NUM_RES_PERM_PAIRS \ ((OC_MAX_NUM_SUBJECTS + 2) * \ (OC_MAX_APP_RESOURCES + OC_NUM_CORE_PLATFORM_RESOURCES + \ @@ -37,6 +49,21 @@ OC_MEMB(g_res_l, oc_ace_res_t, OC_MAX_APP_RESOURCES + OC_NUM_CORE_PLATFORM_RESOURCES + OC_NUM_CORE_LOGICAL_DEVICE_RESOURCES * OC_MAX_NUM_DEVICES); +oc_string_view_t +oc_ace_wildcard_to_string(oc_ace_wildcard_t wc) +{ + if (wc == OC_ACE_WC_ALL) { + return OC_STRING_VIEW(OC_ACE_WC_ALL_STR); + } + if (wc == OC_ACE_WC_ALL_SECURED) { + return OC_STRING_VIEW(OC_ACE_WC_ALL_SECURED_STR); + } + if (wc == OC_ACE_WC_ALL_PUBLIC) { + return OC_STRING_VIEW(OC_ACE_WC_ALL_PUBLIC_STR); + } + return OC_STRING_VIEW_NULL; +} + #if OC_DBG_IS_ENABLED static void log_new_ace(const oc_sec_ace_t *ace) @@ -107,21 +134,10 @@ static void log_new_ace_resource(const oc_ace_res_t *res, uint16_t permission) { // GCOVR_EXCL_START - switch (res->wildcard) { - case OC_ACE_WC_ALL_SECURED: - OC_DBG("Adding wildcard resource %s with permission %d", - OC_ACE_WC_ALL_SECURED_STR, permission); - break; - case OC_ACE_WC_ALL_PUBLIC: - OC_DBG("Adding wildcard resource %s with permission %d", - OC_ACE_WC_ALL_PUBLIC_STR, permission); - break; - case OC_ACE_WC_ALL: - OC_DBG("Adding wildcard resource %s with permission %d", OC_ACE_WC_ALL_STR, + oc_string_view_t wcv = oc_ace_wildcard_to_string(res->wildcard); + if (wcv.data != NULL) { + OC_DBG("Adding wildcard resource %s with permission %d", wcv.data, permission); - break; - default: - break; } if (oc_string(res->href) != NULL) { OC_DBG("Adding resource %s with permission %d", oc_string(res->href), @@ -134,12 +150,17 @@ log_new_ace_resource(const oc_ace_res_t *res, uint16_t permission) static oc_ace_res_t * oc_sec_add_new_ace_res(oc_string_view_t href, oc_ace_wildcard_t wildcard) { + if (wildcard == OC_ACE_NO_WC && href.data == NULL) { + OC_ERR("wildcard and href cannot both be empty"); + return NULL; + } + oc_ace_res_t *res = oc_memb_alloc(&g_res_l); if (res == NULL) { OC_WRN("insufficient memory to add new resource to ACE"); return NULL; } - res->wildcard = 0; + res->wildcard = OC_ACE_NO_WC; if (wildcard != OC_ACE_NO_WC) { res->wildcard = wildcard; } @@ -305,4 +326,126 @@ oc_sec_ace_find_resource(oc_ace_res_t *start, const oc_sec_ace_t *ace, return ace_res_find_resource(res, href, wildcard); } +static oc_string_view_t +ace_connection_type_to_str(oc_ace_connection_type_t type) +{ + if (type == OC_CONN_AUTH_CRYPT) { + return OC_STRING_VIEW(OC_CONN_AUTH_CRYPT_STR); + } + if (type == OC_CONN_ANON_CLEAR) { + return OC_STRING_VIEW(OC_CONN_ANON_CLEAR_STR); + } + return OC_STRING_VIEW_NULL; +} + +static void +ace_encode_subject(CborEncoder *encoder, const oc_sec_ace_t *sub) +{ + if (sub->subject_type == OC_SUBJECT_UUID) { + char uuid[OC_UUID_LEN]; + int len = oc_uuid_to_str_v1(&sub->subject.uuid, uuid, OC_UUID_LEN); + assert(len > 0); + oc_string_view_t key = OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_UUID); + g_err |= oc_rep_object_set_text_string(encoder, key.data, key.length, uuid, + (size_t)len); + return; + } + + if (sub->subject_type == OC_SUBJECT_ROLE) { + oc_string_view_t role_key = OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_ROLE); + g_err |= oc_rep_object_set_text_string( + encoder, role_key.data, role_key.length, + oc_string(sub->subject.role.role), + oc_string_len_unsafe(sub->subject.role.role)); + if (!oc_string_is_empty(&sub->subject.role.authority)) { + oc_string_view_t authority_key = + OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_AUTHORITY); + g_err |= oc_rep_object_set_text_string( + encoder, authority_key.data, authority_key.length, + oc_string(sub->subject.role.authority), + oc_string_len_unsafe(sub->subject.role.authority)); + } + return; + } + + if (sub->subject_type == OC_SUBJECT_CONN) { + oc_string_view_t conntype_key = + OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_CONNTYPE); + oc_string_view_t conntype = ace_connection_type_to_str(sub->subject.conn); + g_err |= oc_rep_object_set_text_string(encoder, conntype_key.data, + conntype_key.length, conntype.data, + conntype.length); + return; + } +} + +static void +ace_encode_subject_resource(CborEncoder *encoder, const oc_ace_res_t *res) +{ + size_t href_len = oc_string_len(res->href); + if (href_len > 0) { + oc_string_view_t href_key = OC_STRING_VIEW(OC_ACE_PROP_RESOURCE_HREF); + g_err |= oc_rep_object_set_text_string( + encoder, href_key.data, href_key.length, oc_string(res->href), href_len); + return; + } + + oc_string_view_t wcv = oc_ace_wildcard_to_string(res->wildcard); + if (wcv.length > 0) { + oc_string_view_t wc_key = OC_STRING_VIEW(OC_ACE_PROP_RESOURCE_WILDCARD); + g_err |= oc_rep_object_set_text_string(encoder, wc_key.data, wc_key.length, + wcv.data, wcv.length); + return; + } +} + +static void +ace_encode_subject_resources(CborEncoder *encoder, const oc_ace_res_t *res) +{ + if (res == NULL) { + return; + } + oc_string_view_t key = OC_STRING_VIEW(OC_ACE_PROP_RESOURCES); + g_err |= oc_rep_encode_text_string(encoder, key.data, key.length); + oc_rep_begin_array(encoder, resources); + for (; res != NULL; res = res->next) { + oc_rep_object_array_begin_item(resources); + ace_encode_subject_resource(oc_rep_object(resources), res); + oc_rep_object_array_end_item(resources); + } + oc_rep_end_array(encoder, resources); +} + +void +oc_sec_encode_ace(CborEncoder *encoder, const oc_sec_ace_t *sub, + bool to_storage) +{ + oc_string_view_t subject_key = OC_STRING_VIEW(OC_ACE_PROP_SUBJECT); + g_err |= + oc_rep_encode_text_string(encoder, subject_key.data, subject_key.length); + oc_rep_begin_object(encoder, subject); + ace_encode_subject(oc_rep_object(subject), sub); + oc_rep_end_object(encoder, subject); + + ace_encode_subject_resources( + encoder, (const oc_ace_res_t *)oc_list_head(sub->resources)); + + oc_string_view_t permission_key = + OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_PERMISSION); + g_err |= oc_rep_object_set_uint(encoder, permission_key.data, + permission_key.length, sub->permission); + + oc_string_view_t aceid_key = OC_STRING_VIEW(OC_ACE_PROP_SUBJECT_ACEID); + g_err |= oc_rep_object_set_int(encoder, aceid_key.data, aceid_key.length, + sub->aceid); + if (to_storage) { + if (!oc_string_is_empty(&sub->tag)) { + oc_string_view_t tag_key = OC_STRING_VIEW(OC_ACE_PROP_TAG); + g_err |= oc_rep_object_set_text_string( + encoder, tag_key.data, tag_key.length, oc_string(sub->tag), + oc_string_len_unsafe(sub->tag)); // safe: oc_string_is_empty check above + } + } +} + #endif /* OC_SECURITY */ diff --git a/security/oc_ace_internal.h b/security/oc_ace_internal.h index b87896628..8b2221997 100644 --- a/security/oc_ace_internal.h +++ b/security/oc_ace_internal.h @@ -35,6 +35,12 @@ extern "C" { #define OC_ACE_WC_ALL_SECURED_STR "+" #define OC_ACE_WC_ALL_PUBLIC_STR "-" +#define OC_CONN_AUTH_CRYPT_STR "auth-crypt" +#define OC_CONN_ANON_CLEAR_STR "anon-clear" + +/** Convert wildcard to string representation */ +oc_string_view_t oc_ace_wildcard_to_string(oc_ace_wildcard_t wc); + /** Create a new ACE of given subject type */ oc_sec_ace_t *oc_sec_new_ace(oc_ace_subject_type_t type, const oc_ace_subject_t *subject, int aceid, @@ -58,18 +64,22 @@ typedef struct oc_ace_res_data_t bool created; } oc_ace_res_data_t; -/** Get an oc_ace_res_t* if it exists, otherwise create it */ +/** Get an ACE if it exists, otherwise create it */ oc_ace_res_data_t oc_sec_ace_get_or_add_res(oc_sec_ace_t *ace, oc_string_view_t href, oc_ace_wildcard_t wildcard, bool create) OC_NONNULL(); -/** Find an ace_res_t* match by href or a wildcard */ +/** Find an ACE match by href or a wildcard */ oc_ace_res_t *oc_sec_ace_find_resource(oc_ace_res_t *start, const oc_sec_ace_t *ace, oc_string_view_t href, uint16_t wildcard); +/** Encode an ACE to encoder */ +void oc_sec_encode_ace(CborEncoder *encoder, const oc_sec_ace_t *sub, + bool to_storage); + #ifdef __cplusplus } #endif diff --git a/security/oc_acl.c b/security/oc_acl.c index 97596b150..fb350a416 100644 --- a/security/oc_acl.c +++ b/security/oc_acl.c @@ -109,124 +109,15 @@ acl_get_new_aceid(size_t device) return aceid; } -#define OC_ACL_PROP_UUID "uuid" -#define OC_ACL_PROP_ROLE "role" -#define OC_ACL_PROP_AUTHORITY "authority" -#define OC_ACL_PROP_CONNTYPE "conntype" -#define OC_ACL_PROP_RESOURCES "resources" - -static oc_string_view_t -ace_connection_type_to_str(oc_ace_connection_type_t type) -{ - if (type == OC_CONN_AUTH_CRYPT) { - return OC_STRING_VIEW(OC_CONN_AUTH_CRYPT_STR); - } - if (type == OC_CONN_ANON_CLEAR) { - return OC_STRING_VIEW(OC_CONN_ANON_CLEAR_STR); - } - return OC_STRING_VIEW_NULL; -} - -static void -acl_encode_subject(CborEncoder *encoder, const oc_sec_ace_t *sub) -{ - if (sub->subject_type == OC_SUBJECT_UUID) { - char uuid[OC_UUID_LEN]; - int len = oc_uuid_to_str_v1(&sub->subject.uuid, uuid, OC_UUID_LEN); - assert(len > 0); - oc_string_view_t key = OC_STRING_VIEW(OC_ACL_PROP_UUID); - g_err |= oc_rep_object_set_text_string(encoder, key.data, key.length, uuid, - (size_t)len); - return; - } - - if (sub->subject_type == OC_SUBJECT_ROLE) { - oc_string_view_t role_key = OC_STRING_VIEW(OC_ACL_PROP_ROLE); - g_err |= oc_rep_object_set_text_string( - encoder, role_key.data, role_key.length, - oc_string(sub->subject.role.role), - oc_string_len_unsafe(sub->subject.role.role)); - if (!oc_string_is_empty(&sub->subject.role.authority)) { - oc_string_view_t authority_key = OC_STRING_VIEW(OC_ACL_PROP_AUTHORITY); - g_err |= oc_rep_object_set_text_string( - encoder, authority_key.data, authority_key.length, - oc_string(sub->subject.role.authority), - oc_string_len_unsafe(sub->subject.role.authority)); - } - return; - } - - if (sub->subject_type == OC_SUBJECT_CONN) { - oc_string_view_t conntype_key = OC_STRING_VIEW(OC_ACL_PROP_CONNTYPE); - oc_string_view_t conntype = ace_connection_type_to_str(sub->subject.conn); - g_err |= oc_rep_object_set_text_string(encoder, conntype_key.data, - conntype_key.length, conntype.data, - conntype.length); - return; - } -} - -static void -acl_encode_subject_resources(CborEncoder *encoder, const oc_ace_res_t *res) -{ - oc_string_view_t key = OC_STRING_VIEW(OC_ACL_PROP_RESOURCES); - g_err |= oc_rep_encode_text_string(encoder, key.data, key.length); - oc_rep_begin_array(encoder, resources); - for (; res != NULL; res = res->next) { - oc_rep_object_array_start_item(resources); - size_t href_len = oc_string_len(res->href); - if (href_len > 0) { - oc_rep_set_text_string_v1(resources, href, oc_string(res->href), - href_len); - } else { - switch (res->wildcard) { - case OC_ACE_WC_ALL_SECURED: - oc_rep_set_text_string_v1(resources, wc, OC_ACE_WC_ALL_SECURED_STR, - OC_CHAR_ARRAY_LEN(OC_ACE_WC_ALL_SECURED_STR)); - break; - case OC_ACE_WC_ALL_PUBLIC: - oc_rep_set_text_string_v1(resources, wc, OC_ACE_WC_ALL_PUBLIC_STR, - OC_CHAR_ARRAY_LEN(OC_ACE_WC_ALL_PUBLIC_STR)); - break; - case OC_ACE_WC_ALL: - oc_rep_set_text_string_v1(resources, wc, OC_ACE_WC_ALL_STR, - OC_CHAR_ARRAY_LEN(OC_ACE_WC_ALL_STR)); - break; - default: - break; - } - } - oc_rep_object_array_end_item(resources); - } - oc_rep_end_array(encoder, resources); -} - static void acl_encode_subjects(oc_list_t subjects, bool to_storage) { oc_rep_open_array(root, aclist2); - const oc_sec_ace_t *sub = oc_list_head(subjects); - - while (sub != NULL) { - oc_rep_object_array_start_item(aclist2); - - oc_rep_open_object(aclist2, subject); - acl_encode_subject(oc_rep_object(subject), sub); - oc_rep_close_object(aclist2, subject); - - acl_encode_subject_resources( - oc_rep_object(aclist2), - (const oc_ace_res_t *)oc_list_head(sub->resources)); - - oc_rep_set_uint(aclist2, permission, sub->permission); - oc_rep_set_int(aclist2, aceid, sub->aceid); - if (to_storage) { - if (oc_string_len(sub->tag) > 0) { - oc_rep_set_text_string(aclist2, tag, oc_string(sub->tag)); - } - } + for (const oc_sec_ace_t *sub = oc_list_head(subjects); sub != NULL; + sub = sub->next) { + oc_rep_object_array_begin_item(aclist2); + oc_sec_encode_ace(oc_rep_object(aclist2), sub, to_storage); oc_rep_object_array_end_item(aclist2); - sub = sub->next; } oc_rep_close_array(root, aclist2); } diff --git a/security/oc_acl_internal.h b/security/oc_acl_internal.h index cb4a12e2d..0f76be3d9 100644 --- a/security/oc_acl_internal.h +++ b/security/oc_acl_internal.h @@ -38,9 +38,6 @@ extern "C" { #define OCF_SEC_ACL_URI "/oic/sec/acl2" #define OCF_SEC_ACL_RT "oic.r.acl2" -#define OC_CONN_AUTH_CRYPT_STR "auth-crypt" -#define OC_CONN_ANON_CLEAR_STR "anon-clear" - /** @brief Allocate and initialize global variables */ void oc_sec_acl_init(void); diff --git a/security/unittest/acetest.cpp b/security/unittest/acetest.cpp new file mode 100644 index 000000000..2c5db151f --- /dev/null +++ b/security/unittest/acetest.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifdef OC_SECURITY + +#include "api/oc_helpers_internal.h" +#include "oc_enums.h" +#include "oc_rep.h" +#include "oc_uuid.h" +#include "port/oc_log_internal.h" +#include "port/oc_random.h" +#include "security/oc_ace_internal.h" +#include "tests/gtest/RepPool.h" +#include "util/oc_list.h" + +#include + +class TestACE : public testing::Test { +public: + static void SetUpTestCase() { oc_random_init(); } + + static void TearDownTestCase() { oc_random_destroy(); } + + void SetUp() override + { + // TODO: rm + oc_log_set_level(OC_LOG_LEVEL_DEBUG); + } + + void TearDown() override { oc_log_set_level(OC_LOG_LEVEL_INFO); } +}; + +TEST_F(TestACE, NewUUID) +{ + oc_uuid_t uuid{}; + oc_gen_uuid(&uuid); + oc_ace_subject_t subject_uuid{}; + subject_uuid.uuid = uuid; + auto tag = OC_STRING_VIEW("l33t"); + oc_sec_ace_t *ace = + oc_sec_new_ace(OC_SUBJECT_UUID, &subject_uuid, 42, OC_PERM_RETRIEVE, tag); + ASSERT_NE(ace, nullptr); + + EXPECT_EQ(OC_SUBJECT_UUID, ace->subject_type); + EXPECT_TRUE(oc_uuid_is_equal(uuid, ace->subject.uuid)); + EXPECT_EQ(42, ace->aceid); + EXPECT_EQ(OC_PERM_RETRIEVE, ace->permission); + EXPECT_EQ(0, oc_list_length(ace->resources)); + ASSERT_NE(nullptr, oc_string(ace->tag)); + EXPECT_STREQ(tag.data, oc_string(ace->tag)); + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, NewRole) +{ + oc_ace_subject_t subject_role{}; + auto testRole = OC_STRING_LOCAL("test.role"); + subject_role.role = { testRole, {} }; + auto tag = OC_STRING_VIEW("role"); + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_ROLE, &subject_role, 13, + OC_PERM_RETRIEVE | OC_PERM_UPDATE, tag); + ASSERT_NE(ace, nullptr); + ASSERT_EQ(OC_SUBJECT_ROLE, ace->subject_type); + ASSERT_NE(nullptr, oc_string(ace->subject.role.role)); + EXPECT_STREQ(oc_string(testRole), oc_string(ace->subject.role.role)); + ASSERT_EQ(nullptr, oc_string(ace->subject.role.authority)); + EXPECT_EQ(13, ace->aceid); + EXPECT_EQ(OC_PERM_RETRIEVE | OC_PERM_UPDATE, ace->permission); + EXPECT_EQ(0, oc_list_length(ace->resources)); + ASSERT_NE(nullptr, oc_string(ace->tag)); + EXPECT_STREQ(tag.data, oc_string(ace->tag)); + oc_sec_free_ace(ace); + + // empty role.authority is equal to NULL + auto testAuthority = OC_STRING_LOCAL(""); + ace = oc_sec_new_ace(OC_SUBJECT_ROLE, &subject_role, 13, + OC_PERM_RETRIEVE | OC_PERM_UPDATE, tag); + ASSERT_NE(ace, nullptr); + ASSERT_EQ(nullptr, oc_string(ace->subject.role.authority)); + oc_sec_free_ace(ace); + + oc_ace_subject_t subject_role_with_authority{}; + testRole = OC_STRING_LOCAL("test.newrole"); + testAuthority = OC_STRING_LOCAL("test.authority"); + subject_role_with_authority.role = { testRole, testAuthority }; + ace = oc_sec_new_ace(OC_SUBJECT_ROLE, &subject_role_with_authority, 37, + OC_PERM_DELETE | OC_PERM_NOTIFY, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); + ASSERT_EQ(OC_SUBJECT_ROLE, ace->subject_type); + ASSERT_NE(nullptr, oc_string(ace->subject.role.role)); + EXPECT_STREQ(oc_string(testRole), oc_string(ace->subject.role.role)); + ASSERT_NE(nullptr, oc_string(ace->subject.role.authority)); + EXPECT_STREQ(oc_string(testAuthority), + oc_string(ace->subject.role.authority)); + EXPECT_EQ(37, ace->aceid); + EXPECT_EQ(OC_PERM_DELETE | OC_PERM_NOTIFY, ace->permission); + EXPECT_EQ(0, oc_list_length(ace->resources)); + EXPECT_EQ(nullptr, oc_string(ace->tag)); + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, NewAnonConn) +{ + oc_ace_subject_t anon_conn{}; + anon_conn.conn = OC_CONN_ANON_CLEAR; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, &anon_conn, 1, + OC_PERM_NONE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); + ASSERT_EQ(OC_SUBJECT_CONN, ace->subject_type); + EXPECT_EQ(OC_CONN_ANON_CLEAR, ace->subject.conn); + EXPECT_EQ(1, ace->aceid); + EXPECT_EQ(OC_PERM_NONE, ace->permission); + EXPECT_EQ(0, oc_list_length(ace->resources)); + EXPECT_EQ(nullptr, oc_string(ace->tag)); + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, NewCryptConn) +{ + oc_ace_subject_t crypt_conn{}; + crypt_conn.conn = OC_CONN_AUTH_CRYPT; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, &crypt_conn, 2, + OC_PERM_CREATE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); + ASSERT_EQ(OC_SUBJECT_CONN, ace->subject_type); + EXPECT_EQ(OC_CONN_AUTH_CRYPT, ace->subject.conn); + EXPECT_EQ(2, ace->aceid); + EXPECT_EQ(OC_PERM_CREATE, ace->permission); + EXPECT_EQ(0, oc_list_length(ace->resources)); + EXPECT_EQ(nullptr, oc_string(ace->tag)); + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, GetOrAddResource) +{ + oc_uuid_t uuid{}; + oc_gen_uuid(&uuid); + oc_ace_subject_t subject_uuid{}; + subject_uuid.uuid = uuid; + auto tag = OC_STRING_VIEW("l33t"); + oc_sec_ace_t *ace = + oc_sec_new_ace(OC_SUBJECT_UUID, &subject_uuid, 42, OC_PERM_RETRIEVE, tag); + ASSERT_NE(ace, nullptr); + + oc_string_view_t href = OC_STRING_VIEW("/uri/1"); + auto res_data = oc_sec_ace_get_or_add_res(ace, href, OC_ACE_NO_WC, true); + EXPECT_TRUE(res_data.created); + EXPECT_NE(nullptr, res_data.res); + + // get the same resource + res_data = oc_sec_ace_get_or_add_res(ace, href, OC_ACE_NO_WC, false); + EXPECT_FALSE(res_data.created); + EXPECT_NE(nullptr, res_data.res); + + // cannot create the same resource again, get will be invoked instead + res_data = oc_sec_ace_get_or_add_res(ace, href, OC_ACE_NO_WC, true); + EXPECT_FALSE(res_data.created); + EXPECT_NE(nullptr, res_data.res); + + // trying to create a resource without href or wildcard should fail + res_data = + oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, OC_ACE_NO_WC, true); + EXPECT_FALSE(res_data.created); + EXPECT_EQ(nullptr, res_data.res); + + // try to get a resource that does not exist + oc_string_view_t href2 = OC_STRING_VIEW("/uri/2"); + res_data = oc_sec_ace_get_or_add_res(ace, href2, OC_ACE_NO_WC, false); + EXPECT_FALSE(res_data.created); + EXPECT_EQ(nullptr, res_data.res); + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, FindResource) +{ + oc_uuid_t uuid{}; + oc_gen_uuid(&uuid); + oc_ace_subject_t subject_uuid{}; + subject_uuid.uuid = uuid; + auto tag = OC_STRING_VIEW("l33t"); + oc_sec_ace_t *ace = + oc_sec_new_ace(OC_SUBJECT_UUID, &subject_uuid, 42, OC_PERM_RETRIEVE, tag); + ASSERT_NE(ace, nullptr); + + // href-only + oc_string_view_t href = OC_STRING_VIEW("/uri/1"); + auto res_data = oc_sec_ace_get_or_add_res(ace, href, OC_ACE_NO_WC, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // href + wildcard + oc_string_view_t href2 = OC_STRING_VIEW("/uri/2"); + res_data = oc_sec_ace_get_or_add_res(ace, href2, OC_ACE_WC_ALL_SECURED, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // wildcard-only + res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_PUBLIC, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // wc-all + res_data = + oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, OC_ACE_WC_ALL, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // once wc-all is added, adding other wildcard resources should fail + res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED, true); + ASSERT_FALSE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_PUBLIC, true); + ASSERT_FALSE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // find the resource by href + oc_ace_res_t *res = + oc_sec_ace_find_resource(nullptr, ace, href, OC_ACE_NO_WC); + ASSERT_NE(nullptr, res); + EXPECT_STREQ(href.data, oc_string(res->href)); + // no other should match + res = oc_sec_ace_find_resource(res, nullptr, href, OC_ACE_NO_WC); + EXPECT_EQ(nullptr, res); + + // find the resource by wildcard (2 resource match = secured + all) + int count = 0; + res = oc_sec_ace_find_resource(nullptr, ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED); + ASSERT_NE(nullptr, res); + while (res != NULL) { + ++count; + res = oc_sec_ace_find_resource(res, nullptr, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED); + } + EXPECT_EQ(2, count); + + // find public + secured wildcard (3 resources match = public, secured + all) + count = 0; + res = oc_sec_ace_find_resource(nullptr, ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED | OC_ACE_WC_ALL_PUBLIC); + ASSERT_NE(nullptr, res); + while (res != NULL) { + ++count; + res = + oc_sec_ace_find_resource(res, nullptr, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED | OC_ACE_WC_ALL_PUBLIC); + } + EXPECT_EQ(3, count); + + // find wc-all + res = + oc_sec_ace_find_resource(nullptr, ace, OC_STRING_VIEW_NULL, OC_ACE_WC_ALL); + ASSERT_NE(nullptr, res); + ASSERT_EQ(nullptr, oc_string(res->href)); + EXPECT_EQ(OC_ACE_WC_ALL, res->wildcard); + // no other should match + res = + oc_sec_ace_find_resource(res, nullptr, OC_STRING_VIEW_NULL, OC_ACE_WC_ALL); + EXPECT_EQ(nullptr, res); + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeUUID) +{ + oc_uuid_t uuid{}; + oc_gen_uuid(&uuid); + oc_ace_subject_t subject_uuid{}; + subject_uuid.uuid = uuid; + auto tag = OC_STRING_VIEW("l33t"); + oc_sec_ace_t *ace = + oc_sec_new_ace(OC_SUBJECT_UUID, &subject_uuid, 42, OC_PERM_RETRIEVE, tag); + ASSERT_NE(ace, nullptr); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, true); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeRole) +{ + oc_ace_subject_t subject_role{}; + auto testRole = OC_STRING_LOCAL("test.role"); + auto testAuthority = OC_STRING_LOCAL("test.authority"); + subject_role.role = { testRole, testAuthority }; + oc_sec_ace_t *ace = + oc_sec_new_ace(OC_SUBJECT_ROLE, &subject_role, 13, + OC_PERM_RETRIEVE | OC_PERM_UPDATE, OC_STRING_VIEW_NULL); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, true); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeAnonConn) +{ + oc_ace_subject_t anon_conn{}; + anon_conn.conn = OC_CONN_ANON_CLEAR; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, &anon_conn, 1, + OC_PERM_NONE, OC_STRING_VIEW_NULL); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, false); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeCryptConn) +{ + oc_ace_subject_t crypt_conn{}; + crypt_conn.conn = OC_CONN_AUTH_CRYPT; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, &crypt_conn, 2, + OC_PERM_CREATE, OC_STRING_VIEW_NULL); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, false); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeWithResources) +{ + oc_ace_subject_t subject_role{}; + auto testRole = OC_STRING_LOCAL("test.role"); + subject_role.role = { testRole, {} }; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_ROLE, &subject_role, 17, + OC_PERM_NOTIFY, OC_STRING_VIEW_NULL); + + // href resource + oc_string_view_t href = OC_STRING_VIEW("/uri/1"); + auto res_data = oc_sec_ace_get_or_add_res(ace, href, OC_ACE_NO_WC, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // wc-all secured resource + res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + // wc-all public resource + res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_PUBLIC, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, false); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +TEST_F(TestACE, EncodeWithWCAllResource) +{ + oc_ace_subject_t anon_conn{}; + anon_conn.conn = OC_CONN_ANON_CLEAR; + oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, &anon_conn, 1, + OC_PERM_CREATE, OC_STRING_VIEW_NULL); + + // wc-all resource + auto res_data = + oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, OC_ACE_WC_ALL, true); + ASSERT_TRUE(res_data.created); + ASSERT_NE(nullptr, res_data.res); + + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_sec_encode_ace(oc_rep_object(root), ace, false); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc::oc_rep_unique_ptr rep = pool.ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + // TODO: decode and check + + oc_sec_free_ace(ace); +} + +#endif /* OC_SECURITY */