17 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
182 |
ilm |
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
17 |
ilm |
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
package org.openconcerto.sql.users.rights;
|
|
|
15 |
|
|
|
16 |
import org.openconcerto.sql.Log;
|
|
|
17 |
import org.openconcerto.sql.model.DBRoot;
|
144 |
ilm |
18 |
import org.openconcerto.sql.model.SQLData;
|
|
|
19 |
import org.openconcerto.sql.model.SQLDataListener;
|
17 |
ilm |
20 |
import org.openconcerto.sql.model.SQLRow;
|
|
|
21 |
import org.openconcerto.sql.model.SQLRowAccessor;
|
|
|
22 |
import org.openconcerto.sql.model.SQLRowValues;
|
|
|
23 |
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
|
|
|
24 |
import org.openconcerto.sql.model.SQLSelect;
|
|
|
25 |
import org.openconcerto.sql.model.SQLTable;
|
|
|
26 |
import org.openconcerto.sql.model.SQLTableModifiedListener;
|
|
|
27 |
import org.openconcerto.sql.model.Where;
|
144 |
ilm |
28 |
import org.openconcerto.sql.model.graph.Link;
|
|
|
29 |
import org.openconcerto.sql.request.SQLCache;
|
|
|
30 |
import org.openconcerto.sql.request.SQLCacheWatcher;
|
17 |
ilm |
31 |
import org.openconcerto.sql.users.UserManager;
|
144 |
ilm |
32 |
import org.openconcerto.sql.users.UserSingleton;
|
|
|
33 |
import org.openconcerto.sql.users.UserSingletonManager;
|
|
|
34 |
import org.openconcerto.utils.CollectionMap2Itf.ListMapItf;
|
41 |
ilm |
35 |
import org.openconcerto.utils.CompareUtils;
|
|
|
36 |
import org.openconcerto.utils.CompareUtils.Equalizer;
|
19 |
ilm |
37 |
import org.openconcerto.utils.ExceptionHandler;
|
144 |
ilm |
38 |
import org.openconcerto.utils.IFutureTask;
|
80 |
ilm |
39 |
import org.openconcerto.utils.ListMap;
|
144 |
ilm |
40 |
import org.openconcerto.utils.RTInterruptedException;
|
|
|
41 |
import org.openconcerto.utils.ThreadFactory;
|
17 |
ilm |
42 |
import org.openconcerto.utils.Tuple2;
|
|
|
43 |
import org.openconcerto.utils.Tuple3;
|
144 |
ilm |
44 |
import org.openconcerto.utils.cache.CacheItem.RemovalType;
|
|
|
45 |
import org.openconcerto.utils.cache.CacheResult;
|
|
|
46 |
import org.openconcerto.utils.cache.CacheWatcher;
|
|
|
47 |
import org.openconcerto.utils.cache.CacheWatcherFactory;
|
|
|
48 |
import org.openconcerto.utils.cache.ICache;
|
|
|
49 |
import org.openconcerto.utils.cache.ICache.ItemEvent;
|
|
|
50 |
import org.openconcerto.utils.cache.ICacheSupport;
|
|
|
51 |
import org.openconcerto.utils.cc.IClosure;
|
17 |
ilm |
52 |
import org.openconcerto.utils.cc.IFactory;
|
|
|
53 |
import org.openconcerto.utils.cc.ITransformer;
|
|
|
54 |
|
144 |
ilm |
55 |
import java.beans.PropertyChangeEvent;
|
|
|
56 |
import java.beans.PropertyChangeListener;
|
|
|
57 |
import java.beans.PropertyChangeSupport;
|
17 |
ilm |
58 |
import java.util.ArrayList;
|
73 |
ilm |
59 |
import java.util.Collections;
|
17 |
ilm |
60 |
import java.util.HashMap;
|
|
|
61 |
import java.util.HashSet;
|
|
|
62 |
import java.util.List;
|
|
|
63 |
import java.util.Map;
|
|
|
64 |
import java.util.Set;
|
144 |
ilm |
65 |
import java.util.concurrent.Callable;
|
|
|
66 |
import java.util.concurrent.ExecutionException;
|
|
|
67 |
import java.util.concurrent.ExecutorService;
|
|
|
68 |
import java.util.concurrent.Executors;
|
|
|
69 |
import java.util.concurrent.Future;
|
17 |
ilm |
70 |
|
73 |
ilm |
71 |
import net.jcip.annotations.GuardedBy;
|
|
|
72 |
|
144 |
ilm |
73 |
public class UserRightsManager implements UserSingleton {
|
17 |
ilm |
74 |
|
73 |
ilm |
75 |
public static final String USER_RIGHT_TABLE = UserRightSQLElement.TABLE_NAME;
|
144 |
ilm |
76 |
private static final UserSingletonManager<UserRightsManager> sMngr = new UserSingletonManager<UserRightsManager>(USER_RIGHT_TABLE) {
|
|
|
77 |
@Override
|
|
|
78 |
protected UserRightsManager createInstance(SQLTable t) {
|
|
|
79 |
return new UserRightsManager(t);
|
|
|
80 |
}
|
|
|
81 |
};
|
|
|
82 |
|
73 |
ilm |
83 |
public static final String SUPERUSER_FIELD = "SUPERUSER";
|
80 |
ilm |
84 |
private static final int ADMIN_ID = SQLRow.NONEXISTANT_ID;
|
73 |
ilm |
85 |
/**
|
|
|
86 |
* Only administrators can see user rights.
|
|
|
87 |
*/
|
|
|
88 |
public static final String ADMIN_FIELD = "ADMIN";
|
83 |
ilm |
89 |
private static final ListMap<String, Tuple2<String, Boolean>> SUPERUSER_RIGHTS = ListMap.singleton(null, Tuple2.create((String) null, true));
|
|
|
90 |
private static final ListMap<String, Tuple2<String, Boolean>> NO_RIGHTS = ListMap.singleton(null, Tuple2.create((String) null, false));
|
73 |
ilm |
91 |
public static final List<MacroRight> DEFAULT_MACRO_RIGHTS = Collections.synchronizedList(new ArrayList<MacroRight>());
|
|
|
92 |
static {
|
80 |
ilm |
93 |
// "addRight() ambiguous"
|
|
|
94 |
assert ADMIN_ID < SQLRow.MIN_VALID_ID;
|
73 |
ilm |
95 |
DEFAULT_MACRO_RIGHTS.add(new LockAdminUserRight());
|
|
|
96 |
DEFAULT_MACRO_RIGHTS.add(new TableAllRights(true));
|
|
|
97 |
DEFAULT_MACRO_RIGHTS.add(new TableAllRights(false));
|
|
|
98 |
}
|
17 |
ilm |
99 |
|
144 |
ilm |
100 |
public static UserSingletonManager<UserRightsManager> getSingletonManager() {
|
|
|
101 |
return sMngr;
|
73 |
ilm |
102 |
}
|
|
|
103 |
|
|
|
104 |
/**
|
|
|
105 |
* Set the instance using the table in the passed root.
|
|
|
106 |
*
|
|
|
107 |
* @param root the root where the rights should be.
|
|
|
108 |
* @return the new instance, <code>null</code> if <code>root</code> does not contain the
|
|
|
109 |
* {@value #USER_RIGHT_TABLE} table.
|
|
|
110 |
*/
|
|
|
111 |
public static UserRightsManager setInstanceFromRoot(final DBRoot root) {
|
144 |
ilm |
112 |
return getSingletonManager().setInstanceFromRoot(root);
|
73 |
ilm |
113 |
}
|
|
|
114 |
|
|
|
115 |
/**
|
|
|
116 |
* Set the instance.
|
|
|
117 |
*
|
|
|
118 |
* @param t the table, <code>null</code> to remove.
|
|
|
119 |
* @return the new instance, <code>null</code> if t was.
|
|
|
120 |
*/
|
144 |
ilm |
121 |
public static UserRightsManager setInstance(final SQLTable t) {
|
|
|
122 |
return getSingletonManager().setInstance(t);
|
73 |
ilm |
123 |
}
|
|
|
124 |
|
144 |
ilm |
125 |
public static UserRightsManager getInstance() {
|
|
|
126 |
return getSingletonManager().getInstance();
|
17 |
ilm |
127 |
}
|
|
|
128 |
|
|
|
129 |
public static final UserRights getCurrentUserRights() {
|
|
|
130 |
final UserManager mngr = UserManager.getInstance();
|
182 |
ilm |
131 |
final UserRightsManager rightsMngr = getInstance();
|
|
|
132 |
return getCurrentUserRights(rightsMngr, mngr);
|
|
|
133 |
}
|
|
|
134 |
|
|
|
135 |
public static final UserRights getCurrentUserRights(final UserRightsManager rightsMngr, final UserManager mngr) {
|
17 |
ilm |
136 |
// if right table doesn't exist, give access to everything
|
144 |
ilm |
137 |
if (rightsMngr == null)
|
73 |
ilm |
138 |
return UserRights.ALLOW_ALL;
|
17 |
ilm |
139 |
// else if there are rights (and thus users) but no user is defined, use the default rights
|
|
|
140 |
else
|
144 |
ilm |
141 |
return rightsMngr.getUserRights(mngr.getCurrentUser() == null ? null : mngr.getCurrentUser().getId());
|
17 |
ilm |
142 |
}
|
|
|
143 |
|
144 |
ilm |
144 |
private static final class JavaRights {
|
|
|
145 |
|
|
|
146 |
static final String TUPLES_CHANGED = "tuples";
|
|
|
147 |
static final String TUPLE_CHANGED = "tuple";
|
|
|
148 |
|
|
|
149 |
// rights by user ID, immutable
|
|
|
150 |
@GuardedBy("this")
|
|
|
151 |
private ListMapItf<Integer, RightTuple> tuples;
|
|
|
152 |
private final PropertyChangeSupport propSupp = new PropertyChangeSupport(this);
|
|
|
153 |
|
|
|
154 |
public JavaRights() {
|
|
|
155 |
this.tuples = ListMap.empty();
|
|
|
156 |
}
|
|
|
157 |
|
|
|
158 |
final void changeRight(final boolean add, final Integer userID, final RightTuple right) {
|
|
|
159 |
if (add && right == null)
|
|
|
160 |
throw new NullPointerException("Null right entry");
|
|
|
161 |
final ListMapItf<Integer, RightTuple> oldVal, newVal;
|
|
|
162 |
synchronized (this) {
|
|
|
163 |
oldVal = this.tuples;
|
|
|
164 |
final ListMap<Integer, RightTuple> newMap = new ListMap<Integer, RightTuple>(this.tuples);
|
|
|
165 |
if (add)
|
|
|
166 |
newMap.add(userID, right);
|
|
|
167 |
else if (right == null)
|
|
|
168 |
newMap.remove(userID);
|
|
|
169 |
else
|
|
|
170 |
newMap.removeAllInstancesOfItem(userID, right);
|
|
|
171 |
newVal = ListMap.unmodifiableMap(newMap);
|
|
|
172 |
this.tuples = newVal;
|
|
|
173 |
}
|
|
|
174 |
this.propSupp.firePropertyChange(TUPLES_CHANGED, oldVal, newVal);
|
|
|
175 |
this.propSupp.firePropertyChange(TUPLE_CHANGED, null, userID);
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
final synchronized ListMapItf<Integer, RightTuple> getTuples() {
|
|
|
179 |
return this.tuples;
|
|
|
180 |
}
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
// cheat a little by pretending to be SQL, so that java rights can be listened to by the cache.
|
|
|
184 |
private static class JavaRightsUser implements SQLData {
|
|
|
185 |
|
|
|
186 |
private final JavaRights rights;
|
|
|
187 |
private final int id;
|
|
|
188 |
|
|
|
189 |
public JavaRightsUser(final JavaRights rights, final int id) {
|
|
|
190 |
super();
|
|
|
191 |
this.rights = rights;
|
|
|
192 |
this.id = id;
|
|
|
193 |
}
|
|
|
194 |
|
|
|
195 |
@Override
|
|
|
196 |
public SQLTableModifiedListener createTableListener(final SQLDataListener l) {
|
|
|
197 |
return null;
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
@Override
|
|
|
201 |
public SQLTable getTable() {
|
|
|
202 |
return null;
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
@Override
|
|
|
206 |
public int hashCode() {
|
|
|
207 |
final int prime = 31;
|
|
|
208 |
int result = 1;
|
|
|
209 |
result = prime * result + this.id;
|
|
|
210 |
result = prime * result + this.rights.hashCode();
|
|
|
211 |
return result;
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
@Override
|
|
|
215 |
public boolean equals(Object obj) {
|
|
|
216 |
if (this == obj)
|
|
|
217 |
return true;
|
|
|
218 |
if (obj == null)
|
|
|
219 |
return false;
|
|
|
220 |
if (getClass() != obj.getClass())
|
|
|
221 |
return false;
|
|
|
222 |
final JavaRightsUser other = (JavaRightsUser) obj;
|
|
|
223 |
return this.id == other.id && this.rights.equals(other.rights);
|
|
|
224 |
}
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
private static class JavaRightsWatcher extends CacheWatcher<SQLData> {
|
|
|
228 |
|
|
|
229 |
private final int id;
|
|
|
230 |
private final PropertyChangeListener l;
|
|
|
231 |
|
|
|
232 |
public JavaRightsWatcher(JavaRightsUser u) {
|
|
|
233 |
super(u);
|
|
|
234 |
this.id = u.id;
|
|
|
235 |
this.l = new PropertyChangeListener() {
|
|
|
236 |
@Override
|
|
|
237 |
public void propertyChange(PropertyChangeEvent evt) {
|
|
|
238 |
if (((Number) evt.getNewValue()).intValue() == JavaRightsWatcher.this.id) {
|
|
|
239 |
dataChanged(evt);
|
|
|
240 |
}
|
|
|
241 |
}
|
|
|
242 |
};
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
@Override
|
|
|
246 |
protected void startWatching() {
|
|
|
247 |
((JavaRightsUser) this.getData()).rights.propSupp.addPropertyChangeListener(JavaRights.TUPLE_CHANGED, this.l);
|
|
|
248 |
}
|
|
|
249 |
|
|
|
250 |
@Override
|
|
|
251 |
protected void stopWatching() {
|
|
|
252 |
((JavaRightsUser) this.getData()).rights.propSupp.removePropertyChangeListener(JavaRights.TUPLE_CHANGED, this.l);
|
|
|
253 |
}
|
|
|
254 |
}
|
|
|
255 |
|
17 |
ilm |
256 |
// Gérer un droit avec une classe
|
144 |
ilm |
257 |
@GuardedBy("macroRights")
|
17 |
ilm |
258 |
private final Map<String, MacroRight> macroRights;
|
|
|
259 |
// {user -> {code -> [<object, bool>]}}
|
144 |
ilm |
260 |
@GuardedBy("rights")
|
|
|
261 |
private final Map<Integer, ListMapItf<String, Tuple2<String, Boolean>>> rights;
|
|
|
262 |
private final SQLCache<Integer, ListMapItf<String, Tuple2<String, Boolean>>> cache;
|
73 |
ilm |
263 |
private final SQLTable table;
|
144 |
ilm |
264 |
private final Link toUserLink;
|
|
|
265 |
private final JavaRights javaRights;
|
|
|
266 |
|
73 |
ilm |
267 |
@GuardedBy("this")
|
144 |
ilm |
268 |
private final Map<Integer, UserRights> userRights;
|
17 |
ilm |
269 |
|
144 |
ilm |
270 |
private final ExecutorService exec;
|
|
|
271 |
|
73 |
ilm |
272 |
private UserRightsManager(final SQLTable t) {
|
|
|
273 |
if (t == null)
|
|
|
274 |
throw new NullPointerException("Missing table");
|
144 |
ilm |
275 |
this.macroRights = Collections.synchronizedMap(new HashMap<String, MacroRight>());
|
|
|
276 |
this.rights = new HashMap<Integer, ListMapItf<String, Tuple2<String, Boolean>>>();
|
|
|
277 |
this.cache = new SQLCache<Integer, ListMapItf<String, Tuple2<String, Boolean>>>(15 * 60, -1, "Cache of rights") {
|
73 |
ilm |
278 |
@Override
|
144 |
ilm |
279 |
protected ICacheSupport<SQLData> createSupp(String name) {
|
|
|
280 |
final ICacheSupport<SQLData> res = new ICacheSupport<SQLData>(name);
|
|
|
281 |
res.setWatcherFactory(new CacheWatcherFactory<SQLData>() {
|
|
|
282 |
@Override
|
|
|
283 |
public CacheWatcher<SQLData> createWatcher(SQLData o) {
|
|
|
284 |
if (o instanceof JavaRightsUser) {
|
|
|
285 |
return new JavaRightsWatcher((JavaRightsUser) o);
|
|
|
286 |
} else {
|
|
|
287 |
return new SQLCacheWatcher(o);
|
|
|
288 |
}
|
|
|
289 |
}
|
|
|
290 |
});
|
|
|
291 |
return res;
|
73 |
ilm |
292 |
}
|
|
|
293 |
};
|
144 |
ilm |
294 |
this.javaRights = new JavaRights();
|
|
|
295 |
this.table = t;
|
156 |
ilm |
296 |
this.toUserLink = t.getField("ID_USER_COMMON").getFieldGroup().getForeignLink();
|
17 |
ilm |
297 |
defaultRegister();
|
144 |
ilm |
298 |
this.userRights = new HashMap<Integer, UserRights>();
|
|
|
299 |
this.exec = Executors.newSingleThreadExecutor(new ThreadFactory(this.getClass().getSimpleName() + " executor for " + t.getSQLName(), true).setPriority(Thread.MIN_PRIORITY));
|
|
|
300 |
|
|
|
301 |
this.cache.addItemListener(new IClosure<ItemEvent<Integer, ListMapItf<String, Tuple2<String, Boolean>>, SQLData>>() {
|
|
|
302 |
@Override
|
|
|
303 |
public void executeChecked(ItemEvent<Integer, ListMapItf<String, Tuple2<String, Boolean>>, SQLData> evt) {
|
|
|
304 |
cacheChanged(evt);
|
|
|
305 |
}
|
|
|
306 |
});
|
17 |
ilm |
307 |
}
|
|
|
308 |
|
144 |
ilm |
309 |
private void cacheChanged(final ItemEvent<Integer, ListMapItf<String, Tuple2<String, Boolean>>, ?> evt) {
|
|
|
310 |
if (evt.getPropertyName().equals(ICache.ITEM_ADDED)) {
|
|
|
311 |
synchronized (this.rights) {
|
|
|
312 |
this.rights.put(evt.getNewValue().getKey(), evt.getNewValue().getValue());
|
|
|
313 |
}
|
|
|
314 |
} else if (evt.getPropertyName().equals(ICache.ITEM_REMOVED)) {
|
|
|
315 |
final Integer userID = evt.getOldValue().getKey();
|
|
|
316 |
final RemovalType removalType = evt.getOldValue().getRemovalType();
|
|
|
317 |
if (removalType == RemovalType.DATA_CHANGE || removalType == RemovalType.TIMEOUT) {
|
|
|
318 |
invokeLater(new Callable<Object>() {
|
|
|
319 |
@Override
|
|
|
320 |
public Object call() throws Exception {
|
|
|
321 |
// we know the value is in the map, but we want to refresh it so
|
|
|
322 |
// don't use the map and only update the cache. Which would then
|
|
|
323 |
// trigger ICache.ITEM_ADDED.
|
|
|
324 |
getRightsForUser(userID.intValue(), false);
|
|
|
325 |
return null;
|
|
|
326 |
}
|
|
|
327 |
});
|
|
|
328 |
} else {
|
|
|
329 |
assert removalType != RemovalType.SIZE_LIMIT;
|
|
|
330 |
synchronized (this.rights) {
|
|
|
331 |
this.rights.remove(userID);
|
|
|
332 |
}
|
|
|
333 |
}
|
|
|
334 |
}
|
|
|
335 |
}
|
|
|
336 |
|
17 |
ilm |
337 |
/**
|
|
|
338 |
* enregistre les instances gérants les droits
|
|
|
339 |
*/
|
|
|
340 |
private void defaultRegister() {
|
73 |
ilm |
341 |
synchronized (DEFAULT_MACRO_RIGHTS) {
|
|
|
342 |
for (final MacroRight macroRight : DEFAULT_MACRO_RIGHTS) {
|
|
|
343 |
register(macroRight);
|
|
|
344 |
}
|
|
|
345 |
}
|
17 |
ilm |
346 |
}
|
|
|
347 |
|
|
|
348 |
/**
|
|
|
349 |
* Ajoute une instance pour la gestion d'un droit
|
|
|
350 |
*
|
|
|
351 |
* @param userRight the instance which will now be used for <code>userRight.getCode()</code>.
|
|
|
352 |
*/
|
|
|
353 |
public void register(final MacroRight userRight) {
|
144 |
ilm |
354 |
if (userRight == null)
|
|
|
355 |
throw new IllegalArgumentException("Missing right");
|
17 |
ilm |
356 |
this.macroRights.put(userRight.getCode(), userRight);
|
|
|
357 |
}
|
|
|
358 |
|
|
|
359 |
/**
|
80 |
ilm |
360 |
* Add an unconditional right for the passed user. I.e. it is loaded before the SQL ones and
|
|
|
361 |
* even default unconditional rights are before user SQL rights.
|
17 |
ilm |
362 |
*
|
|
|
363 |
* @param userID the user id, <code>null</code> meaning for everyone.
|
|
|
364 |
* @param right the right the user should always have.
|
|
|
365 |
*/
|
|
|
366 |
public void addRight(Integer userID, RightTuple right) {
|
144 |
ilm |
367 |
this.javaRights.changeRight(true, getKey(userID), right);
|
17 |
ilm |
368 |
}
|
|
|
369 |
|
80 |
ilm |
370 |
/**
|
|
|
371 |
* Add an unconditional right for administrators. This will be after user rights and before
|
|
|
372 |
* default rights.
|
|
|
373 |
*
|
|
|
374 |
* @param right the right to add.
|
|
|
375 |
*/
|
|
|
376 |
public void addRightForAdmins(RightTuple right) {
|
144 |
ilm |
377 |
this.javaRights.changeRight(true, ADMIN_ID, right);
|
80 |
ilm |
378 |
}
|
|
|
379 |
|
|
|
380 |
private final int getKey(final Integer userID) {
|
|
|
381 |
if (userID != null && userID < SQLRow.MIN_VALID_ID)
|
|
|
382 |
throw new IllegalArgumentException("invalid ID : " + userID);
|
|
|
383 |
return userID == null ? getDefaultUserId() : userID;
|
|
|
384 |
}
|
|
|
385 |
|
|
|
386 |
/**
|
|
|
387 |
* Remove a right.
|
|
|
388 |
*
|
|
|
389 |
* @param userID the user id, <code>null</code> meaning default user.
|
|
|
390 |
* @param right the right to remove, <code>null</code> meaning remove all.
|
|
|
391 |
* @see #addRight(Integer, RightTuple)
|
|
|
392 |
*/
|
|
|
393 |
public void removeRight(final Integer userID, final RightTuple right) {
|
144 |
ilm |
394 |
this.javaRights.changeRight(false, getKey(userID), right);
|
80 |
ilm |
395 |
}
|
|
|
396 |
|
|
|
397 |
public void removeRightForAdmins(final RightTuple right) {
|
144 |
ilm |
398 |
this.javaRights.changeRight(false, ADMIN_ID, right);
|
80 |
ilm |
399 |
}
|
|
|
400 |
|
73 |
ilm |
401 |
public synchronized final boolean isValid() {
|
144 |
ilm |
402 |
return !this.cache.getSupp().isDying();
|
17 |
ilm |
403 |
}
|
|
|
404 |
|
144 |
ilm |
405 |
@Override
|
73 |
ilm |
406 |
public synchronized final void destroy() {
|
|
|
407 |
if (this.isValid()) {
|
144 |
ilm |
408 |
this.cache.getSupp().die();
|
|
|
409 |
this.exec.shutdown();
|
73 |
ilm |
410 |
}
|
|
|
411 |
assert !this.isValid();
|
|
|
412 |
}
|
|
|
413 |
|
144 |
ilm |
414 |
@Override
|
17 |
ilm |
415 |
public final SQLTable getTable() {
|
|
|
416 |
return this.table;
|
|
|
417 |
}
|
|
|
418 |
|
|
|
419 |
public final DBRoot getRoot() {
|
|
|
420 |
return this.getTable().getDBRoot();
|
|
|
421 |
}
|
|
|
422 |
|
144 |
ilm |
423 |
private final SQLTable getUserTable() {
|
|
|
424 |
return this.toUserLink.getTarget();
|
|
|
425 |
}
|
|
|
426 |
|
|
|
427 |
public synchronized final <T> Future<T> invokeLater(final Callable<T> c) {
|
|
|
428 |
if (!this.isValid())
|
|
|
429 |
return null;
|
|
|
430 |
return this.exec.submit(c);
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
/**
|
|
|
434 |
* Block until all out of date rights are refreshed. Useful since <code>haveRight()</code> will
|
|
|
435 |
* return the last known rights while a refresh is ongoing.
|
|
|
436 |
*
|
|
|
437 |
* @return <code>true</code> if this method blocked, <code>false</code> if this was already
|
|
|
438 |
* {@link #isValid() invalid}.
|
|
|
439 |
* @throws InterruptedException if the current thread was interrupted while waiting.
|
|
|
440 |
*/
|
|
|
441 |
public final boolean waitForCurrentRefresh() throws InterruptedException {
|
|
|
442 |
try {
|
|
|
443 |
final Future<Object> f = this.invokeLater(IFutureTask.getNoOpCallable());
|
|
|
444 |
if (f == null)
|
|
|
445 |
return false;
|
|
|
446 |
f.get();
|
|
|
447 |
return true;
|
|
|
448 |
} catch (ExecutionException e) {
|
|
|
449 |
throw new IllegalStateException("No-op failed", e);
|
|
|
450 |
}
|
|
|
451 |
}
|
|
|
452 |
|
|
|
453 |
public final synchronized UserRights getUserRights(Integer userID) {
|
|
|
454 |
if (userID == null)
|
|
|
455 |
userID = this.getDefaultUserId();
|
|
|
456 |
UserRights res = this.userRights.get(userID);
|
|
|
457 |
if (res == null) {
|
|
|
458 |
res = new UserRights(this, userID);
|
|
|
459 |
this.userRights.put(userID, res);
|
|
|
460 |
}
|
|
|
461 |
return res;
|
|
|
462 |
}
|
|
|
463 |
|
17 |
ilm |
464 |
public final boolean haveRight(final int userID, final String code) {
|
|
|
465 |
return this.haveRight(userID, code, null);
|
|
|
466 |
}
|
|
|
467 |
|
41 |
ilm |
468 |
public final boolean haveRight(final int userID, final String code, final String object) {
|
|
|
469 |
return this.haveRight(userID, code, object, CompareUtils.OBJECT_EQ);
|
|
|
470 |
}
|
|
|
471 |
|
17 |
ilm |
472 |
/**
|
41 |
ilm |
473 |
* Whether <code>userID</code> should be allowed the <code>code</code> (e.g. DELETE) right on
|
|
|
474 |
* <code>object</code> (e.g. TENSION).<br>
|
17 |
ilm |
475 |
* The rights are ordered and the first one that matches is returned. Furthermore after
|
|
|
476 |
* searching for the passed <code>userID</code> the default user is searched. <br>
|
|
|
477 |
* To match, the code of the right must be equal to <code>code</code> and either the object of
|
41 |
ilm |
478 |
* the right is <code>null</code> or <code>objectMatcher</code> returns <code>true</code> when
|
|
|
479 |
* passed both objects. There's also a special case if <code>object</code> is <code>null</code>
|
|
|
480 |
* : in that case all found objects must be allowed until a right with a <code>null</code>
|
80 |
ilm |
481 |
* object for the right to be granted. With these rules setting the object of the right to
|
|
|
482 |
* <code>null</code> means giving the right to any object. And searching for the object
|
|
|
483 |
* <code>null</code> means asking if the right is allowed for all the objects. <br>
|
17 |
ilm |
484 |
* For example if you have these rights (* meaning <code>null</code>) :
|
|
|
485 |
* <ol>
|
|
|
486 |
* <li>del T yes</li>
|
|
|
487 |
* <li>ins T no</li>
|
|
|
488 |
* <li>del T no</li>
|
|
|
489 |
* <li>ins * yes</li>
|
|
|
490 |
* <li>del * yes</li>
|
|
|
491 |
* </ol>
|
80 |
ilm |
492 |
* then you can delete from T but not insert ; you can however do both on any other object. If
|
17 |
ilm |
493 |
* you pass <code>null</code> for <code>object</code>, it will return <code>true</code> for del,
|
|
|
494 |
* but <code>false</code> for ins.
|
|
|
495 |
*
|
|
|
496 |
* @param userID the user.
|
|
|
497 |
* @param code the requested right.
|
41 |
ilm |
498 |
* @param requestedObject the requested object, can be <code>null</code>.
|
|
|
499 |
* @param objectMatcher how to match objects, first parameter passed is the right object, the
|
|
|
500 |
* second is <code>requestedObject</code>.
|
17 |
ilm |
501 |
* @return <code>true</code> if the right is allowed.
|
|
|
502 |
*/
|
41 |
ilm |
503 |
public final boolean haveRight(final int userID, final String code, final String requestedObject, final Equalizer<? super String> objectMatcher) {
|
17 |
ilm |
504 |
final Set<String> unicity = new HashSet<String>();
|
41 |
ilm |
505 |
final Boolean userRight = haveRightP(userID, code, requestedObject, objectMatcher, unicity);
|
17 |
ilm |
506 |
if (userRight != null)
|
|
|
507 |
return userRight;
|
80 |
ilm |
508 |
final int defaultUser = getDefaultUserId();
|
|
|
509 |
if (defaultUser != userID) {
|
|
|
510 |
final Boolean defaultRight = haveRightP(defaultUser, code, requestedObject, objectMatcher, unicity);
|
|
|
511 |
if (defaultRight != null)
|
|
|
512 |
return defaultRight;
|
|
|
513 |
}
|
17 |
ilm |
514 |
|
|
|
515 |
return false;
|
|
|
516 |
}
|
|
|
517 |
|
41 |
ilm |
518 |
private final Boolean haveRightP(final int userID, final String code, final String object, final Equalizer<? super String> objectMatcher, Set<String> unicity) {
|
144 |
ilm |
519 |
final ListMapItf<String, Tuple2<String, Boolean>> rightsForUser = getRightsForUser(userID);
|
17 |
ilm |
520 |
// super-user
|
|
|
521 |
if (rightsForUser == SUPERUSER_RIGHTS)
|
|
|
522 |
return true;
|
41 |
ilm |
523 |
if (rightsForUser == NO_RIGHTS)
|
|
|
524 |
return false;
|
17 |
ilm |
525 |
|
|
|
526 |
if (rightsForUser.containsKey(code)) {
|
|
|
527 |
for (final Tuple2<String, Boolean> t : rightsForUser.getNonNull(code)) {
|
|
|
528 |
// as explained in expand() we need unicity for null object, we have it for each
|
|
|
529 |
// user, but we also need it between userID and undefinedID
|
|
|
530 |
if (unicity.add(t.get0())) {
|
|
|
531 |
// if the object of the right matches the requested object :
|
|
|
532 |
// null for the right matches any requested object
|
41 |
ilm |
533 |
// null for the requested object means searching for all objects so we can't let
|
|
|
534 |
// objectMatcher match and thus ignore subsequent objects
|
|
|
535 |
if (t.get0() == null || (object != null && safeEquals(objectMatcher, t, object)))
|
17 |
ilm |
536 |
return t.get1();
|
|
|
537 |
// but null for the requested object means that all right objects must be true
|
|
|
538 |
else if (object == null && !t.get1())
|
|
|
539 |
return false;
|
|
|
540 |
}
|
|
|
541 |
}
|
|
|
542 |
}
|
|
|
543 |
return null;
|
|
|
544 |
}
|
|
|
545 |
|
41 |
ilm |
546 |
private boolean safeEquals(final Equalizer<? super String> objectMatcher, final Tuple2<String, Boolean> t, final String requestedObject) {
|
|
|
547 |
final String rightObject = t.get0();
|
|
|
548 |
try {
|
|
|
549 |
return objectMatcher.equals(rightObject, requestedObject);
|
|
|
550 |
} catch (Exception e) {
|
|
|
551 |
// if the right could be allowed we don't match (so the row is ignored)
|
|
|
552 |
// if the right could be disallowed we match
|
|
|
553 |
final boolean res = !t.get1();
|
|
|
554 |
final String desc = !res ? "Row ignored." : "Right denied.";
|
|
|
555 |
Log.get().warning("Couldn't compare " + rightObject + " and " + requestedObject + ". " + desc);
|
|
|
556 |
e.printStackTrace();
|
|
|
557 |
return res;
|
|
|
558 |
}
|
|
|
559 |
}
|
|
|
560 |
|
144 |
ilm |
561 |
final Set<Integer> getNonBlockingUsers() {
|
17 |
ilm |
562 |
synchronized (this.rights) {
|
144 |
ilm |
563 |
return Collections.unmodifiableSet(new HashSet<Integer>(this.rights.keySet()));
|
17 |
ilm |
564 |
}
|
|
|
565 |
}
|
|
|
566 |
|
144 |
ilm |
567 |
private ListMapItf<String, Tuple2<String, Boolean>> getRightsForUser(final int userID) {
|
|
|
568 |
// MAYBE add an option to call waitForCurrentRefresh() here, to be sure rights are up to
|
|
|
569 |
// date
|
|
|
570 |
return this.getRightsForUser(userID, true);
|
|
|
571 |
}
|
|
|
572 |
|
|
|
573 |
private ListMapItf<String, Tuple2<String, Boolean>> getRightsForUser(final int userID, final boolean checkMap) {
|
|
|
574 |
if (checkMap) {
|
|
|
575 |
synchronized (this.rights) {
|
|
|
576 |
if (this.rights.containsKey(userID)) {
|
|
|
577 |
return this.rights.get(userID);
|
|
|
578 |
}
|
17 |
ilm |
579 |
}
|
|
|
580 |
}
|
144 |
ilm |
581 |
final Set<SQLData> data = new HashSet<SQLData>();
|
|
|
582 |
// for changes in admin/superuser for user
|
|
|
583 |
data.add(new SQLRow(getUserTable(), userID));
|
|
|
584 |
// for change in rights for user
|
|
|
585 |
data.add(getTable());
|
|
|
586 |
// for change in java rights (data is a Set : no need to check IDs)
|
|
|
587 |
data.add(new JavaRightsUser(this.javaRights, userID));
|
|
|
588 |
data.add(new JavaRightsUser(this.javaRights, ADMIN_ID));
|
|
|
589 |
data.add(new JavaRightsUser(this.javaRights, getDefaultUserId()));
|
|
|
590 |
final CacheResult<ListMapItf<String, Tuple2<String, Boolean>>> cached = this.cache.check(userID, data);
|
|
|
591 |
if (cached.getState() == CacheResult.State.INTERRUPTED)
|
|
|
592 |
throw new RTInterruptedException("interrupted while waiting for the cache");
|
|
|
593 |
else if (cached.getState() == CacheResult.State.VALID)
|
|
|
594 |
return cached.getRes();
|
|
|
595 |
|
|
|
596 |
final ListMapItf<String, Tuple2<String, Boolean>> res;
|
|
|
597 |
try {
|
|
|
598 |
res = loadRightsForUser(userID);
|
|
|
599 |
this.cache.put(cached, res);
|
|
|
600 |
} catch (RuntimeException exn) {
|
|
|
601 |
this.cache.removeRunning(cached);
|
|
|
602 |
throw exn;
|
|
|
603 |
}
|
|
|
604 |
assert res != null;
|
|
|
605 |
return res;
|
17 |
ilm |
606 |
}
|
|
|
607 |
|
|
|
608 |
/**
|
|
|
609 |
* Charge les droits définit dans la table USER_RIGHT.
|
|
|
610 |
*
|
|
|
611 |
* @param userID which user.
|
144 |
ilm |
612 |
* @return the immutable user's rights by CODE.
|
17 |
ilm |
613 |
*/
|
144 |
ilm |
614 |
private final ListMapItf<String, Tuple2<String, Boolean>> loadRightsForUser(final int userID) {
|
|
|
615 |
// snapshot of java rights
|
|
|
616 |
final ListMapItf<Integer, RightTuple> javaRights = this.javaRights.getTuples();
|
19 |
ilm |
617 |
try {
|
144 |
ilm |
618 |
// this method is called to fill our cache, so don't use the data source cache
|
|
|
619 |
// (SQLRowValuesListFetcher below never uses the cache)
|
|
|
620 |
final SQLRow userRow = new SQLRow(getUserTable(), userID).fetchValues(false);
|
73 |
ilm |
621 |
if (userRow != null && userRow.getBoolean(SUPERUSER_FIELD))
|
19 |
ilm |
622 |
return SUPERUSER_RIGHTS;
|
17 |
ilm |
623 |
|
83 |
ilm |
624 |
final ListMap<String, Tuple2<String, Boolean>> res = new ListMap<String, Tuple2<String, Boolean>>();
|
19 |
ilm |
625 |
final Set<Tuple2<String, String>> unicity = new HashSet<Tuple2<String, String>>();
|
|
|
626 |
// only superuser can modify RIGHTs
|
|
|
627 |
expand(res, unicity, TableAllRights.createRight(TableAllRights.CODE_MODIF, this.getTable().getForeignTable("ID_RIGHT"), false));
|
|
|
628 |
// only admin can modify or see USER_RIGHTs
|
80 |
ilm |
629 |
final boolean isAdmin = userRow != null && userRow.getBoolean(ADMIN_FIELD);
|
|
|
630 |
expand(res, unicity, TableAllRights.createRight(TableAllRights.CODE, this.getTable(), isAdmin));
|
17 |
ilm |
631 |
|
19 |
ilm |
632 |
// java rights have priority over SQL rights
|
144 |
ilm |
633 |
for (final RightTuple t : javaRights.getNonNull(userID)) {
|
19 |
ilm |
634 |
expand(res, unicity, t);
|
|
|
635 |
}
|
80 |
ilm |
636 |
// perhaps allow SQL to also specify admin rights
|
|
|
637 |
if (isAdmin) {
|
144 |
ilm |
638 |
for (final RightTuple t : javaRights.getNonNull(ADMIN_ID))
|
80 |
ilm |
639 |
expand(res, unicity, t);
|
19 |
ilm |
640 |
}
|
80 |
ilm |
641 |
final int defaultUser = getDefaultUserId();
|
|
|
642 |
if (defaultUser != userID) {
|
|
|
643 |
// even default java rights are before user SQL rights
|
144 |
ilm |
644 |
for (final RightTuple t : javaRights.getNonNull(defaultUser)) {
|
80 |
ilm |
645 |
expand(res, unicity, t);
|
|
|
646 |
}
|
|
|
647 |
}
|
17 |
ilm |
648 |
|
19 |
ilm |
649 |
final SQLRowValues vals = new SQLRowValues(getTable()).setAllToNull();
|
|
|
650 |
vals.putRowValues("ID_RIGHT").setAllToNull();
|
17 |
ilm |
651 |
|
19 |
ilm |
652 |
final SQLRowValuesListFetcher sel = new SQLRowValuesListFetcher(vals);
|
|
|
653 |
sel.setOrdered(true);
|
|
|
654 |
sel.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
|
|
|
655 |
@Override
|
|
|
656 |
public SQLSelect transformChecked(final SQLSelect sel) {
|
144 |
ilm |
657 |
sel.setWhere(new Where(UserRightsManager.this.toUserLink.getLabel(), "=", userID));
|
19 |
ilm |
658 |
return sel;
|
|
|
659 |
}
|
|
|
660 |
});
|
17 |
ilm |
661 |
|
19 |
ilm |
662 |
final List<SQLRowValues> list = sel.fetch();
|
|
|
663 |
for (final SQLRowValues row : list) {
|
|
|
664 |
final SQLRowAccessor right = row.getForeign("ID_RIGHT");
|
|
|
665 |
if (row.isUndefined()) {
|
|
|
666 |
Log.get().warning(row.asRow() + " has undef right");
|
|
|
667 |
} else {
|
|
|
668 |
final String rightCode = right.getString("CODE");
|
|
|
669 |
// do *not* load null code has it means SUPERUSER
|
|
|
670 |
if (rightCode == null)
|
|
|
671 |
Log.get().warning(right + " has null CODE");
|
|
|
672 |
else {
|
|
|
673 |
final String object = row.getString("OBJECT");
|
|
|
674 |
final Boolean haveRight = row.getBoolean("HAVE_RIGHT");
|
|
|
675 |
expand(res, unicity, rightCode, object, haveRight);
|
|
|
676 |
}
|
17 |
ilm |
677 |
}
|
|
|
678 |
}
|
19 |
ilm |
679 |
|
144 |
ilm |
680 |
return ListMap.unmodifiableMap(res);
|
19 |
ilm |
681 |
} catch (Exception e) {
|
|
|
682 |
ExceptionHandler.handle("Erreur lors du chargement des droits utilisateurs pour l'utilisateur (Id:" + userID + ")", e);
|
41 |
ilm |
683 |
return NO_RIGHTS;
|
17 |
ilm |
684 |
}
|
|
|
685 |
}
|
|
|
686 |
|
83 |
ilm |
687 |
private final void expand(final ListMap<String, Tuple2<String, Boolean>> res, final Set<Tuple2<String, String>> unicity, final RightTuple t) {
|
17 |
ilm |
688 |
this.expand(res, unicity, t.get0(), t.get1(), t.get2());
|
|
|
689 |
}
|
|
|
690 |
|
83 |
ilm |
691 |
private final void expand(final ListMap<String, Tuple2<String, Boolean>> res, final Set<Tuple2<String, String>> unicity, final String rightCode, final String object, final Boolean haveRight) {
|
17 |
ilm |
692 |
if (haveRight == null)
|
|
|
693 |
throw new IllegalStateException("HAVE_RIGHT cannot be null");
|
|
|
694 |
|
144 |
ilm |
695 |
// OK since macroRights doesn't contain null
|
|
|
696 |
final MacroRight macroRight = this.macroRights.get(rightCode);
|
|
|
697 |
if (macroRight != null) {
|
|
|
698 |
for (final RightTuple t : macroRight.expand(this, rightCode, object, haveRight)) {
|
17 |
ilm |
699 |
expand(res, unicity, t);
|
|
|
700 |
}
|
|
|
701 |
} else if (unicity.add(Tuple2.create(rightCode, object))) {
|
|
|
702 |
// we need to have unique rights, otherwise simple queries will still work since they
|
|
|
703 |
// will stop at the first match. But for queries with null object we need to traverse
|
|
|
704 |
// all rights.
|
83 |
ilm |
705 |
res.add(rightCode, Tuple2.create(object, haveRight));
|
17 |
ilm |
706 |
}
|
|
|
707 |
}
|
|
|
708 |
|
|
|
709 |
/**
|
|
|
710 |
* Return the list of objects the passed user is allowed for the passed code.
|
|
|
711 |
*
|
|
|
712 |
* @param userID the user.
|
|
|
713 |
* @param code the requested right.
|
|
|
714 |
* @param allObjects depending on the rights it might be necessary to know the full list of
|
|
|
715 |
* possible values.
|
|
|
716 |
* @return the allowed objects or <code>null</code> if they're all allowed.
|
|
|
717 |
*/
|
|
|
718 |
public final Set<String> getObjects(final int userID, final String code, final IFactory<Set<String>> allObjects) {
|
|
|
719 |
// test for everything to avoid calling allObjects
|
|
|
720 |
// (also takes care of superuser)
|
|
|
721 |
if (this.haveRight(userID, code))
|
|
|
722 |
return null;
|
|
|
723 |
|
41 |
ilm |
724 |
// the above line handles "* true", MAYBE we should search for e.g. "A false, * false"
|
17 |
ilm |
725 |
// and then return {}.
|
|
|
726 |
|
|
|
727 |
// try to add all objects which we are allowed to
|
41 |
ilm |
728 |
// but stop at the first null since it means we have to do a subtraction.
|
|
|
729 |
// (e.g. A f, * t)
|
17 |
ilm |
730 |
final Set<String> unicity = new HashSet<String>();
|
|
|
731 |
final Set<String> userRight = getObjectsP(userID, code, unicity);
|
|
|
732 |
if (userRight != null) {
|
65 |
ilm |
733 |
final Set<String> defaultRight = getObjectsP(getDefaultUserId(), code, unicity);
|
17 |
ilm |
734 |
if (defaultRight != null) {
|
|
|
735 |
userRight.addAll(defaultRight);
|
|
|
736 |
return userRight;
|
|
|
737 |
}
|
|
|
738 |
}
|
|
|
739 |
// there was at least one null
|
|
|
740 |
final Set<String> res = new HashSet<String>();
|
|
|
741 |
for (final String object : allObjects.createChecked()) {
|
|
|
742 |
if (this.haveRight(userID, code, object))
|
|
|
743 |
res.add(object);
|
|
|
744 |
}
|
|
|
745 |
return res;
|
|
|
746 |
}
|
|
|
747 |
|
144 |
ilm |
748 |
final int getDefaultUserId() {
|
|
|
749 |
return getUserTable().getUndefinedID();
|
65 |
ilm |
750 |
}
|
|
|
751 |
|
|
|
752 |
public void preloadRightsForUserId(int userID) {
|
|
|
753 |
getRightsForUser(getDefaultUserId());
|
|
|
754 |
getRightsForUser(userID);
|
|
|
755 |
}
|
|
|
756 |
|
144 |
ilm |
757 |
/**
|
|
|
758 |
* Invalidate all cached rights (the next call to {@link #haveRight(int, String)} will wait for
|
|
|
759 |
* the new rights from the DB).
|
|
|
760 |
*/
|
|
|
761 |
public void clearRights() {
|
|
|
762 |
// this will also clear this.rights, see cacheChanged()
|
|
|
763 |
this.cache.clear();
|
|
|
764 |
}
|
|
|
765 |
|
17 |
ilm |
766 |
private final Set<String> getObjectsP(final int userID, final String code, Set<String> unicity) {
|
144 |
ilm |
767 |
final ListMapItf<String, Tuple2<String, Boolean>> rightsForUser = getRightsForUser(userID);
|
41 |
ilm |
768 |
// don't let it proceed, otherwise it will then load objects for undef
|
|
|
769 |
if (rightsForUser == NO_RIGHTS)
|
|
|
770 |
return null;
|
17 |
ilm |
771 |
final Set<String> res = new HashSet<String>();
|
|
|
772 |
if (rightsForUser.containsKey(code)) {
|
|
|
773 |
for (final Tuple2<String, Boolean> t : rightsForUser.getNonNull(code)) {
|
|
|
774 |
// as usual don't let following rights overwrite preceding ones (ie if userID has
|
|
|
775 |
// "A false" and undef has "A true", then the second one should be ignored)
|
|
|
776 |
if (unicity.add(t.get0())) {
|
|
|
777 |
if (t.get0() == null)
|
|
|
778 |
return null;
|
|
|
779 |
else if (t.get1())
|
|
|
780 |
res.add(t.get0());
|
|
|
781 |
}
|
|
|
782 |
}
|
|
|
783 |
}
|
|
|
784 |
return res;
|
|
|
785 |
}
|
|
|
786 |
|
41 |
ilm |
787 |
public final Set<String> getObjects(final int userID, final String code, final Set<String> objectsToTest, final Equalizer<? super String> objectMatcher) {
|
|
|
788 |
// test for everything to avoid looping through potentially numerous allObjects
|
|
|
789 |
// (also takes care of superuser)
|
|
|
790 |
if (this.haveRight(userID, code, null, objectMatcher))
|
|
|
791 |
return objectsToTest;
|
|
|
792 |
|
|
|
793 |
// if userID hasn't the right to any object we can't try to list objects since in this case
|
|
|
794 |
// (with an Equalizer) the objects in the DB are more like patterns.
|
|
|
795 |
final Set<String> res = new HashSet<String>();
|
|
|
796 |
for (final String object : objectsToTest) {
|
|
|
797 |
if (this.haveRight(userID, code, object, objectMatcher))
|
|
|
798 |
res.add(object);
|
|
|
799 |
}
|
|
|
800 |
return res;
|
|
|
801 |
}
|
|
|
802 |
|
17 |
ilm |
803 |
static public final class RightTuple extends Tuple3<String, String, Boolean> {
|
|
|
804 |
public RightTuple(final String code, final boolean haveRight) {
|
|
|
805 |
this(code, null, haveRight);
|
|
|
806 |
}
|
|
|
807 |
|
|
|
808 |
public RightTuple(final String code, final String object, final boolean haveRight) {
|
|
|
809 |
super(code, object, haveRight);
|
|
|
810 |
}
|
|
|
811 |
}
|
|
|
812 |
}
|