kre / Kinetic-Rules-Engine

Source code for KRE
GNU General Public License v2.0
54 stars 8 forks source link

Limit event domains and types on a channel by policy. #19

Open windley opened 11 years ago

windley commented 11 years ago

As Drummond went thru his example of a link contract as an event channel policy expression, it because more clear than ever that for this to work, we need a policy expression language that compiles to XDI. Programmers aren't going to write the XDI directly. I don't know how they could. More and more I think of XDI as the assembly language of data.

Here is Drummond's example from https://wiki.oasis-open.org/xdi/XdiPolicyExpression/Discussion

...$do/$do$send/=!1111$(+channel)$(!23)$(+event)
...$do$if$($and)$(!1)/$do$send/(=!1111$(+channel)$(!23)$(+event)($1)$!(+domain)/!/(data:,cloudos))
...$do$if$($and)$(!2)/$do$send/(=!1111$(+channel)$(!23)$(+event)($1)$!(+type)/!/(data:,notification))
...$do$if$($and)$(!2)/$do$send/(=!1111$(+channel)$(!23)$(+event)($1)$!(+type)/!/(data:,subscription))
...$do$if$($and)$(!2)/$do$send/(=!1111$(+channel)$(!23)$(+event)($1)$!(+type)/!/(data:,deletion))

This says, to the extent I understand it:

User =!1111 can send events on channel !23
Channel !23 allows cloudos:{notification|subscription|deletion} events

We'd probably more likely need the following kinds of policy:

Cloud =!1111 is a +friend
Channel !23 belongs to user =!1111
Any channel that belong to a +friend allows cloudos:{notification|subscription|deletion} events

Or, more likely,

Cloud =!1111 owns channel !23
Channel !23 has relationship type +friend
Any channel with relationship type +friend allows cloudos:{notification|subscription|deletion} events

Suppose this is our policy language (and it could be; I don't see any reason we could write a parser for statements like the three proceeding). We would want to compile it to the equivalent XDI statements for storage in the XDI graph of the cloud.

We would want a system that allows us to query the XDI graph and produce a data structure like this for use in filtering events on channels:

{...
 "!23" : {"cloudos" : {"notification" : true,
                       "subscription" : true,
                       "deletion" : true
                      }
          ...
         }
...
}

This would be cached and only flushed if the XDI server raised a system event saying that the policy graph had changed and allow the rules engine to make quick determinations whether or not the event was allowed on a specific channel.

This all seems relatively straightforward. We could have (maybe) an XDI pretty printer that turns the XDI statements that these policy statements compile to back into the policy statements. That means that the XDI graph for the cloud is the repository for the policy (if we can't get it back out in a human readable form, you would have to have another repository for the human readable statements and keep them in sync and that would be ugly).

The biggest question in my mind is where does this policy come from in the first place? Who writes it? Using what tool? The first two statements are created when the channel is created, so that's OK:

Cloud =!1111 owns channel !23
Channel !23 has relationship type +friend

The last one is likely a global policy that the user might override or customize, but I don't know how that would work.

Any channel with relationship type +friend allows cloudos:{notification|subscription|deletion} events

So, there are my thoughts for today.

peacekeeper commented 11 years ago

I added a few examples relevant to this to the XDI Local Messenger tool:

Giving $get access to all my friends: http://xdi2.projectdanube.org/XDILocalMessenger?category=2&sample=3

Allowing only certain events: http://xdi2.projectdanube.org/XDILocalMessenger?category=2&sample=4

animeshc commented 11 years ago

I’ve come up with a small EBNF grammar below for the language. I have not included any condition expression on event domains, types or attributes because I thought those are handled quite effectively by eventexes in the rule today. My understanding is that, when an event hits a personal cloud, unless there is a rule which is supposed to handle that event, it will be ignored. There may be very good reasons to include those filters in this language too. So, I can put them in if required. So, I have only included filter expressions which are not easily handled by an eventex.

policy : cloud CLOUD_IDENTIFER (allows|blocks) events [on channel CHANNEL_IDENTIFIER] [if ‘(‘ condition ‘)’] ‘;’ | cloud CLOUD_IDENTIFER owns channel CHANNEL_IDENTIFIER ‘;’ | policy ; condition : NOT ‘(‘ condition ‘)’ | ‘(‘ condition ‘)’ AND ‘(‘ condition ‘)’ | ‘(‘ condition ‘)’ OR ‘(‘ condition ‘)’ | from cloud (INUMBER|INAME) equals CLOUD_IDENTIFER | from cloud (INUMBER|INAME) in ‘{‘ CLOUD_IDENTIFER_LIST ‘}’ | channel relationship equals CHANNEL_RELATIONSHIP_IDENTIFIER ;

CHANNEL_RELATIONSHIPIDENTIFIER : ([A-Za-z-])+ ; CLOUD_IDENTIFIER : iname | inumber ; CLOUD_IDENTIFER_LIST : (CLOUD_IDENTIFIER)+ ; CHANNEL_IDENTIFER : ([A-Za-z0-9])+ ;

windley commented 11 years ago

comments below:

policy : cloud CLOUD_IDENTIFER (allows|blocks) events 
           [on channel CHANNEL_IDENTIFIER]  [if  ‘(‘ condition ‘)’]    ‘;’  |
         cloud CLOUD_IDENTIFER owns channel  CHANNEL_IDENTIFIER   ‘;’ |
         policy
;

I think this could be rewritten to put the separator in the recursion and be clearer:

policy : cloud CLOUD_IDENTIFER (allows|blocks) events 
           [on channel CHANNEL_IDENTIFIER]  [if  ‘(‘ condition ‘)’]    |
         cloud CLOUD_IDENTIFER owns channel  CHANNEL_IDENTIFIER        |
         policy [';' policy]*
;

We need to add a way to specify primitive events in the policy language because one of the major uses is to filter specific events on a per channel basis, not merely events in general. Really a simple syntax statement that allows for a domain and type separated by a ':' would be sufficient.

Any channel allows cloudos:{notification|subscription|deletion} events if channel relationship equals +friend We need to allow wild cards and choice in event specifications.

condition : NOT ‘(‘ condition ‘)’ |
  ‘(‘ condition ‘)’ AND ‘(‘ condition ‘)’ |
‘(‘ condition ‘)’ OR ‘(‘ condition ‘)’  |
from cloud  (INUMBER|INAME) equals CLOUD_IDENTIFER |
from cloud  (INUMBER|INAME) in  ‘{‘  CLOUD_IDENTIFER_LIST    ‘}’ |
channel relationship equals CHANNEL_RELATIONSHIP_IDENTIFIER ;

I think this should be rewritten to not require so much explicit parenthesezation. For example, I want to write

channel relationship equals foo and cloud inumber equals !4444

rather than

(channel relationship equals 'foo') and (cloud inumber equals !4444)

Is 'equals' the right relation or should we use 'is'? Not sure.

Also, we might not want to specify that it's a iname or inumber, we may want to be more general in some cases:

cloud identifier equals !4444

and then take either an iname or inumber.

CHANNEL_RELATIONSHIP_IDENTIFIER : ([A-Za-z_-])+ ;

This looks fine, but I'm wondering whether we ought to allow namespacing (two level hierarchy of relationships). Namespaces cover a multitude of sins. So, something like

CHANNEL_RELATIONSHIP_IDENTIFIER : ([A-Za-z_-])+':'([A-Za-z_-])+ ;

It may be helpful to specify these in the BNF separately. Should these be XDI compatible as well so we can reference a XDI specified relationship? +friend, for example? Should relationships always be in the dictionary?

We also need to allow for specifying choice here. {+friend|+puppy}

CLOUD_IDENTIFIER : iname | inumber ;

I presume you're referring to standard iname and inumber syntax here.

CLOUD_IDENTIFER_LIST :  (CLOUD_IDENTIFIER)+ ;

I think the delimiters ought to be here rather than in the condition statement and we ought to use [] rather than {} so that list syntax is compatible with JSON, etc.

CHANNEL_IDENTIFER :  ([A-Za-z0-9])+ ;

Add !, -, and _. We want these to be inumber compatible.

animeshc commented 11 years ago

policy : cloud identifier CLOUD_IDENTIFER (allows|blocks) EVENT_FILTER events on channel CHANNEL_IDENTIFIER [if ‘(‘ condition ‘)’] | cloud identifier CLOUD_IDENTIFER owns channel CHANNEL_IDENTIFIER | policy [';' policy]* ;

condition : not condition_seg | condition_seg and condition_seg | condition_seg or condition_seg ;

condition_seg : cloud identifier is CLOUD_IDENTIFER | cloud identifier is in CLOUD_IDENTIFER_LIST | channel relationship is CHANNEL_RELATIONSHIP_IDENTIFIER | channel relationship is in CHANNEL_RELATIONSHIP_IDENTIFIER_LIST;

CHANNEL_RELATIONSHIP_IDENTIFIER : ([A-Za-z+])+;

CHANNEL_RELATIONSHIP_IDENTIFIER_LIST : '[' CHANNEL_RELATIONSHIP_IDENTIFIER [ ',' CHANNEL_RELATIONSHIP_IDENTIFIER ]* ']' ; CLOUD_IDENTIFIER : '*' | INAME| INUMBER ;

CLOUD_IDENTIFER_LIST : '[' CLOUD_IDENTIFIER [',' CLOUD_IDENTIFIER]* ']' ; CHANNELIDENTIFER : '*' | ([A-Za-z0-9-!])+ ;

EVENT_FILTER : '*' | EVENT_DOMAIN ':' '[' EVENT_TYPE_LIST ']' ;

EVENT_TYPE_LIST : EVENT_TYPE ['|' EVENT_TYPE]* ; EVENTDOMAIN : ([A-Za-z+])+ ; EVENTTYPE : ([A-Za-z+])+ ; INAME : [=@]INAME_SEG INAME_SEG : ([A-Za-z+0-9])+ ['' INAMESEG ] INUMBER : '=!' INUMBER_SEG INUMBER_SEG : ([A-Za-z+_0-9])+ ['!' INUMBER_SEG ]*

animeshc commented 11 years ago

reg. hierarchy of channel relationship, in XDI a hierarchy can be specified as "+neustar+colleague" or "+friend+cousin". So, "CHANNEL_RELATIONSHIP_IDENTIFIER : ([A-Za-z+])+" should be able to handle that.

animeshc commented 11 years ago

reg. INAME and INUMBER syntax, these will be treated as strings for comparison. So, as long as valid inames and inumbers are not rejected by the syntax, then for the purposes of this language it should be ok.

windley commented 11 years ago

This looks good. Let's get it in ANTLR and see how it parses! :)

animeshc commented 11 years ago

grammar PersonalChannelPolicy; tokens { cloud = 'cloud'; identifier = 'identifier'; allows = 'allows'; blocks = 'blocks'; events = 'events'; on = 'on'; channel = 'channel'; owns = 'owns'; not = 'not'; and = 'and'; or = 'or'; relationship = 'relationship'; is = 'is'; in = 'in'; NEWLINE = '\n';

}

policy : policy_stmt [NEWLINE policy_stmt]* ;

policy_stmt : cloud identifier CLOUD_IDENTIFIER (allows|blocks) EVENT_FILTER events on channel CHANNEL_IDENTIFER [if ‘(‘ condition ‘)’] | cloud identifier CLOUD_IDENTIFIER owns channel CHANNEL_IDENTIFER ';' ; condition : not condition_seg | condition_seg and condition_seg | condition_seg or condition_seg ;

condition_seg : cloud identifier is CLOUD_IDENTIFIER | cloud identifier is in CLOUD_IDENTIFER_LIST | channel relationship is CHANNEL_RELATIONSHIP_IDENTIFIER | channel relationship is in CHANNEL_RELATIONSHIP_IDENTIFIER_LIST;

UPPERCASE_LETTERS : 'A'..'Z'; LOWERCASE_LETTERS : 'a'..'z'; DIGIT :
'0'..'9'; PLUS : '+';

BANG : '!'; EQUAL : '='; AT : '@'; UNDERSCORE : '_'; HYPHEN : '-';

CHANNEL_RELATIONSHIP_IDENTIFIER : ([UPPERCASE_LETTERS LOWERCASE_LETTERS PLUS])+;

CHANNEL_RELATIONSHIP_IDENTIFIER_LIST : '[' CHANNEL_RELATIONSHIP_IDENTIFIER [ ',' CHANNEL_RELATIONSHIP_IDENTIFIER ]* ']' ; CLOUD_IDENTIFIER : '*' | INAME| INUMBER ;

CLOUD_IDENTIFER_LIST : '[' CLOUD_IDENTIFIER [',' CLOUD_IDENTIFIER]* ']' ; CHANNEL_IDENTIFER : '*' | ([UPPERCASE_LETTERS LOWERCASE_LETTERS HYPHEN UNDERSCORE BANG])+ ;

EVENT_FILTER : '*' | EVENT_DOMAIN ':' '[' EVENT_TYPE_LIST ']' ;

EVENT_TYPE_LIST : EVENT_TYPE ['|' EVENT_TYPE]* ; EVENT_DOMAIN : ([UPPERCASE_LETTERS LOWERCASE_LETTERS PLUS UNDERSCORE])+ ; EVENT_TYPE : ([UPPERCASE_LETTERS LOWERCASE_LETTERS PLUS UNDERSCORE])+ ; INAME : [EQUAL AT]INAME_SEG ; INAME_SEG : ([UPPERCASE_LETTERS LOWERCASELETTERS PLUS UNDERSCORE DIGIT])+ ['' INAMESEG ]; INUMBER : '=!' INUMBER_SEG ; INUMBER_SEG : ([UPPERCASE_LETTERS LOWERCASE_LETTERS DIGIT])+ ['!' INUMBER_SEG ]*;

animeshc commented 11 years ago

ANTLR grammar that compiles - grammar PersonalChannelPolicy;

/*------------------------------------------------------------------

policy : policy_expr (NEWLINE policy_expr)* EOF;

policy_expr : policy_stmt ';' ; policy_stmt : CLOUD IDENTIFIER cloud_id (ALLOWS|BLOCKS) event_filter EVENTS ON CHANNEL channel_id IF condition | CHANNEL channel_id BELONGS_TO CLOUD IDENTIFIER cloud_id ;

condition : FROM CLOUD IDENTIFIER IS cloud_id | FROM CLOUD IDENTIFIER IS NOT cloud_id FROM CLOUD IDENTIFIER IS IN cloud_id_list | FROM CLOUD IDENTIFIER IS NOT IN cloud_id_list | CHANNEL RELATIONSHIP IS channel_relationship_id | CHANNEL RELATIONSHIP IS NOT channel_relationship_id | CHANNEL RELATIONSHIP IS IN channel_relationship_id_list | CHANNEL RELATIONSHIP IS NOT IN channel_relationship_id_list;

cloud_id : ALL | iname | inumber ; channel_relationship_id : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS)+;

channel_relationship_id_list : '[' channel_relationship_id ( ',' channel_relationship_id )* ']' ;

cloud_id_list : '[' cloud_id (',' (iname|inumber))* ']' ;

channel_id : ALL | (UPPERCASE_LETTERS|LOWERCASE_LETTERS|HYPHEN|UNDERSCORE|BANG)+;

event_type_list : '[' event_type ('|' event_type)* ']'; event_domain : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE)+ ; event_type : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE)+ ; event_filter : ALL | event_expr; event_expr : event_domain ':' event_type_list; iname : (EQUAL|AT) inameseg ; inameseg : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE|DIGIT|SEPARATOR_I)+ ; inumber : '=!' inumberseg ; inumberseg : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|DIGIT|BANG)+;

/*------------------------------------------------------------------

UPPERCASE_LETTERS : 'A'..'Z'; LOWERCASE_LETTERS : 'a'..'z'; DIGIT :
'0'..'9'; PLUS : '+';

BANG : '!'; EQUAL : '='; AT : '@'; UNDERSCORE : '_'; HYPHEN : '-';

CLOUD : 'cloud'; IDENTIFIER : 'identifier'; ALLOWS : 'allows'; BLOCKS : 'blocks'; EVENTS : 'events'; ON : 'on'; CHANNEL : 'channel'; OWNS : 'owns'; NOT : 'not'; AND : 'and'; OR : 'or'; RELATIONSHIP : 'relationship'; IS : 'is'; IN : 'in'; NEWLINE : '\n'; IF : 'if'; FROM : 'from'; BELONGS_TO : 'belongs to'; ALL : 'all'; SEPARATOR_I : '*';

windley commented 11 years ago

Very good.

Ultimately, I think this policy language needs to be part of KRL so a KRL programmer can express policy changes based on apps getting installed, deleted, accounts being linked, subscriptions being set up, channels being created, etc.

So, what I'd love to see next if you're game is a perl-based parser module that uses ANTLR (modeled after lib/per/Kynetx/Parser.pm) and a stand-alone program (like lib/perl/bin/krl-parser) that can parse policy statements and validate them.

--phil--

Phil Windley www.windley.com @windley

My Book on Event-Based Programming: The Live Web

On Jan 11, 2013, at 9:54 AM, animeshc notifications@github.com wrote:

ANTLR grammar that compiles - grammar PersonalChannelPolicy;

/*------------------------------------------------------------------

PARSER RULES ------------------------------------------------------------------/ policy : policy_expr (NEWLINE policy_expr)* EOF;

policy_expr : policy_stmt ';' ; policy_stmt : CLOUD IDENTIFIER cloud_id (ALLOWS|BLOCKS) event_filter EVENTS ON CHANNEL channel_id IF condition | CHANNEL channel_id BELONGS_TO CLOUD IDENTIFIER cloud_id ;

condition : FROM CLOUD IDENTIFIER IS cloud_id | FROM CLOUD IDENTIFIER IS NOT cloud_id FROM CLOUD IDENTIFIER IS IN cloud_id_list | FROM CLOUD IDENTIFIER IS NOT IN cloud_id_list | CHANNEL RELATIONSHIP IS channel_relationship_id | CHANNEL RELATIONSHIP IS NOT channel_relationship_id | CHANNEL RELATIONSHIP IS IN channel_relationship_id_list | CHANNEL RELATIONSHIP IS NOT IN channel_relationship_id_list;

cloud_id : ALL | iname | inumber ; channel_relationship_id : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS)+;

channel_relationship_id_list : '[' channel_relationship_id ( ',' channel_relationship_id )* ']' ;

cloud_id_list : '[' cloud_id (',' (iname|inumber))* ']' ;

channel_id : ALL | (UPPERCASE_LETTERS|LOWERCASE_LETTERS|HYPHEN|UNDERSCORE|BANG)+;

event_type_list : '[' event_type ('|' event_type)* ']'; event_domain : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE)+ ; event_type : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE)+ ; event_filter : ALL | event_expr; event_expr : event_domain ':' event_type_list; iname : (EQUAL|AT) inameseg ; inameseg : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|PLUS|UNDERSCORE|DIGIT|SEPARATOR_I)+ ; inumber : '=!' inumberseg ; inumberseg : (UPPERCASE_LETTERS|LOWERCASE_LETTERS|DIGIT|BANG)+;

/*------------------------------------------------------------------

LEXER RULES ------------------------------------------------------------------/ UPPERCASE_LETTERS : 'A'..'Z'; LOWERCASE_LETTERS : 'a'..'z'; DIGIT :

'0'..'9'; PLUS : '+';

BANG : '!'; EQUAL : '='; AT : '@'; UNDERSCORE : '_'; HYPHEN : '-';

CLOUD : 'cloud'; IDENTIFIER : 'identifier'; ALLOWS : 'allows'; BLOCKS : 'blocks'; EVENTS : 'events'; ON : 'on'; CHANNEL : 'channel'; OWNS : 'owns'; NOT : 'not'; AND : 'and'; OR : 'or'; RELATIONSHIP : 'relationship'; IS : 'is'; IN : 'in'; NEWLINE : '\n'; IF : 'if'; FROM : 'from'; BELONGS_TO : 'belongs to'; ALL : 'all'; SEPARATOR_I : '*';

— Reply to this email directly or view it on GitHub.

animeshc commented 11 years ago

Ok. Will start on it. BTW, the de-compilation back from XDI to this language is going to be a lot trickier than going from this one to XDI , because XDI grammar is way more complicated than this one. So, Markus had a thought - how about we store policy defined in this language as is in the personal graph as a literal and fetch it when required. One has to keep the XDI definition and this literal value in-sync, that's the overhead. What do you think ?

windley commented 11 years ago

I think that's a reasonable compromise. Especially at this point.

--phil--

On Jan 11, 2013, at 1:49 PM, animeshc notifications@github.com wrote:

Ok. Will start on it. BTW, the de-compilation back from XDI to this language is going to be a lot trickier than going from this one to XDI , because XDI grammar is way more complicated than this one. So, Markus had a thought - how about we store policy defined in this language as is in the personal graph as a literal and fetch it when required. One has to keep the XDI definition and this literal value in-sync, that's the overhead. What do you think ?

— Reply to this email directly or view it on GitHub.

windley commented 11 years ago

For now, this work has moved to https://github.com/kynetx/PolicyLanguage. Once that's ready, we'll need to modify KRE to use it.