[ 
https://issues.apache.org/jira/browse/KNOX-2707?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Attila Magyar updated KNOX-2707:
--------------------------------
    Description: 
Adding a more flexible way to map principals to groups than the existing 
_group.principal.mapping_ in {_}CommonIdentityAssertionFilter{_}.

See the motivations behind this at 
[https://cwiki.apache.org/confluence/display/KNOX/KIP-16+-+Virtual+Groups+in+Apache+Knox]

Example:
{code:java}
<provider>
  <role>identity-assertion</role>
  <name>Default</name>
  <enabled>true</enabled>
  <param>
     <name>virtual.group.mapping.vgroup1</name>
     <value>(or (username 'tom') (member 'analyst'))</value>
  </param>
</provider>
{code}
General usage:
{code:java}
<name>virtual.group.mapping.VIRTUAL-GROUP-NAME</name>
<value>PREDICATE</value>
{code}
If the *PREDICATE* evaluates to true the user is added to 
{*}VIRTUAL-GROUP-NAME{*}.

There can be any number of virtual group mappings within the provider.
h2. Language Syntax

The predicate uses a parenthesized prefix notation language, similar to Lisp.
 * Everything in the language is either an atom or a list
 * A list is written with its elements separated by whitespace, and surrounded 
by parentheses, like (or true false false)
 * Lists can be nested to arbitrary level, like (or true (and false true))
 * An atom is either a boolean (true/false), a string, a number or a symbol 
(which denotes a functions name or a variable name).
 * Strings are single-quoted which makes easier to embed the language into XML 
or JSON.
 * There is a one to one mapping between the textual syntax and the parser 
generated AST. You can always infer the exact AST just by looking at the code 
(homoiconicity).

>From this code the parses generates the following AST:
h5. Textual code:
{code:java}
(or true (and false true))
{code}
h5. AST:
{code:java}
[or, true, [and, false, true]]
{code}
This has exactly the same structure, but everything is converted to internal 
Java representations. Lists are ArrayLists, booleans are java.lang.Booleans, 
etc.
h2. Evaluation rules
 * A literal atom evaluates to itself ('astring', 123).
 * If an atom is a symbol (like {_}or{_}, {_}username{_}, {_}true{_}, 
{_}false{_}) then the atom is looked up in a dictionary.
 * The head of a list is the name of the function we're about to call. The rest 
are the parameters. E.g.: (or true false)
 * Before calling the function (1st item of the list) we evaluate the rest of 
the list (recursively).

{code:java}
(=   0  (size groups))
 ^   ^         ^
 |   |         |
func param1  param2
{code}
 # Get the head of the list (=), see if it's an existing function
 # Evaluate param1, and param2, recusrivly
 # Call the function (=)

h2. Special forms

For some expressions the evaluation rule is slightly different. These are 
called special forms. These are the _or_ and the {_}and{_} operators. To 
support short-circuit evaluation, the parameters are not evaluated at the call 
site but by the definition itself.
{code:java}
(or   true  (and true false))
 ^     ^         ^
 |     |         |
func param1   param2
{code}
First we call the operator (or) then we let the operator decide which part to 
evaluate. In the above example the _or_ will stop evaluating the expression 
after it sees that the first argument is {_}true{_}.

These few evaluation rules (general + special forms) cover the semantics of the 
whole language.

h2. Supported functions
h3. Logical operators
h4. or

Evaluates true if one or more of its operands is true. Supports short-circuit 
evaluation and variable number of arguments.

Number of arguments: 1..N

{code:java}
(or bool1 bool2 ... boolN)
{code}
h5. Example
{code:java}
(or true false true)
{code}
h4. and

Evaluates true if all of its operands are true. Supports short-circuit 
evaluation and variable number of arguments.

Number of arguments: 1..N

{code:java}
(and bool1 bool2 ... boolN)
{code}
h5. Example
{code:java}
(and true false true)
{code}
h4. not

Negates the operand.

Number of arguments: 1

{code:java}
(not aBool)
{code}
h5. Example
{code:java}
(not true)
{code}
h3. Equality checks
h4. =

Evaluates true if the two operands are equal.

Number of arguments: 2

{code:java}
(= op1 op2)
{code}
h5. Example
{code:java}
(= 'apple' 'orange')
{code}
h4. !=

Evaluates true if the two operands are not equal.

Number of arguments: 2

{code:java}
(!= op1 op2)
{code}
h5. Example
{code:java}
(!= 'apple' 'orange')
{code}
h4. member

Evaluates true if the current user is a member of the given group

Number of arguments: 1

{code:java}
(member aString)
{code}
h5. Example
{code:java}
(member 'analyst')
{code}
h4. username

Evaluates true if the current user has the given username

Number of arguments: 1

{code:java}
(username aString)
{code}
h5. Example
{code:java}
(username 'admin')
{code}
This is a shorter version of (= username 'admin')
h4. size

Gets the size of a list

Number of arguments: 1

{code:java}
(size alist)
{code}
h5. Example
{code:java}
(size groups)
{code}
h4. empty

Evaluates to true if the given list is empty

Number of arguments: 1

{code:java}
(empty alist)
{code}
h5. Example
{code:java}
(empty groups)
{code}
h4. match

Evaluates true if the given string matches to the given regexp. Or any items of 
the given list matches the given regexp.

Number of arguments: 2

{code:java}
(match aString aRegExpString)
(match aList aRegExpString)
{code}
h5. Example
{code:java}
(match username 'tom|sam')
{code}
This function can also take a list as a first argument. In this case it will 
return true if the regexp matches to *any of the items* in the list.

{code:java}
(match groups 'analyst|scientist')
{code}
This returns true if the user is either in the 'analyst' group or in the 
'scientist' group.
h2. Constants

The following constants are populated automatically from the current security 
context.
h3. username

The username (principal) of the current user, derived from 
javax.security.auth.Subject.
h4. groups

The groups of the current user (LDAP or OS level), derived from 
{_}subject.getPrincipals(GroupPrincipal.class{_}.
h2. Examples
{code:java}
(or 
  (and
    (member 'admin')
    (member 'datalake'))
    (or
        (username 'lmccay')
        (username 'pzampino')))
{code}
1. Returns true if the user is either 'lmccay' or 'pzampino'
2. Returns true if the user is both in the 'admin' and the 'datalake' group.

A shorter version of this is
{code:java}
(or 
  (and (member 'admin') (member 'datalake'))
  (match username 'lmccay|pzampino'))
{code}
h2. Any group

If we want to put a user into a virtual group if they are a member of ANY 
groups, we can use either of the following predicates.
{code:java}
(!= (size groups) 0)
{code}
{code:java}
(not (empty groups))
{code}
{code:java}
(match groups '.*')
{code}

h2. Implementation notes

CommonIdentityAssertionFilter parses all the group mapping predicates at 
deployment time and caches the ASTs. In the doFilter we only evaluate the ASTs 
without parsing the text over and over.

  was:
Adding a more flexible way to map principals to groups than the existing 
_group.principal.mapping_ in {_}CommonIdentityAssertionFilter{_}.

See the motivations behind this at 
[https://cwiki.apache.org/confluence/display/KNOX/KIP-16+-+Virtual+Groups+in+Apache+Knox]

Example:
{code:java}
<provider>
  <role>identity-assertion</role>
  <name>Default</name>
  <enabled>true</enabled>
  <param>
     <name>virtual.group.mapping.vgroup1</name>
     <value>(or (username 'tom') (member 'analyst'))</value>
  </param>
</provider>
{code}
General usage:
{code:java}
<name>virtual.group.mapping.VIRTUAL-GROUP-NAME</name>
<value>PREDICATE</value>
{code}
If the *PREDICATE* evaluates to true the user is added to 
{*}VIRTUAL-GROUP-NAME{*}.

There can be any number of virtual group mappings within the provider.
h2. Language Syntax

The predicate uses a parenthesized prefix notation language, similar to Lisp.
 * Everything in the language is either an atom or a list
 * A list is written with its elements separated by whitespace, and surrounded 
by parentheses, like (or true false false)
 * Lists can be nested to arbitrary level, like (or true (and false true))
 * An atom is either a boolean (true/false), a string, a number or a symbol 
(which denotes a functions name or a variable name).
 * Strings are single-quoted which makes easier to embed the language into XML 
or JSON.
 * There is a one to one mapping between the textual syntax and the parser 
generated AST. You can always infer the exact AST just by looking at the code 
(homoiconicity).

>From this code the parses generates the following AST:
h5. Textual code:
{code:java}
(or true (and false true))
{code}
h5. AST:
{code:java}
[or, true, [and, false, true]]
{code}
This has exactly the same structure, but everything is converted to internal 
Java representations. Lists are ArrayLists, booleans are java.lang.Booleans, 
etc.
h2. Evaluation rules
 * A literal atom evaluates to itself ('astring', 123).
 * If an atom is a symbol (like {_}or{_}, {_}username{_}, {_}true{_}, 
{_}false{_}) then the atom is looked up in a dictionary.
 * The head of a list is the name of the function we're about to call. The rest 
are the parameters. E.g.: (or true false)
 * Before calling the function (1st item of the list) we evaluate the rest of 
the list (recursively).

{code:java}
(=   0  (size groups))
 ^   ^         ^
 |   |         |
func param1  param2
{code}
 # Get the head of the list (=), see if it's an existing function
 # Evaluate param1, and param2, recusrivly
 # Call the function (=)

h2. Special forms

For some expressions the evaluation rule is slightly different. These are 
called special forms. These are the _or_ and the {_}and{_} operators. To 
support short-circuit evaluation, the parameters are not evaluated at the call 
site but by the definition itself.
{code:java}
(or   true  (and true false))
 ^     ^         ^
 |     |         |
func param1   param2
{code}
First we call the operator (or) then we let the operator decide which part to 
evaluate. In the above example the _or_ will stop evaluating the expression 
after it sees that the first argument is {_}true{_}.

These few evaluation rules (general + special forms) cover the semantics of the 
whole language.

h2. Supported functions
h3. Logical operators
h4. or

Evaluates true if one or more of its operands is true. Supports short-circuit 
evaluation and variable number of arguments.

Number of arguments: 1..N

{code:java}
(or bool1 bool2 ... boolN)
{code}
h5. Example
{code:java}
(or true false true)
{code}
h4. and

Evaluates true if all of its operands are true. Supports short-circuit 
evaluation and variable number of arguments.

Number of arguments: 1..N

{code:java}
(and bool1 bool2 ... boolN)
{code}
h5. Example
{code:java}
(and true false true)
{code}
h4. not

Negates the operand.

Number of arguments: 1

{code:java}
(not aBool)
{code}
h5. Example
{code:java}
(not true)
{code}
h3. Equality checks
h4. =

Evaluates true if the two operands are equal.

Number of arguments: 2

{code:java}
(= op1 op2)
{code}
h5. Example
{code:java}
(= 'apple' 'orange')
{code}
h4. !=

Evaluates true if the two operands are not equal.

Number of arguments: 2

{code:java}
(!= op1 op2)
{code}
h5. Example
{code:java}
(!= 'apple' 'orange')
{code}
h4. member

Evaluates true if the current user is a member of the given group

Number of arguments: 1

{code:java}
(member aString)
{code}
h5. Example
{code:java}
(member 'analyst')
{code}
h4. username

Evaluates true if the current user has the given username

Number of arguments: 1

{code:java}
(username aString)
{code}
h5. Example
{code:java}
(username 'admin')
{code}
This is a shorter version of (= username 'admin')
h4. size

Gets the size of a list

Number of arguments: 1

{code:java}
(size alist)
{code}
h5. Example
{code:java}
(size groups)
{code}
h4. empty

Evaluates to true if the given list is empty

Number of arguments: 1

{code:java}
(empty alist)
{code}
h5. Example
{code:java}
(empty groups)
{code}
h4. match

Evaluates true if the given string matches to the given regexp. Or any items of 
the given list matches the given regexp.

Number of arguments: 2

{code:java}
(match aString aRegExpString)
(match aList aRegExpString)
{code}
h5. Example
{code:java}
(match username 'tom|sam')
{code}
This function can also take a list as a first argument. In this case it will 
return true if the regexp matches to *any of the items* in the list.

{code:java}
(match groups 'analyst|scientist')
{code}
This returns true if the user is either in the 'analyst' group or in the 
'scientist' group.
h2. Constants

The following constants are populated automatically from the current security 
context.
h3. username

The username (principal) of the current user, derived from 
javax.security.auth.Subject.
h4. groups

The groups of the current user (LDAP or OS level), derived from 
{_}subject.getPrincipals(GroupPrincipal.class{_}.
h2. Examples
{code:java}
(or 
  (and
    (member 'admin')
    (member 'datalake'))
    (or
        (username 'lmccay')
        (username 'pzampino')))
{code}
1. Returns true if the user is either 'lmccay' or 'pzampino'
2. Returns true if the user is both in the 'admin' and the 'datalake' group.

A shorter version of this is
{code:java}
(or 
  (and (member 'admin') (member 'datalake'))
  (match username 'lmccay|pzampino'))
{code}
h2. Any group

If we want to put a user into a virtual group if they are a member of ANY 
groups, we can use either of the following predicates.
{code:java}
(!= (size groups) 0)
{code}
{code:java}
(not (empty groups))
{code}
{code:java}
(match groups '.*')
{code}


> Virtual Group Mapping Provider
> ------------------------------
>
>                 Key: KNOX-2707
>                 URL: https://issues.apache.org/jira/browse/KNOX-2707
>             Project: Apache Knox
>          Issue Type: New Feature
>            Reporter: Attila Magyar
>            Assignee: Attila Magyar
>            Priority: Major
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> Adding a more flexible way to map principals to groups than the existing 
> _group.principal.mapping_ in {_}CommonIdentityAssertionFilter{_}.
> See the motivations behind this at 
> [https://cwiki.apache.org/confluence/display/KNOX/KIP-16+-+Virtual+Groups+in+Apache+Knox]
> Example:
> {code:java}
> <provider>
>   <role>identity-assertion</role>
>   <name>Default</name>
>   <enabled>true</enabled>
>   <param>
>      <name>virtual.group.mapping.vgroup1</name>
>      <value>(or (username 'tom') (member 'analyst'))</value>
>   </param>
> </provider>
> {code}
> General usage:
> {code:java}
> <name>virtual.group.mapping.VIRTUAL-GROUP-NAME</name>
> <value>PREDICATE</value>
> {code}
> If the *PREDICATE* evaluates to true the user is added to 
> {*}VIRTUAL-GROUP-NAME{*}.
> There can be any number of virtual group mappings within the provider.
> h2. Language Syntax
> The predicate uses a parenthesized prefix notation language, similar to Lisp.
>  * Everything in the language is either an atom or a list
>  * A list is written with its elements separated by whitespace, and 
> surrounded by parentheses, like (or true false false)
>  * Lists can be nested to arbitrary level, like (or true (and false true))
>  * An atom is either a boolean (true/false), a string, a number or a symbol 
> (which denotes a functions name or a variable name).
>  * Strings are single-quoted which makes easier to embed the language into 
> XML or JSON.
>  * There is a one to one mapping between the textual syntax and the parser 
> generated AST. You can always infer the exact AST just by looking at the code 
> (homoiconicity).
> From this code the parses generates the following AST:
> h5. Textual code:
> {code:java}
> (or true (and false true))
> {code}
> h5. AST:
> {code:java}
> [or, true, [and, false, true]]
> {code}
> This has exactly the same structure, but everything is converted to internal 
> Java representations. Lists are ArrayLists, booleans are java.lang.Booleans, 
> etc.
> h2. Evaluation rules
>  * A literal atom evaluates to itself ('astring', 123).
>  * If an atom is a symbol (like {_}or{_}, {_}username{_}, {_}true{_}, 
> {_}false{_}) then the atom is looked up in a dictionary.
>  * The head of a list is the name of the function we're about to call. The 
> rest are the parameters. E.g.: (or true false)
>  * Before calling the function (1st item of the list) we evaluate the rest of 
> the list (recursively).
> {code:java}
> (=   0  (size groups))
>  ^   ^         ^
>  |   |         |
> func param1  param2
> {code}
>  # Get the head of the list (=), see if it's an existing function
>  # Evaluate param1, and param2, recusrivly
>  # Call the function (=)
> h2. Special forms
> For some expressions the evaluation rule is slightly different. These are 
> called special forms. These are the _or_ and the {_}and{_} operators. To 
> support short-circuit evaluation, the parameters are not evaluated at the 
> call site but by the definition itself.
> {code:java}
> (or   true  (and true false))
>  ^     ^         ^
>  |     |         |
> func param1   param2
> {code}
> First we call the operator (or) then we let the operator decide which part to 
> evaluate. In the above example the _or_ will stop evaluating the expression 
> after it sees that the first argument is {_}true{_}.
> These few evaluation rules (general + special forms) cover the semantics of 
> the whole language.
> h2. Supported functions
> h3. Logical operators
> h4. or
> Evaluates true if one or more of its operands is true. Supports short-circuit 
> evaluation and variable number of arguments.
> Number of arguments: 1..N
> {code:java}
> (or bool1 bool2 ... boolN)
> {code}
> h5. Example
> {code:java}
> (or true false true)
> {code}
> h4. and
> Evaluates true if all of its operands are true. Supports short-circuit 
> evaluation and variable number of arguments.
> Number of arguments: 1..N
> {code:java}
> (and bool1 bool2 ... boolN)
> {code}
> h5. Example
> {code:java}
> (and true false true)
> {code}
> h4. not
> Negates the operand.
> Number of arguments: 1
> {code:java}
> (not aBool)
> {code}
> h5. Example
> {code:java}
> (not true)
> {code}
> h3. Equality checks
> h4. =
> Evaluates true if the two operands are equal.
> Number of arguments: 2
> {code:java}
> (= op1 op2)
> {code}
> h5. Example
> {code:java}
> (= 'apple' 'orange')
> {code}
> h4. !=
> Evaluates true if the two operands are not equal.
> Number of arguments: 2
> {code:java}
> (!= op1 op2)
> {code}
> h5. Example
> {code:java}
> (!= 'apple' 'orange')
> {code}
> h4. member
> Evaluates true if the current user is a member of the given group
> Number of arguments: 1
> {code:java}
> (member aString)
> {code}
> h5. Example
> {code:java}
> (member 'analyst')
> {code}
> h4. username
> Evaluates true if the current user has the given username
> Number of arguments: 1
> {code:java}
> (username aString)
> {code}
> h5. Example
> {code:java}
> (username 'admin')
> {code}
> This is a shorter version of (= username 'admin')
> h4. size
> Gets the size of a list
> Number of arguments: 1
> {code:java}
> (size alist)
> {code}
> h5. Example
> {code:java}
> (size groups)
> {code}
> h4. empty
> Evaluates to true if the given list is empty
> Number of arguments: 1
> {code:java}
> (empty alist)
> {code}
> h5. Example
> {code:java}
> (empty groups)
> {code}
> h4. match
> Evaluates true if the given string matches to the given regexp. Or any items 
> of the given list matches the given regexp.
> Number of arguments: 2
> {code:java}
> (match aString aRegExpString)
> (match aList aRegExpString)
> {code}
> h5. Example
> {code:java}
> (match username 'tom|sam')
> {code}
> This function can also take a list as a first argument. In this case it will 
> return true if the regexp matches to *any of the items* in the list.
> {code:java}
> (match groups 'analyst|scientist')
> {code}
> This returns true if the user is either in the 'analyst' group or in the 
> 'scientist' group.
> h2. Constants
> The following constants are populated automatically from the current security 
> context.
> h3. username
> The username (principal) of the current user, derived from 
> javax.security.auth.Subject.
> h4. groups
> The groups of the current user (LDAP or OS level), derived from 
> {_}subject.getPrincipals(GroupPrincipal.class{_}.
> h2. Examples
> {code:java}
> (or 
>   (and
>     (member 'admin')
>     (member 'datalake'))
>     (or
>         (username 'lmccay')
>         (username 'pzampino')))
> {code}
> 1. Returns true if the user is either 'lmccay' or 'pzampino'
> 2. Returns true if the user is both in the 'admin' and the 'datalake' group.
> A shorter version of this is
> {code:java}
> (or 
>   (and (member 'admin') (member 'datalake'))
>   (match username 'lmccay|pzampino'))
> {code}
> h2. Any group
> If we want to put a user into a virtual group if they are a member of ANY 
> groups, we can use either of the following predicates.
> {code:java}
> (!= (size groups) 0)
> {code}
> {code:java}
> (not (empty groups))
> {code}
> {code:java}
> (match groups '.*')
> {code}
> h2. Implementation notes
> CommonIdentityAssertionFilter parses all the group mapping predicates at 
> deployment time and caches the ASTs. In the doFilter we only evaluate the 
> ASTs without parsing the text over and over.



--
This message was sent by Atlassian Jira
(v8.20.1#820001)

Reply via email to