1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2016 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * 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
19 package org.wildfly.common.context;
20
21 import java.security.Permission;
22 import java.security.PermissionCollection;
23 import java.util.function.Supplier;
24
25 import org.wildfly.common.Assert;
26 import org.wildfly.common._private.CommonMessages;
27 import org.wildfly.common.annotation.NotNull;
28
29 /**
30  * A permission object for operations on {@link ContextManager} instances.
31  * <p>
32  * This type of permission requires a {@code name} and an {@code action}.  The {@code name} may be the name
33  * of a context manager, or the special {@code *} name which means the permission applies to all context managers.
34  * <p>
35  * The {@code action} may be one or more of the following (each action name being separated by a comma):
36  * <ul>
37  *     <li>{@code get} - allow {@linkplain ContextManager#get() getting} the current context</li>
38  *     <li>{@code getPrivilegedSupplier} - allow access to the {@link ContextManager#getPrivilegedSupplier()} method</li>
39  *     <li>{@code getGlobalDefault} - allow access to the {@linkplain ContextManager#getGlobalDefault() global default context}</li>
40  *     <li>{@code setGlobalDefault} - allow {@linkplain ContextManager#setGlobalDefault(Contextual) setting the global default instance}</li>
41  *     <li>{@code setGlobalDefaultSupplier} - allow {@linkplain ContextManager#setGlobalDefaultSupplier(Supplier) setting the global default instance supplier}</li>
42  *     <li>{@code getThreadDefault} - allow access to the {@linkplain ContextManager#getThreadDefault() per-thread default context}</li>
43  *     <li>{@code setThreadDefault} - allow {@linkplain ContextManager#setThreadDefault(Contextual) setting the per-thread default instance}</li>
44  *     <li>{@code setThreadDefaultSupplier} - allow {@linkplain ContextManager#setThreadDefaultSupplier(Supplier) setting the per-thread default instance supplier}</li>
45  * </ul>
46  * Additionally, the special {@code *} action name is allowed which implies all of the above actions.
47  * <p>
48  * The {@link #newPermissionCollection()} method returns an optimized container for context permissions.
49  *
50  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
51  */

52 public final class ContextPermission extends Permission {
53
54     private static final long serialVersionUID = 2149744699461086708L;
55
56     private static final int ACTION_GET                     = 0b00000000001;
57     private static final int ACTION_GET_PRIV_SUP            = 0b00000000010;
58     private static final int ACTION_GET_GLOBAL_DEF          = 0b00000000100;
59     private static final int ACTION_SET_GLOBAL_DEF          = 0b00000001000;
60     private static final int ACTION_SET_GLOBAL_DEF_SUP      = 0b00000010000;
61     private static final int ACTION_GET_THREAD_DEF          = 0b00000100000;
62     private static final int ACTION_SET_THREAD_DEF          = 0b00001000000;
63     private static final int ACTION_SET_THREAD_DEF_SUP      = 0b00010000000;
64     private static final int ACTION_GET_CLASSLOADER_DEF     = 0b00100000000;
65     private static final int ACTION_SET_CLASSLOADER_DEF     = 0b01000000000;
66     private static final int ACTION_SET_CLASSLOADER_DEF_SUP = 0b10000000000;
67
68     private static final int ACTION_ALL                     = 0b11111111111;
69
70     static final String STR_GET = "get";
71     static final String STR_GET_PRIV_SUP = "getPrivilegedSupplier";
72     static final String STR_GET_GLOBAL_DEF = "getGlobalDefault";
73     static final String STR_SET_GLOBAL_DEF = "setGlobalDefault";
74     static final String STR_SET_GLOBAL_DEF_SUP = "setGlobalDefaultSupplier";
75     static final String STR_GET_THREAD_DEF = "getThreadDefault";
76     static final String STR_SET_THREAD_DEF = "setThreadDefault";
77     static final String STR_SET_THREAD_DEF_SUP = "setThreadDefaultSupplier";
78     static final String STR_GET_CLASSLOADER_DEF = "getClassLoaderDefault";
79     static final String STR_SET_CLASSLOADER_DEF = "setClassLoaderDefault";
80     static final String STR_SET_CLASSLOADER_DEF_SUP = "setClassLoaderDefaultSupplier";
81
82     private final transient int actionBits;
83
84     private transient String actionString;
85
86     /**
87      * Constructs a permission with the specified name.
88      *
89      * @param name name of the Permission object being created (must not be {@code null})
90      * @param actions the actions string (must not be {@code null})
91      */

92     public ContextPermission(final String name, final String actions) {
93         super(name);
94         Assert.checkNotNullParam("name", name);
95         Assert.checkNotNullParam("actions", actions);
96         actionBits = parseActions(actions);
97     }
98
99     ContextPermission(final String name, final int actionBits) {
100         super(name);
101         Assert.checkNotNullParam("name", name);
102         this.actionBits = actionBits & ACTION_ALL;
103     }
104
105     private static int parseActions(final String actions) throws IllegalArgumentException {
106         int bits = 0;
107         int start = 0;
108         int idx = actions.indexOf(',');
109         if (idx == -1) {
110             return parseAction(actions);
111         } else do {
112             bits |= parseAction(actions.substring(start, idx));
113             start = idx + 1;
114             idx = actions.indexOf(',', start);
115         } while (idx != -1);
116         bits |= parseAction(actions.substring(start));
117         return bits;
118     }
119
120     private static int parseAction(final String action) {
121         switch (action.trim()) {
122             case STR_GET: return ACTION_GET;
123             case STR_GET_PRIV_SUP: return ACTION_GET_PRIV_SUP;
124             case STR_GET_GLOBAL_DEF: return ACTION_GET_GLOBAL_DEF;
125             case STR_SET_GLOBAL_DEF: return ACTION_SET_GLOBAL_DEF;
126             case STR_SET_GLOBAL_DEF_SUP: return ACTION_SET_GLOBAL_DEF_SUP;
127             case STR_GET_THREAD_DEF: return ACTION_GET_THREAD_DEF;
128             case STR_SET_THREAD_DEF: return ACTION_SET_THREAD_DEF;
129             case STR_SET_THREAD_DEF_SUP: return ACTION_SET_THREAD_DEF_SUP;
130             case STR_GET_CLASSLOADER_DEF: return ACTION_GET_CLASSLOADER_DEF;
131             case STR_SET_CLASSLOADER_DEF: return ACTION_SET_CLASSLOADER_DEF;
132             case STR_SET_CLASSLOADER_DEF_SUP: return ACTION_SET_CLASSLOADER_DEF_SUP;
133             case "*"return ACTION_ALL;
134             case ""return 0;
135             default: {
136                 throw CommonMessages.msg.invalidPermissionAction(action);
137             }
138         }
139     }
140
141     /**
142      * Determine if the given permission is implied by this permission.
143      *
144      * @param permission the other permission
145      * @return {@code trueif the other permission is not {@code null} and is a context permission which is implied by
146      *  this permission instance; {@code false} otherwise
147      */

148     public boolean implies(final Permission permission) {
149         return permission instanceof ContextPermission && implies((ContextPermission) permission);
150     }
151
152     /**
153      * Determine if the given permission is implied by this permission.
154      *
155      * @param permission the other permission
156      * @return {@code trueif the other permission is not {@code null} and is a context permission which is implied by
157      *  this permission instance; {@code false} otherwise
158      */

159     public boolean implies(final ContextPermission permission) {
160         return this == permission || permission != null && isSet(this.actionBits, permission.actionBits) && impliesName(permission.getName());
161     }
162
163     private boolean impliesName(String otherName) {
164         final String myName = getName();
165         return myName.equals("*") || myName.equals(otherName);
166     }
167
168     static boolean isSet(int mask, int test) {
169         return (mask & test) == test;
170     }
171
172     /**
173      * Determine if this permission is equal to the given object.
174      *
175      * @param obj the other object
176      * @return {@code trueif the object is a context permission that is exactly equal to this one; {@code false} otherwise
177      */

178     public boolean equals(final Object obj) {
179         return obj instanceof ContextPermission && equals((ContextPermission) obj);
180     }
181
182     /**
183      * Determine if this permission is equal to the given permission.
184      *
185      * @param permission the other permission
186      * @return {@code trueif the permission is a context permission that is exactly equal to this one; {@code false} otherwise
187      */

188     public boolean equals(final ContextPermission permission) {
189         return this == permission || permission != null && actionBits == permission.actionBits && getName().equals(permission.getName());
190     }
191
192     /**
193      * Get the hash code of this permission.
194      *
195      * @return the hash code of this permission
196      */

197     public int hashCode() {
198         return getName().hashCode() * 17 + actionBits;
199     }
200
201     /**
202      * Get the actions string.  This string will be returned in a canonical format.
203      *
204      * @return the actions string
205      */

206     public String getActions() {
207         String actionString = this.actionString;
208         if (actionString == null) {
209             final int actionBits = this.actionBits;
210             if (isSet(actionBits, ACTION_ALL)) {
211                 return this.actionString = "*";
212             } else if (actionBits == 0) {
213                 return this.actionString = "";
214             }
215             final StringBuilder b = new StringBuilder();
216             if (isSet(actionBits, ACTION_GET)) b.append(STR_GET).append(',');
217             if (isSet(actionBits, ACTION_GET_PRIV_SUP)) b.append(STR_GET_PRIV_SUP).append(',');
218             if (isSet(actionBits, ACTION_GET_GLOBAL_DEF)) b.append(STR_GET_GLOBAL_DEF).append(',');
219             if (isSet(actionBits, ACTION_SET_GLOBAL_DEF)) b.append(STR_SET_GLOBAL_DEF).append(',');
220             if (isSet(actionBits, ACTION_SET_GLOBAL_DEF_SUP)) b.append(STR_SET_GLOBAL_DEF_SUP).append(',');
221             if (isSet(actionBits, ACTION_GET_THREAD_DEF)) b.append(STR_GET_THREAD_DEF).append(',');
222             if (isSet(actionBits, ACTION_SET_THREAD_DEF)) b.append(STR_SET_THREAD_DEF).append(',');
223             if (isSet(actionBits, ACTION_SET_THREAD_DEF_SUP)) b.append(STR_SET_THREAD_DEF_SUP).append(',');
224             if (isSet(actionBits, ACTION_GET_CLASSLOADER_DEF)) b.append(STR_GET_CLASSLOADER_DEF).append(',');
225             if (isSet(actionBits, ACTION_SET_CLASSLOADER_DEF)) b.append(STR_SET_CLASSLOADER_DEF).append(',');
226             if (isSet(actionBits, ACTION_SET_CLASSLOADER_DEF_SUP)) b.append(STR_SET_CLASSLOADER_DEF_SUP).append(',');
227             assert b.length() > 0;
228             b.setLength(b.length() - 1);
229             return this.actionString = b.toString();
230         }
231         return actionString;
232     }
233
234     /**
235      * Create a copy of this permission with the additional given actions.
236      *
237      * @param actions the additional actions (must not be {@code null})
238      * @return the new permission (not {@code null})
239      */

240     @NotNull
241     public ContextPermission withActions(String actions) {
242         return withActionBits(parseActions(actions));
243     }
244
245     ContextPermission withActionBits(int actionBits) {
246         if (isSet(this.actionBits, actionBits)) {
247             return this;
248         } else {
249             return new ContextPermission(getName(), this.actionBits | actionBits);
250         }
251     }
252
253     /**
254      * Create a copy of this permission without any of the given actions.
255      *
256      * @param actions the actions to subtract (must not be {@code null})
257      * @return the new permission (not {@code null})
258      */

259     @NotNull
260     public ContextPermission withoutActions(String actions) {
261         return withoutActionBits(parseActions(actions));
262     }
263
264     ContextPermission withoutActionBits(final int actionBits) {
265         if ((actionBits & this.actionBits) == 0) {
266             return this;
267         } else {
268             return new ContextPermission(getName(), this.actionBits & ~actionBits);
269         }
270     }
271
272     /**
273      * Get a new permission collection instance which can hold this type of permissions.
274      *
275      * @return a new permission collection instance (not {@code null})
276      */

277     public PermissionCollection newPermissionCollection() {
278         return new ContextPermissionCollection();
279     }
280
281     int getActionBits() {
282         return actionBits;
283     }
284 }
285