View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security.visibility;
19  
20  import java.nio.charset.StandardCharsets;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Stack;
24  
25  import org.apache.hadoop.hbase.classification.InterfaceAudience;
26  import org.apache.hadoop.hbase.security.visibility.expression.ExpressionNode;
27  import org.apache.hadoop.hbase.security.visibility.expression.LeafExpressionNode;
28  import org.apache.hadoop.hbase.security.visibility.expression.NonLeafExpressionNode;
29  import org.apache.hadoop.hbase.security.visibility.expression.Operator;
30  import org.apache.hadoop.hbase.util.Bytes;
31  
32  @InterfaceAudience.Private
33  public class ExpressionParser {
34  
35    private static final char CLOSE_PARAN = ')';
36    private static final char OPEN_PARAN = '(';
37    private static final char OR = '|';
38    private static final char AND = '&';
39    private static final char NOT = '!';
40    private static final char SPACE = ' ';
41    private static final char DOUBLE_QUOTES = '"';
42    public ExpressionNode parse(String expS) throws ParseException {
43      expS = expS.trim();
44      Stack<ExpressionNode> expStack = new Stack<ExpressionNode>();
45      int index = 0;
46      byte[] exp = Bytes.toBytes(expS);
47      int endPos = exp.length;
48      while (index < endPos) {
49        byte b = exp[index];
50        switch (b) {
51          case OPEN_PARAN:
52            processOpenParan(expStack, expS, index);
53            index = skipSpaces(exp, index);
54            break;
55          case CLOSE_PARAN:
56            processCloseParan(expStack, expS, index);
57            index = skipSpaces(exp, index);
58            break;
59          case AND:
60          case OR:
61            processANDorOROp(getOperator(b), expStack, expS, index);
62            index = skipSpaces(exp, index);
63            break;
64          case NOT:
65            processNOTOp(expStack, expS, index);
66            break;
67          case DOUBLE_QUOTES:
68            int labelOffset = ++index;
69            // We have to rewrite the expression within double quotes as incase of expressions 
70            // with escape characters we may have to avoid them as the original expression did
71            // not have them
72            List<Byte> list = new ArrayList<Byte>();
73            while (index < endPos && !endDoubleQuotesFound(exp[index])) {
74              if (exp[index] == '\\') {
75                index++;
76                if (exp[index] != '\\' && exp[index] != '"')
77                  throw new ParseException("invalid escaping with quotes " + expS + " at column : "
78                      + index);
79              }
80              list.add(exp[index]);
81              index++;
82            }
83            // The expression has come to the end. still no double quotes found 
84            if(index == endPos) {
85              throw new ParseException("No terminating quotes " + expS + " at column : " + index);
86            }
87            // This could be costly. but do we have any alternative?
88            // If we don't do this way then we may have to handle while checking the authorizations.
89            // Better to do it here.
90            byte[] array = com.google.common.primitives.Bytes.toArray(list);
91            String leafExp = Bytes.toString(array).trim();
92            if (leafExp.isEmpty()) {
93              throw new ParseException("Error parsing expression " + expS + " at column : " + index);
94            }
95            processLabelExpNode(new LeafExpressionNode(leafExp), expStack, expS, index);
96            index = skipSpaces(exp, index);
97            break;
98          default:
99            labelOffset = index;
100           do {
101             if (!VisibilityLabelsValidator.isValidAuthChar(exp[index])) {
102               throw new ParseException("Error parsing expression " 
103                  + expS + " at column : " + index);
104             }
105             index++;
106           } while (index < endPos && !isEndOfLabel(exp[index]));
107           leafExp = new String(exp, labelOffset, index - labelOffset,
108             StandardCharsets.UTF_8).trim();
109           if (leafExp.isEmpty()) {
110             throw new ParseException("Error parsing expression " + expS + " at column : " + index);
111           }
112           processLabelExpNode(new LeafExpressionNode(leafExp), expStack, expS, index);
113           // We already crossed the label node index. So need to reduce 1 here.
114           index--;
115           index = skipSpaces(exp, index);
116       }
117       index++;
118     }
119     if (expStack.size() != 1) {
120       throw new ParseException("Error parsing expression " + expS);
121     }
122     ExpressionNode top = expStack.pop();
123     if (top == LeafExpressionNode.OPEN_PARAN_NODE) {
124       throw new ParseException("Error parsing expression " + expS);
125     }
126     if (top instanceof NonLeafExpressionNode) {
127       NonLeafExpressionNode nlTop = (NonLeafExpressionNode) top;
128       if (nlTop.getOperator() == Operator.NOT) {
129         if (nlTop.getChildExps().size() != 1) {
130           throw new ParseException("Error parsing expression " + expS);
131         }
132       } else if (nlTop.getChildExps().size() != 2) {
133         throw new ParseException("Error parsing expression " + expS);
134       }
135     }
136     return top;
137   }
138 
139   private int skipSpaces(byte[] exp, int index) {
140     while (index < exp.length -1 && exp[index+1] == SPACE) {
141       index++;
142     }
143     return index;
144   }
145 
146   private void processCloseParan(Stack<ExpressionNode> expStack, String expS, int index)
147       throws ParseException {
148     if (expStack.size() < 2) {
149       // When ) comes we expect atleast a ( node and another leaf/non leaf node
150       // in stack.
151       throw new ParseException();
152     } else {
153       ExpressionNode top = expStack.pop();
154       ExpressionNode secondTop = expStack.pop();
155       // The second top must be a ( node and top should not be a ). Top can be
156       // any thing else
157       if (top == LeafExpressionNode.OPEN_PARAN_NODE
158           || secondTop != LeafExpressionNode.OPEN_PARAN_NODE) {
159         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
160       }
161       // a&(b|) is not valid.
162       // The top can be a ! node but with exactly child nodes. !).. is invalid
163       // Other NonLeafExpressionNode , then there should be exactly 2 child.
164       // (a&) is not valid.
165       if (top instanceof NonLeafExpressionNode) {
166         NonLeafExpressionNode nlTop = (NonLeafExpressionNode) top;
167         if ((nlTop.getOperator() == Operator.NOT && nlTop.getChildExps().size() != 1)
168             || (nlTop.getOperator() != Operator.NOT && nlTop.getChildExps().size() != 2)) {
169           throw new ParseException("Error parsing expression " + expS + " at column : " + index);
170         }
171       }
172       // When (a|b)&(c|d) comes while processing the second ) there will be
173       // already (a|b)& node
174       // avail in the stack. The top will be c|d node. We need to take it out
175       // and combine as one
176       // node.
177       if (!expStack.isEmpty()) {
178         ExpressionNode thirdTop = expStack.peek();
179         if (thirdTop instanceof NonLeafExpressionNode) {
180           NonLeafExpressionNode nlThirdTop = (NonLeafExpressionNode) expStack.pop();
181           nlThirdTop.addChildExp(top);
182           if (nlThirdTop.getOperator() == Operator.NOT) {
183             // It is a NOT node. So there may be a NonLeafExpressionNode below
184             // it to which the
185             // completed NOT can be added now.
186             if (!expStack.isEmpty()) {
187               ExpressionNode fourthTop = expStack.peek();
188               if (fourthTop instanceof NonLeafExpressionNode) {
189                 // Its Operator will be OR or AND
190                 NonLeafExpressionNode nlFourthTop = (NonLeafExpressionNode) fourthTop;
191                 assert nlFourthTop.getOperator() != Operator.NOT;
192                 // Also for sure its number of children will be 1
193                 assert nlFourthTop.getChildExps().size() == 1;
194                 nlFourthTop.addChildExp(nlThirdTop);
195                 return;// This case no need to add back the nlThirdTop.
196               }
197             }
198           }
199           top = nlThirdTop;
200         }
201       }
202       expStack.push(top);
203     }
204   }
205 
206   private void processOpenParan(Stack<ExpressionNode> expStack, String expS, int index)
207       throws ParseException {
208     if (!expStack.isEmpty()) {
209       ExpressionNode top = expStack.peek();
210       // Top can not be a Label Node. a(.. is not valid. but ((a.. is fine.
211       if (top instanceof LeafExpressionNode && top != LeafExpressionNode.OPEN_PARAN_NODE) {
212         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
213       } else if (top instanceof NonLeafExpressionNode) {
214         // Top is non leaf.
215         // It can be ! node but with out any child nodes. !a(.. is invalid
216         // Other NonLeafExpressionNode , then there should be exactly 1 child.
217         // a&b( is not valid.
218         // a&( is valid though. Also !( is valid
219         NonLeafExpressionNode nlTop = (NonLeafExpressionNode) top;
220         if ((nlTop.getOperator() == Operator.NOT && nlTop.getChildExps().size() != 0)
221             || (nlTop.getOperator() != Operator.NOT && nlTop.getChildExps().size() != 1)) {
222           throw new ParseException("Error parsing expression " + expS + " at column : " + index);
223         }
224       }
225     }
226     expStack.push(LeafExpressionNode.OPEN_PARAN_NODE);
227   }
228 
229   private void processLabelExpNode(LeafExpressionNode node, Stack<ExpressionNode> expStack,
230       String expS, int index) throws ParseException {
231     if (expStack.isEmpty()) {
232       expStack.push(node);
233     } else {
234       ExpressionNode top = expStack.peek();
235       if (top == LeafExpressionNode.OPEN_PARAN_NODE) {
236         expStack.push(node);
237       } else if (top instanceof NonLeafExpressionNode) {
238         NonLeafExpressionNode nlTop = (NonLeafExpressionNode) expStack.pop();
239         nlTop.addChildExp(node);
240         if (nlTop.getOperator() == Operator.NOT && !expStack.isEmpty()) {
241           ExpressionNode secondTop = expStack.peek();
242           if (secondTop == LeafExpressionNode.OPEN_PARAN_NODE) {
243             expStack.push(nlTop);
244           } else if (secondTop instanceof NonLeafExpressionNode) {
245             ((NonLeafExpressionNode) secondTop).addChildExp(nlTop);
246           }
247         } else {
248           expStack.push(nlTop);
249         }
250       } else {
251         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
252       }
253     }
254   }
255 
256   private void processANDorOROp(Operator op, Stack<ExpressionNode> expStack, String expS, int index)
257       throws ParseException {
258     if (expStack.isEmpty()) {
259       throw new ParseException("Error parsing expression " + expS + " at column : " + index);
260     }
261     ExpressionNode top = expStack.pop();
262     if (top.isSingleNode()) {
263       if (top == LeafExpressionNode.OPEN_PARAN_NODE) {
264         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
265       }
266       expStack.push(new NonLeafExpressionNode(op, top));
267     } else {
268       NonLeafExpressionNode nlTop = (NonLeafExpressionNode) top;
269       if (nlTop.getChildExps().size() != 2) {
270         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
271       }
272       expStack.push(new NonLeafExpressionNode(op, nlTop));
273     }
274   }
275 
276   private void processNOTOp(Stack<ExpressionNode> expStack, String expS, int index)
277       throws ParseException {
278     // When ! comes, the stack can be empty or top ( or top can be some exp like
279     // a&
280     // !!.., a!, a&b!, !a! are invalid
281     if (!expStack.isEmpty()) {
282       ExpressionNode top = expStack.peek();
283       if (top.isSingleNode() && top != LeafExpressionNode.OPEN_PARAN_NODE) {
284         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
285       }
286       if (!top.isSingleNode() && ((NonLeafExpressionNode) top).getChildExps().size() != 1) {
287         throw new ParseException("Error parsing expression " + expS + " at column : " + index);
288       }
289     }
290     expStack.push(new NonLeafExpressionNode(Operator.NOT));
291   }
292 
293   private static boolean endDoubleQuotesFound(byte b) {
294     return (b == DOUBLE_QUOTES);
295   }
296   private static boolean isEndOfLabel(byte b) {
297     return (b == OPEN_PARAN || b == CLOSE_PARAN || b == OR || b == AND || 
298         b == NOT || b == SPACE);
299   }
300 
301   private static Operator getOperator(byte op) {
302     switch (op) {
303     case AND:
304       return Operator.AND;
305     case OR:
306       return Operator.OR;
307     case NOT:
308       return Operator.NOT;
309     }
310     return null;
311   }
312 }