diff options
Diffstat (limited to 'old-docs')
-rw-r--r-- | old-docs/compilation.mdwn | 190 | ||||
-rw-r--r-- | old-docs/developing.mdwn | 55 | ||||
-rw-r--r-- | old-docs/execution.mdwn | 94 | ||||
-rw-r--r-- | old-docs/index.mdwn | 25 | ||||
-rw-r--r-- | old-docs/syntax-allow-deny.mdwn | 20 | ||||
-rw-r--r-- | old-docs/syntax-default.mdwn | 33 | ||||
-rw-r--r-- | old-docs/syntax-define.mdwn | 31 | ||||
-rw-r--r-- | old-docs/syntax-include.mdwn | 27 | ||||
-rw-r--r-- | old-docs/syntax.mdwn | 112 |
9 files changed, 587 insertions, 0 deletions
diff --git a/old-docs/compilation.mdwn b/old-docs/compilation.mdwn new file mode 100644 index 0000000..37876db --- /dev/null +++ b/old-docs/compilation.mdwn @@ -0,0 +1,190 @@ +Lace - The nitty-gritty of the compilation process +================================================== + +When you construct a Lace engine, you give it a compilation callback +set. That set is used to call you back when Lace encounters something it +needs help compiling. The structure of it is: + + { _lace = { + loader = function(compcontext, nametoload) ... end, + commands = { + ... = function(compcontext, words...) ... end, + }, + controltype = { + ... = function(compcontext, type, words...) ... end, + } + } } + +Anything outside of the `_lace` entry in the context is considered +fair game structure-wise and can be used by the functions called back +to acquire internal pointers etc. Note however that the compilation +context will not be passed around during execution so if you need to +remember some of it in order to function properly, then it's up to +you. Also note that anything not defined above in the `_lace` example +is considered "private" to Lace and should not be touched by non-Lace +code. + +In addition, Lace will maintain a `source` entry in the _lace table +with the lexed source which is being compiled and, if we're compiling +an included source, a parent entry with the compilation context of the +parent. The toplevel field is the compilation context of the top +level compilation. If parent is nil, then toplevel will equal +compcontext. Lace also maintains a `linenr` entry with the +currently-being-compiled line number, so that commands and control +types can use that in error reports if necessary. + +If `loader` is absent then the `include` statement refuses to include +anything mandatory. If it is present but returns nil when called then +the `include` statement fails any mandatory includes which do so. + +Otherwise the loader is expected to return the 'real' name of the +source and the content of it. This allows for symbolic lookups. + +If Lace encounters a command it does not recognise then it will call +`context._lace.commands[cmdname]` passing in the words representing the line +in question. It's up to that function to compile the line or else to +return an error. + +If Lace encounters a control type during a `define` command which it +does not understand, then it calls the `context._lace.controltype[name]` +function passing in all the remaining arguments. The control type +function is expected to return a compiled set for the define or else +an error. + +To start a Lace engine compiling a ruleset, simply do (pseudocode): + + rules, err = lace.compiler.compile(compcontext, sourcename[, sourcecontent]) + +If `sourcecontent` is not given, Lace will use the loader in the +`compcontext` to load the source. + +If `rules` is `nil`, then `err` is a Lua error. +If `rules` is `false`, then `err` is a formatted error from compilation +Otherwise, `rules` should be a table containing the ruleset. + +Internally, once compiled, Lace rulesets are a table of tables. Each +rule entry has a reference to its source and line number. It then has +a function pointer for executing this rule, and a set of arguments to +give the rule. Lace automatically passes the execution context as the +first argument to the rule. Sub-included rulesets are simply one of +the arguments to the function used to run the rule. + +Loader +====== + +When Lace wishes to load an entry, it calls the `loader` function. This +is to allow rulesets to be loadable from arbitrary locations such as +files on disk, HTTP URLs, random bits of memory or even out of version +control repositories directly. + +The `loader` function is given the compilation context and the name of the +source to load. Note that while it has the compilation context, the loader +function must be sensitive to the case of the initial load. Under that +circumstance, the source information in the compilation context will be +unavailable. The loader function is required to fit the following pseudocode +definition: + + realname, content = loader(compcontext, nametoload) + +If `realname` is not a string then content is expected to be an +"internal" error message (see below) which will be augmented with the +calling source position etc and rendered into an error to return to +the caller of `lace.compiler.compile()`. + +If `realname` is a string then it is taken to be the real name of the +loaded content (at worst you should return nametoload here) and +`content` is a string representing the contents of that file. + +Once it has been loaded by the `loader` function, Lace will compile that +sub-ruleset before continuing with the current ruleset. + +Commands +======== + +When Lace wishes to compile a command for which it has no internal +definition, it will call the command function provided in the +compilation context. If no such command function is found, it will +produce an error and stop the compilation. + +The command functions must fit the following pseudocode definition: + + cmdtab, msg = command_func(compcontext, words...) + +If `cmdtab` is not a table, msg should be an "internal" error message +(see below) which will be augmented with the calling source position +etc and rendered into an error to return to the caller of +`lace.compiler.compile()`. + +If `cmdtab` is a table, it is taken to be the compiled table +representing the command to run at ruleset execution time. It should +have the form: + + { fn = exec_function, args = {...} } + +Lace will automatically augment that with the source information which +led to the compiled rule for use later. + +The `exec_function` is expected to fit the following pseudocode +definition: + + result, msg = exec_function(exec_context, unpack(args)) + +See [[execution]] for notes on how these `exec_function` functions are meant +to behave. + +Control Types +============= + +When Lace is compiling a definition rule with a control type it has +not got internally, Lace will call the controltype function associated +with it (or report an error if no such control type is found). + +The control type functions must fit the following pseudocode +definition: + + ctrltab, msg = controltype_func(compcontext, type, words...) + +If `ctrltab` is not a table, msg should be an "internal" error message +(see below) which will be augmented with the calling source position +etc and rendered into an error to return to the caller of +`lace.compiler.compile()`. + +If `ctrltab` is a table, it is taken to be the compiled table +representing the control type to run at ruleset execution time. It +should have the form: + + { fn = ct_function, args = {...} } + +The `ct_function` is expected to fit the following pseudocode +definition: + + result, msg = ct_function(exec_context, unpack(args)) + +See [[execution]] for notes on how these `ct_function` functions are meant +to behave. + +Compiler internal errors +======================== + +Error messages during compilation are generated by calling: + + return lace.error.error("my message", { x, y }) + +Where the table is a list of numeric indices of the words which caused the +error. If words is empty (or nil) then the error is considered to be the +entire line. + +Lace will use this information to construct meaningful long error +messages which point at the words in question. Such as: + + Unknown command name: 'go_fish' + myruleset :: 6 + go_fish "I have no bananas" + ^^^^^^^ + +In the case of control type compilation, the words will automatically be offset +by the appropriate number to account for the define words. This means you +should always 1-index from your arguments where index 1 is the control type +word index. + +The same kind of situation occurs during [[execution]]. diff --git a/old-docs/developing.mdwn b/old-docs/developing.mdwn new file mode 100644 index 0000000..f82a593 --- /dev/null +++ b/old-docs/developing.mdwn @@ -0,0 +1,55 @@ +Lace - Helping with Development +=============================== + +The Lace codebase is divided up in to directories: + + lace/ + lib/ + ... The Lace libraries live here + test/ + ... All the tests and their data live here + example/ + ... The example and its data lives here + extras/ + ... The Lua coverage tool lives here + doc/ + ... All the documentation lives here + +The codebase has a top level `Makefile` which defaults to running the test +suite. The test suite requires Lua be present and as well as running all the +tests, also runs the Lua coverage tool to produce `luacov.report.out` which +details the coverage of the Lace codebase. + +It is a policy that releases must have 100% coverage from the test suite. +Ideally 100% coverage would be attained by the test cases for the given modules +but sometimes cross-module usage is required in order to best provide 100% +coverage. + +Any line not covered by the tests will be marked the `***0` in the +`luacov.report.out` file. + +If you make substantive non-backward-compatible changes to the API of Lace then +you should increment the ABI number in the main `lib/lace.lua` file. If you +make bug fixes or backward-compatible improvements then don't worry, the +version number in that file will be incremented during the release process. + +If you add more modules to Lace, you should note that you need to update: + +1. The `Makefile`'s `MODULES` variable +2. The `test/` directory will need a `test-lace.NEWMODULE.lua` file +3. You will need to alter `lib/lace.lua` to pull it in and update + the `test/test-lace.lua` test to include a check for the new module +4. You should ensure your new tests in 2 cover the module fully. +5. You should ensure that any changes are shown in the example if possible. +6. You should ensure any changes are reflected in the `docs/` files. + +You can check individual test suite coverage by running: + + make MODULES=lace.SOMEMODULE + +This will cause the test suite for the named modules *only* to be run, and for +the coverage data for that module to be generated. It is not policy that a +given module's individual tests MUST cover the module 100%, but if possible it +SHOULD. It is, as stated before, policy that the full test suite should cover +100% of the modules when run as a whole though. + diff --git a/old-docs/execution.mdwn b/old-docs/execution.mdwn new file mode 100644 index 0000000..50049a9 --- /dev/null +++ b/old-docs/execution.mdwn @@ -0,0 +1,94 @@ +Lace - Execution of rulesets +============================ + +Once compiled, a ruleset is essentially a sequence of functions to call on the +execution context. The simplest execution context is an empty table. If Lace +is going to store anything it will use a `_lace` prefix as with [[compilation]] +contexts. As with compilation, the caller is not permitted to put anything +inside `_lace` nor to rely on its layout. + +A few important functions make up the execution engine. The top level +function is simply: + + result, msg = lace.engine.run(ruleset, exec_context) + +This will run `ruleset` with the given execution context and return +a simple result. + +If `result` is `nil`, then `msg` is a long-form string error explaining +what went wrong. It represents a Lua error being caught and as such +you may not want to report it to your users. + +If `result` is `false`, then `msg` is a long-form string error +explaining that something returned an error during execution which it +would be reasonable to report to users under most circumstances. + +If `result` is `"allow"`, then `msg` is an optional string saying why +the ruleset resulted in an allow. Ditto for `"deny"`. Essentially any +string might be a reason. This is covered below in Commands. + +Commands +======== + +When a command is being run, it is called as: + + result, msg = command_fn(exec_context, unpack(args)) + +where `args` are the arguments returned during the compilation of the command. + +If the function throws an error, that will be caught and processed by +the execution engine. + +If `result` is falsehood (`nil`, `false`) then the command is considered to +have failed for some reason and `msg` contains an "internal" error +message to report to the user. This aborts the execution of the +ruleset. + +If `result` is `true`, then the command successfully ran, and execution +continues at the next rule. + +If `result` is a string, then the command returned a result. This +ceases execution of the ruleset and the result and message (which must +be a string explanation) are returned to the caller. Typically such +results would be "allow" or "deny" although there's nothing forcing +that to be the case. + +Control Types +============= + +When a control type function is being run, it is called as: + + result, msg = ct_fn(exec_context, unpack(args)) + +where `args` are the arguments returned when the definition was compiled. + +If the function throws an error, it will be caught and processed by +the execution engine. + +If `result` is `nil` then msg is an "internal" error, execution will be +stopped and the issue reported to the caller. + +If `result` is `false`, the control call failed and returned falsehood. +Anything else and the control call succeeded and returns truth. + +Control type functions are called at the point of test, not at the +point of definition. Control type results are *NOT* cached. It is up +to the called functions to perform any caching/memoising of results as +needed to ensure suitably performant behaviour. + +Helper functions +================ + +Since sometimes when writing command functions, you need to know if a given +define rule passes, Lace provides a function to do this. It is bound up in the +behaviour of Lace's internal `define` command and as such, you should treat it +as a black box. + + result, msg = lace.engine.test(exec_context, name) + +This, via the magic of the execution context calls through to the +appropriate control type functions, returning their results directly. + +This means that it can throw an error in the case of a Lua error, +otherwise it returns the two values as detailed above. + diff --git a/old-docs/index.mdwn b/old-docs/index.mdwn new file mode 100644 index 0000000..2b7f485 --- /dev/null +++ b/old-docs/index.mdwn @@ -0,0 +1,25 @@ +Lace - Lua Access Control Engine +================================ + +Lace is the core of an access control engine designed to be embedded into other +applications. It is also designed to be extended by the very applications it +is embedded into. + +As such, Lace provides only the core lexing, parsing, error handling and +related functionality of an access control engine, along with some initial +semantics to help the application developer along. + +All rules and mechanisms of deciding if access is to be permitted or not are up +to the application author to define. As such, while this documentation for +Lace will be useful for the application developer; it is recommended that the +applications do not refer their users to the Lace documentation except to +augment that provided in the application specific documentation. + +The Lace codebase provides an example of using the library which should be +referred to for getting started with Lace. However, there is also extensive +documentation on the [[syntax]] of Lace rulesets and also on the +[[compilation]] and [[execution]] phases of access control. + +If you wish to assist with Lace development, then see the [[developing]] +document for pointers around the codebase and the test suite. + diff --git a/old-docs/syntax-allow-deny.mdwn b/old-docs/syntax-allow-deny.mdwn new file mode 100644 index 0000000..c227abf --- /dev/null +++ b/old-docs/syntax-allow-deny.mdwn @@ -0,0 +1,20 @@ +Lace - Syntax of Allow and Deny statements +========================================== + +These access control statements start `allow` or `deny`, followed by a single +token reason to be returned to the caller (thus the reason should be quoted if +it contains spaces), followed by zero or more definition names, all of which +must pass for the statement to execute. + +For example: + + allow "Administrators can do anything" is-admin + deny "Plebs may do nothing" is-pleb + + allow '' + +As with definitions, includes, etc, if rule names are prefixed with an +exclamation point then their sense is inverted, e.g. + + deny "Only admins may alter hooks" altering-hooks !is-admin + diff --git a/old-docs/syntax-default.mdwn b/old-docs/syntax-default.mdwn new file mode 100644 index 0000000..2304ed5 --- /dev/null +++ b/old-docs/syntax-default.mdwn @@ -0,0 +1,33 @@ +Lace - The syntax of the default statement +========================================== + +The `default` statement is unusual in that it has no behaviour at runtime. At +compile time the `default` statement alters the behaviour of the compiler with +respect to what happens at the end of the ruleset parse. + +If, when Lace has finished parsing the ruleset, the last allow or deny was not +unconditional, then the compiler will, in the absence of a `default` statement, +inject a terminal `allow`/`deny` of the opposite sense of the last explicit +operation, unconditionally and with a reason of the empty string. + +If a `default` statement was encountered during processing then its chosen +behaviour will be used instead. + +The syntax of the `default` statement is: + + default 'allow' <reason>? + +or + + default 'deny' <reason>? + +If reasons are not provided, the string "Default behaviour" is +substituted. + +Once a single `default` statement has been encountered during +compilation; it is an error, and the compiler WILL cease, if it +encounters an additional `default` statement. + +Since it's common for rulesets to stem from a single core point, therefore it +is recommended that the application define a `default` policy at the start of +these core statements. diff --git a/old-docs/syntax-define.mdwn b/old-docs/syntax-define.mdwn new file mode 100644 index 0000000..579fe44 --- /dev/null +++ b/old-docs/syntax-define.mdwn @@ -0,0 +1,31 @@ +Lace - Syntax of definition statements +====================================== + +Definition statements start with one of `def` or `define`. They may +also start `acl` to look more squiddish. + +The rough syntax of a definition statement is: + + define <name> <controltype> <0-or-more-args> + +for example: + + define is-admin is-in-group administrators + +There are some loose constraints on the name and controltype +arguments. The name SHOULD NOT contain quote characters and MUST NOT +start with an exclamation point. The controltype simply SHOULD NOT +contain quotes. + +The control types are typically provided by the program which is using +Lace. However Lace does provide some simple control types which can +be useful and callers are welcome to add them to their engines. + +The two simple control types Lace provides are the `allof` and `anyof` +controls. They, as their arguments, take two or more rule names and match if +all or any (respectively) of their arguments resolve to true. As with other +parts of Lace, if the rule names are prefixed by exclamation points then their +result is inverted before being tested. + +These give you ways to produce common subexpressions of the 'AND' and +'OR' forms to be used in later rules. diff --git a/old-docs/syntax-include.mdwn b/old-docs/syntax-include.mdwn new file mode 100644 index 0000000..a95f489 --- /dev/null +++ b/old-docs/syntax-include.mdwn @@ -0,0 +1,27 @@ +Lace - Syntax of include statements +=================================== + +Include statements take a source token to include and an optional list +of definitions which must all be true before the include will take +place. + + include <sourcename> <0-or-more-definitions> + +If the include ends with a question mark (`include?`) then should the +sourcename not be available at the time, it will be silently ignored. + +Nominally an included ruleset is linearly inserted into the execution stream +there and then. In practice, while include statements result in compilation of +the rulesets at the same time, the contents of the ruleset will not be run +unless the supplied definitions all pass. This means that any rules (including +`define`s) in the included ruleset will not be executed unless the definitions +all pass. As such, use conditional includes carefully. + +If an "optional" include source isn't available, it is as though the +definitions did not pass. If a mandatory include source isn't +available then compilation of the ruleset will fail immediately. + +Circular includes are not acceptable. Not even if there's a set of conditions +which mean that it might never happen. Any attempt at circular includes will +result in a critical error at compile time because that is when all included +rulesets are loaded in their entirety. diff --git a/old-docs/syntax.mdwn b/old-docs/syntax.mdwn new file mode 100644 index 0000000..75a2b1e --- /dev/null +++ b/old-docs/syntax.mdwn @@ -0,0 +1,112 @@ +Lace - Syntax of rulesets +========================= + +Lace rule files are parsed line-by-line. There is no provision at +this time for rules to be split across multiple lines. If you require +such, please [submit a patch][developing]. + +Lace splits each line into a series of tokens. Tokens are always +strings and they can be optionally quoted to allow for spaces in them. +Certain tokens then have constraints placed on them so that further +parsing will be unambiguous. + +The lexing into tokens is done similarly to how shell does it. This +means that each whitespace-separated "word" is then subjected to +quoting rules. Lace does not have many special characters. The +backslash introduces escaped values in all cases and lace does not +differentiate between single and double quotes. Backslash escaped +characters sometimes have special meaning if inside quotes. For +example, the following strings lex in the following ways: + +1. Two tokens, one of each word. + + hello world + +2. The same as 1. + + hello world + +3. One token with both words separated by one space + + "hello world" + +4. Same as 3. + + 'hello world' + +5. Same as 3 and 4. + + hello\ world + +6. One token, consisting of the letters: `uptown` + + up\town + +6. One token, consisting of the letters: `up TAB own` + + "up\town" + +7. One token, a double-quote character + + \" + +8. The same as 7 + + '"' + +9. The same as 7 and 8. + + "\"" + + +As you can see, the lexing rules are not trivial, but should not come +as a surprise to anyone used to standard command-line lexing +techniques. + +Comments in Lace are prefixed by a hash '#', a double-slash '//' or a +double-dash '--'. The first word of the line must start with one of +these markers (but may contain other text also), for example: + + # This is a comment + // As is this + -- And this + + #But also this + //And this + --And also this + +Blank lines are permitted and ignored (except for counting), any +prefixed or postfixed whitespace is deleted before lexing begins. As +such: + + # This is a comment + # So is this + +This allows for sub-rulesets to be indented as the author pleases, +without altering the meaning of the rules. + +The first word of a rule defines its type. You can think of it as the +command being run. Lace, by default, provides a small number of rule +types: + +1. [Definitions](../syntax-define) + * This define access control stanzas. The definitions produced are + used in further rules to control access. Lace does not allow any + name to be reused. +2. [Includes](../syntax-include) + * Lace can include further rules at any point during a ruleset. If + the rules are to be optionally run then Lace cannot perform static + analysis of the definitions within the ruleset. Instead it will + rely on runtime catching of multiple-definitions etc. +3. [Access control statements](../syntax-allow-deny) + * These are the core functions of Lace. Namely the allow and deny + statements. These use control definitions from earlier in a ruleset + to determine whether to allow or deny access. The first allow + or deny statement which passes will stop execution of the ruleset. +4. [Default statement](../syntax-default) + * The 'default' statement can only be run once and provides Lace with + information on what to do in the case of no allow or deny rule passing. + +In those files, if you encounter the words WILL, MAY, SHOULD, MUST, or +their negatives, specifically in all-caps, then the meaning of the +words is taken in the spirit of the RFC usage of them. |