From 5200d7022b34c0be646ae92de8c638c9437c7072 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Bollo?= Date: Wed, 5 Apr 2017 14:47:12 +0200 Subject: [PATCH] Add permission subsystem MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I0bbbf55832faf6413dd6147174934d0d4dc5277d Signed-off-by: José Bollo --- CMakeLists.txt | 1 + src/CMakeLists.txt | 3 + src/afb-perm.c | 517 +++++++++++++++++++++++++++++++++++++ src/afb-perm.h | 26 ++ src/tests/CMakeLists.txt | 20 ++ src/tests/test-perm/CMakeLists.txt | 24 ++ src/tests/test-perm/test-perm.c | 166 ++++++++++++ 7 files changed, 757 insertions(+) create mode 100644 src/afb-perm.c create mode 100644 src/afb-perm.h create mode 100644 src/tests/CMakeLists.txt create mode 100644 src/tests/test-perm/CMakeLists.txt create mode 100644 src/tests/test-perm/test-perm.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c5a1e7e0..28fc1e7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ INCLUDE(FindPkgConfig) INCLUDE(CheckIncludeFiles) INCLUDE(CheckLibraryExists) INCLUDE(GNUInstallDirs) +INCLUDE(CTest) ########################################################################### diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f465509..83977e23 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,8 @@ endif(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.9) INCLUDE(FindPkgConfig) +ADD_SUBDIRECTORY(tests) + ADD_DEFINITIONS(-DBINDING_INSTALL_DIR="${binding_install_dir}") # Always add INFER_EXTENSION (more details in afb-hreq.c) ADD_DEFINITIONS(-DINFER_EXTENSION) @@ -70,6 +72,7 @@ ADD_LIBRARY(afb-lib STATIC afb-hswitch.c afb-method.c afb-msg-json.c + afb-perm.c afb-session.c afb-svc.c afb-subcall.c diff --git a/src/afb-perm.c b/src/afb-perm.c new file mode 100644 index 00000000..b4d2b5e3 --- /dev/null +++ b/src/afb-perm.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author José Bollo + * + * 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. + */ + + +#include +#include +#include +#include +#include + +#include "afb-perm.h" +#include "verbose.h" + +/********************************************************************* +*** SECTION node +*********************************************************************/ + +/** + * types of nodes + */ +enum type +{ + Text, /**< text node */ + And, /**< and node */ + Or, /**< or node */ + Not /**< not node */ +}; + +/** + * structure for nodes + */ +struct node +{ + enum type type; /**< type of the node */ + union { + struct node *children[2]; /**< possible subnodes */ + char text[1]; /**< text of the node */ + }; +}; + +/** + * make a text node and return it + * @param type type of the node (should be Text) + * @param text the text to set for the node + * @param length the length of the text + * @return the allocated node or NULL on memory depletion + */ +static struct node *node_make_text(enum type type, const char *text, size_t length) +{ + struct node *node; + + node = malloc(length + sizeof *node); + if (!node) + errno = ENOMEM; + else { + node->type = type; + memcpy(node->text, text, length); + node->text[length] = 0; + } + return node; +} + +/** + * make a node with sub nodes and return it + * @param type type of the node (should be And, Or or Text) + * @param left the "left" sub node + * @param right the "right" sub node (if any or NULL) + * @return the allocated node or NULL on memory depletion + */ +static struct node *node_make_parent(enum type type, struct node *left, struct node *right) +{ + struct node *node; + + node = malloc(sizeof *node); + if (!node) + errno = ENOMEM; + else { + node->type = type; + node->children[0] = left; + node->children[1] = right; + } + return node; +} + +/** + * Frees the node and its possible subnodes + * @param node the node to free + */ +static void node_free(struct node *node) +{ + struct node *left, *right; + + if (node) { + switch (node->type) { + case Text: + free(node); + break; + case And: + case Or: + left = node->children[0]; + right = node->children[1]; + free(node); + node_free(left); + node_free(right); + break; + case Not: + left = node->children[0]; + free(node); + node_free(left); + break; + } + } +} + +/** + * Checks the permissions for the 'node' using the 'check' function + * and its 'closure'. + * @param node the node to check + * @param check the function that checks if a pernmission of 'name' is granted for 'closure' + * @param closure the context closure for the function 'check' + * @return 1 if the permission is granted or 0 otherwise + */ +static int node_check(struct node *node, int (*check)(void *closure, const char *name), void *closure) +{ + int rc; + + switch (node->type) { + case Text: + rc = check(closure, node->text); + break; + case And: + rc = node_check(node->children[0], check, closure); + if (rc) + rc = node_check(node->children[1], check, closure); + break; + case Or: + rc = node_check(node->children[0], check, closure); + if (!rc) + rc = node_check(node->children[1], check, closure); + break; + case Not: + rc = !node_check(node->children[0], check, closure); + break; + } + return rc; +} + +/********************************************************************* +*** SECTION parse +*********************************************************************/ + +/** + * the symbol types + */ +enum symbol +{ + TEXT, /**< a common text, name of a permission */ + AND, /**< and keyword */ + OR, /**< or keyword */ + NOT, /**< not keyword */ + OBRA, /**< open bracket */ + CBRA, /**< close bracket */ + END /**< end of input */ +}; + +/** + * structure for parsing permission description + */ +struct parse +{ + const char *desc; /**< initial permission description */ + const char *symbol; /**< current symbol parsed */ + size_t symlen; /**< length of the current symbol */ + enum symbol type; /**< type of the current symbol */ +}; + +/** + * updates parse to point to the next symbol if any + * @param parse parser state to update + */ +static void parse_next(struct parse *parse) +{ + const char *scan; + size_t len; + + /* skip current symbol */ + scan = &parse->symbol[parse->symlen]; + + /* skip white spaces */ + while (*scan && isspace(*scan)) + scan++; + + /* scan the symbol */ + switch (*scan) { + case 0: + len = 0; + parse->type = END; + break; + case '(': + len = 1; + parse->type = OBRA; + break; + case ')': + len = 1; + parse->type = CBRA; + break; + default: + /* compute the length */ + len = 0; + while (scan[len] && !isspace(scan[len]) && scan[len] != ')' && scan[len] != '(') + len++; + parse->type = TEXT; + + /* fall to keyword if any */ + switch(len) { + case 2: + if (!strncasecmp(scan, "or", len)) + parse->type = OR; + break; + case 3: + if (!strncasecmp(scan, "and", len)) + parse->type = AND; + else if (!strncasecmp(scan, "not", len)) + parse->type = NOT; + break; + } + break; + } + parse->symbol = scan; + parse->symlen = len; +} + +/** + * Init the parser state 'parse' for the description 'desc' + * @param parse the parser state to initialise + * @param desc the description of the permissions to be parsed + */ +static void parse_init(struct parse *parse, const char *desc) +{ + parse->desc = desc; + parse->symbol = desc; + parse->symlen = 0; + parse_next(parse); +} + +/********************************************************************* +*** SECTION node_parse +*********************************************************************/ + +static struct node *node_parse_or(struct parse *parse); + +/** + * Parse a permission name + * @param parser the parser state + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_text(struct parse *parse) +{ + struct node *node; + + if (parse->type == TEXT) { + node = node_make_text(Text, parse->symbol, parse->symlen); + parse_next(parse); + } else { + errno = EINVAL; + node = NULL; + } + return node; +} + +/** + * Parse a term that is either a name (text) or a sub expression + * enclosed in brackets. + * @param parser the parser state + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_term(struct parse *parse) +{ + struct node *node; + + if (parse->type != OBRA) + node = node_parse_text(parse); + else { + parse_next(parse); + node = node_parse_or(parse); + if (parse->type == CBRA) + parse_next(parse); + else { + errno = EINVAL; + node_free(node); + node = NULL; + } + } + return node; +} + +/** + * Parse a term potentially prefixed by not. + * @param parser the parser state + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_not(struct parse *parse) +{ + struct node *node, *not; + + if (parse->type != NOT) + node = node_parse_term(parse); + else { + parse_next(parse); + node = node_parse_term(parse); + if (node) { + not = node_make_parent(Not, node, NULL); + if (not) + node = not; + else { + node_free(node); + node = NULL; + } + } + } + return node; +} + +/** + * Parse a potential sequence of terms connected with the + * given operator (AND or OR). The function takes care to + * create an evaluation tree that respects the order given + * by the description and that will limit the recursivity + * depth. + * @param parser the parser state + * @param operator the symbol type of the operator scanned + * @param subparse the function for parsing terms of the sequence + * @param type the node type corresponding to the operator + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_infix( + struct parse *parse, + enum symbol operator, + struct node *(*subparse)(struct parse*), + enum type type +) +{ + struct node *root, **up, *right, *node; + + root = subparse(parse); + if (root) { + up = &root; + while (parse->type == operator) { + parse_next(parse); + right = subparse(parse); + if (!right) { + node_free(root); + root = NULL; + break; + } + node = node_make_parent(type, *up, right); + if (!node) { + node_free(right); + node_free(root); + root = NULL; + break; + } + *up = node; + up = &node->children[1]; + } + } + return root; +} + +/** + * Parse a potential sequence of anded terms. + * @param parser the parser state + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_and(struct parse *parse) +{ + return node_parse_infix(parse, AND, node_parse_not, And); +} + +/** + * Parse a potential sequence of ored terms. + * @param parser the parser state + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse_or(struct parse *parse) +{ + return node_parse_infix(parse, OR, node_parse_and, Or); +} + +/** + * Parse description of permissions. + * @param desc the description to parse + * @return the parsed node or NULL in case of error + * in case of error errno is set to EINVAL or ENOMEM + */ +static struct node *node_parse(const char *desc) +{ + struct node *node; + struct parse parse; + + parse_init(&parse, desc); + node = node_parse_or(&parse); + if (node && parse.type != END) { + node_free(node); + node = NULL; + } + return node; +} + +/********************************************************************* +*** SECTION perm +*********************************************************************/ + +/** + * structure for storing permissions + */ +struct afb_perm +{ + struct node *root; /**< root node descripbing the permission */ + int refcount; /**< the count of use of the structure */ +}; + +/** + * allocates the structure for the given root + * @param root the root node to keep + * @return the created permission object or NULL + */ +static struct afb_perm *make_perm(struct node *root) +{ + struct afb_perm *perm; + + perm = malloc(sizeof *perm); + if (!perm) + errno = ENOMEM; + else { + perm->root = root; + perm->refcount = 1; + } + return perm; +} + +/** + * Creates the permission for the given description + * @param desc the description of the permission to create + * @return the created permission object or NULL + */ +struct afb_perm *afb_perm_parse(const char *desc) +{ + struct node *root; + struct afb_perm *perm; + + root = node_parse(desc); + if (root) { + perm = make_perm(root); + if (perm) + return perm; + node_free(root); + } + return NULL; +} + +/** + * Adds a reference to the permissions + * @param perm the permission to reference + */ +void afb_perm_addref(struct afb_perm *perm) +{ + assert(perm); + perm->refcount++; +} + +/** + * Removes a reference to the permissions + * @param perm the permission to dereference + */ +void afb_perm_unref(struct afb_perm *perm) +{ + if (perm && !--perm->refcount) { + node_free(perm->root); + free(perm); + } +} + +/** + * Checks permission 'perm' using the 'check' function + * and its 'closure'. + * @param perm the permission to check + * @param check the function that checks if a pernmission of 'name' is granted for 'closure' + * @param closure the context closure for the function 'check' + * @return 1 if the permission is granted or 0 otherwise + */ +int afb_perm_check(struct afb_perm *perm, int (*check)(void *closure, const char *name), void *closure) +{ + return node_check(perm->root, check, closure); +} + + diff --git a/src/afb-perm.h b/src/afb-perm.h new file mode 100644 index 00000000..e6df93c3 --- /dev/null +++ b/src/afb-perm.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author José Bollo + * + * 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. + */ + +#pragma once + +struct afb_perm; + +extern struct afb_perm *afb_perm_parse(const char *desc); +extern void afb_perm_addref(struct afb_perm *perm); +extern void afb_perm_unref(struct afb_perm *perm); +extern int afb_perm_check(struct afb_perm *perm, int (*check)(void *closure, const char *name), void *closure); + diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 00000000..4a1345ee --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: José Bollo +# +# 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. +########################################################################### + +add_subdirectory(test-perm) + diff --git a/src/tests/test-perm/CMakeLists.txt b/src/tests/test-perm/CMakeLists.txt new file mode 100644 index 00000000..4747d182 --- /dev/null +++ b/src/tests/test-perm/CMakeLists.txt @@ -0,0 +1,24 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: José Bollo +# +# 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. +########################################################################### + +include_directories(../..) +add_executable(test-perm test-perm.c) +target_link_libraries(test-perm afb-lib) +add_test(NAME test-perm COMMAND test-perm) + + diff --git a/src/tests/test-perm/test-perm.c b/src/tests/test-perm/test-perm.c new file mode 100644 index 00000000..8d63e3c7 --- /dev/null +++ b/src/tests/test-perm/test-perm.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author José Bollo + * + * 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. + */ + + +#include +#include +#include + +#include "afb-perm.h" + +char *exprs[] = { + "a", + "not a", + "a or b", + "a or b or c", + "a or b or c or d", + "a and b", + "a and b and c", + "a and b and c and d", + "a and b or c and d", + "a or b and c or d", + "(a or b) and (c or d)", + "not (a or b or c or d)", + "a and not (b or c or d)", + "b and not (a or c or d)", + "c and not (a or b or d)", + "d and not (a or b or c)", + NULL +}; + +int check(void *closure, const char *name) +{ + int x; + + x = *(int*)closure; + if (name[0] < 'a' || name[0] > 'd' || name[1]) + return 0; + return 1 & (x >> (name[0] - 'a')); +} + +int test(const char *expr) +{ + int x, r, m, c; + struct afb_perm *perm; + + r = 0; + m = 1; + perm = afb_perm_parse(expr); + if (!perm) + printf("error for %s\n", expr); + else { + printf("\nabcd %s\n", expr); + for (x = 0; x < 16 ; x++) { + c = afb_perm_check(perm, check, &x); + printf("%c%c%c%c %d\n", + '0'+!!(x&1), '0'+!!(x&2), '0'+!!(x&4), '0'+!!(x&8), + c); + if (c) + r |= m; + m <<= 1; + } + } + afb_perm_unref(perm); + return r; +} + +void add(char *buffer, const char *fmt, ...) +{ + char b[60]; + va_list vl; + + va_start(vl, fmt); + vsprintf(b, fmt, vl); + va_end(vl); + strcat(buffer, b); +} + +void mke(int value, int bits, char *buffer) +{ + int nval = 1 << bits; + int sval = 1 << (bits - 1); + int mask = (1 << nval) - 1; + int smask = (1 << sval) - 1; + int val = value & mask; + int val0 = val & smask; + int val1 = (val >> sval) & smask; + char c = (char)('a' + bits - 1); + + if (bits == 1) { + switch(val) { + case 0: add(buffer, "x"); break; + case 1: add(buffer, "not %c", c); break; + case 2: add(buffer, "%c", c); break; + case 3: add(buffer, "(%c or not %c) ", c, c); break; + } + } else if (val0 != val1) { + if (val0) { + add(buffer, "not %c", c); + if (val0 != smask) { + add(buffer, " and ("); + mke(val0, bits - 1, buffer); + add(buffer, ")"); + } + } + if (val0 && val1) + add(buffer, " or "); + if (val1) { + add(buffer, "%c", c); + if (val1 != smask) { + add(buffer, " and ("); + mke(val1, bits - 1, buffer); + add(buffer, ")"); + } + } + } else { + mke(val0, bits - 1, buffer); + } +} + +void makeexpr(int value, char *buffer) +{ + if (!value) + strcpy(buffer, "x"); + else { + buffer[0] = 0; + mke(value, 4, buffer); + } +} + +int fulltest() +{ + char buffer[4096]; + int i, j, r; + + r = 0; + for (i = 0 ; i < 65536 ; i++) { + makeexpr(i, buffer); + j = test(buffer); + printf("[[[ %d %s %d ]]]\n", i, i==j?"==":"!=", j); + if (i != j) + r = 1; + } +} + +int main() +{ + int i = 0; + while(exprs[i]) + test(exprs[i++]); + return fulltest(); +} + -- 2.16.6