001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.shiro.authz;
018
019import org.apache.shiro.authz.Permission;
020import org.apache.shiro.authz.permission.WildcardPermission;
021
022import java.lang.reflect.Method;
023import java.util.List;
024import java.util.Set;
025
026/**
027 * @since 5.10.0
028 */
029public class ActiveMQWildcardPermission extends WildcardPermission {
030
031    private final boolean caseSensitive;
032
033    public ActiveMQWildcardPermission(String wildcardString) {
034        this(wildcardString, true);
035    }
036
037    public ActiveMQWildcardPermission(String wildcardString, boolean caseSensitive) {
038        super(wildcardString, caseSensitive);
039        this.caseSensitive = caseSensitive;
040    }
041
042    @Override
043    public boolean implies(Permission p) {
044        // By default only supports comparisons with other WildcardPermissions
045        if (!(p instanceof WildcardPermission)) {
046            return false;
047        }
048
049        WildcardPermission wp = (WildcardPermission) p;
050
051        List<Set<String>> otherParts = getParts(wp);
052
053        int i = 0;
054        for (Set<String> otherPart : otherParts) {
055            // If this permission has less parts than the other permission, everything after the number of parts contained
056            // in this permission is automatically implied, so return true
057            if (getParts().size() - 1 < i) {
058                return true;
059            } else {
060                Set<String> thisPart = getParts().get(i);
061
062                for (String token : thisPart) {
063                    if (token.equals(WILDCARD_TOKEN)) {
064                        continue;
065                    }
066                    for (String otherToken : otherPart) {
067                        if (!caseSensitive) {
068                            otherToken = otherToken.toLowerCase();
069                        }
070                        if (!matches(token, otherToken)) {
071                            return false;
072                        }
073                    }
074                }
075                i++;
076            }
077        }
078
079        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
080        for (; i < getParts().size(); i++) {
081            Set<String> part = getParts().get(i);
082            if (!part.contains(WILDCARD_TOKEN)) {
083                return false;
084            }
085        }
086
087        return true;
088    }
089
090    /**
091     * Tests whether or not a string matches against a pattern.
092     * The pattern may contain two special characters:<br>
093     * '*' means zero or more characters<br>
094     * '?' means one and only one character
095     *
096     * @param pattern pattern to match against.
097     *                Must not be <code>null</code>.
098     * @param value   string which must be matched against the pattern.
099     *                Must not be <code>null</code>.
100     * @return <code>true</code> if the string matches against the
101     *         pattern, or <code>false</code> otherwise.
102     */
103    protected boolean matches(String pattern, String value) {
104
105        char[] patArr = pattern.toCharArray();
106        char[] valArr = value.toCharArray();
107        int patIndex = 0;
108        int patEndIndex = patArr.length - 1;
109        int valIndex = 0;
110        int valEndIndex = valArr.length - 1;
111        char ch;
112
113        boolean patternContainsStar = false;
114        for (char patternChar : patArr) {
115            if (patternChar == '*') {
116                patternContainsStar = true;
117                break;
118            }
119        }
120
121        if (!patternContainsStar) {
122            // No '*'s, so we make a shortcut
123            if (patEndIndex != valEndIndex) {
124                return false; // Pattern and string do not have the same size
125            }
126            for (int i = 0; i <= patEndIndex; i++) {
127                ch = patArr[i];
128                if (ch != '?') {
129                    if (ch != valArr[i]) {
130                        return false;// Character mismatch
131                    }
132                }
133            }
134            return true; // String matches against pattern
135        }
136
137
138        // Process characters before first star
139        while ((ch = patArr[patIndex]) != '*' && valIndex <= valEndIndex) {
140            if (ch != '?') {
141                if (ch != valArr[valIndex]) {
142                    return false;// Character mismatch
143                }
144            }
145            patIndex++;
146            valIndex++;
147        }
148        if (valIndex > valEndIndex) {
149            // All characters in the value are used. Check if only '*'s remain
150            // in the pattern. If so, we succeeded. Otherwise failure.
151            for (int i = patIndex; i <= patEndIndex; i++) {
152                if (patArr[i] != '*') {
153                    return false;
154                }
155            }
156            return true;
157        }
158
159        // Process characters after last star
160        while ((ch = patArr[patEndIndex]) != '*' && valIndex <= valEndIndex) {
161            if (ch != '?') {
162                if (ch != valArr[valEndIndex]) {
163                    return false;// Character mismatch
164                }
165            }
166            patEndIndex--;
167            valEndIndex--;
168        }
169        if (valIndex > valEndIndex) {
170            // All characters in the value are used. Check if only '*'s remain
171            // in the pattern. If so, we succeeded. Otherwise failure.
172            for (int i = patIndex; i <= patEndIndex; i++) {
173                if (patArr[i] != '*') {
174                    return false;
175                }
176            }
177            return true;
178        }
179
180        // process pattern between stars. patIndex and patEndIndex always point to a '*'.
181        while (patIndex != patEndIndex && valIndex <= valEndIndex) {
182            int innerPatternIndex = -1;
183            for (int i = patIndex + 1; i <= patEndIndex; i++) {
184                if (patArr[i] == '*') {
185                    innerPatternIndex = i;
186                    break;
187                }
188            }
189            if (innerPatternIndex == patIndex + 1) {
190                // Two stars next to each other, skip the first one.
191                patIndex++;
192                continue;
193            }
194            // Find the pattern between patIndex & innerPatternIndex in the value between
195            // valIndex and valEndIndex
196            int innerPatternLength = (innerPatternIndex - patIndex - 1);
197            int innerValueLength = (valEndIndex - valIndex + 1);
198            int foundIndex = -1;
199            innerValueLoop:
200            for (int i = 0; i <= innerValueLength - innerPatternLength; i++) {
201                for (int j = 0; j < innerPatternLength; j++) {
202                    ch = patArr[patIndex + j + 1];
203                    if (ch != '?') {
204                        if (ch != valArr[valIndex + i + j]) {
205                            continue innerValueLoop;
206                        }
207                    }
208                }
209
210                foundIndex = valIndex + i;
211                break;
212            }
213
214            if (foundIndex == -1) {
215                return false;
216            }
217
218            patIndex = innerPatternIndex;
219            valIndex = foundIndex + innerPatternLength;
220        }
221
222        // All characters in the string are used. Check if only '*'s are left
223        // in the pattern. If so, we succeeded. Otherwise failure.
224        for (int i = patIndex; i <= patEndIndex; i++) {
225            if (patArr[i] != '*') {
226                return false;
227            }
228        }
229
230        return true;
231    }
232
233    protected List<Set<String>> getParts(WildcardPermission wp) {
234        if (wp instanceof ActiveMQWildcardPermission) {
235            return ((ActiveMQWildcardPermission) wp).getParts();
236        } else {
237            return getPartsByReflection(wp);
238        }
239    }
240
241    protected List<Set<String>> getPartsByReflection(WildcardPermission wp) {
242        try {
243            return doGetPartsByReflection(wp);
244        } catch (Exception e) {
245            String msg = "Unable to obtain WildcardPermission instance's 'parts' value.";
246            throw new IllegalStateException(msg, e);
247        }
248    }
249
250    @SuppressWarnings("unchecked")
251    protected List<Set<String>> doGetPartsByReflection(WildcardPermission wp) throws Exception {
252        Method getParts = WildcardPermission.class.getDeclaredMethod("getParts");
253        getParts.setAccessible(true);
254        return (List<Set<String>>) getParts.invoke(wp);
255    }
256
257    @Override
258    public String toString() {
259        StringBuilder buffer = new StringBuilder();
260        for (Set<String> part : getParts()) {
261            if (buffer.length() > 0) {
262                buffer.append(":");
263            }
264            boolean first = true;
265            for (String token : part) {
266                if (!first) {
267                    buffer.append(",");
268                }
269                buffer.append(token);
270                first = false;
271            }
272        }
273        return buffer.toString();
274    }
275}