Mercurial Hosting > nabble
annotate src/nabble/view/lib/Permissions.java @ 69:4bc1fc540265 default tip
update luan
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Sun, 05 Oct 2025 20:45:39 -0600 |
| parents | aba8ed4c8a06 |
| children |
| rev | line source |
|---|---|
| 0 | 1 package nabble.view.lib; |
| 2 | |
| 3 import fschmidt.db.Listener; | |
| 4 import fschmidt.db.ListenerList; | |
| 5 import fschmidt.util.java.Filter; | |
| 6 import nabble.model.Db; | |
| 7 import nabble.model.Init; | |
| 8 import nabble.model.Node; | |
| 9 import nabble.model.Person; | |
| 10 import nabble.model.Site; | |
| 11 import nabble.model.User; | |
| 12 | |
| 13 import java.util.ArrayList; | |
| 14 import java.util.Collections; | |
| 15 import java.util.List; | |
| 16 import java.util.Set; | |
| 17 | |
| 18 | |
| 19 public final class Permissions { | |
| 20 private Permissions() {} // never | |
| 21 | |
| 22 private static String encode(String s) { | |
| 23 StringBuilder buf = new StringBuilder(); | |
| 24 int n = s.length(); | |
| 25 for( int i=0; i<n; i++ ) { | |
| 26 char c = s.charAt(i); | |
| 27 if( c == '\'' ) | |
| 28 buf.append('\''); | |
| 29 buf.append(c); | |
| 30 } | |
| 31 return buf.toString(); | |
| 32 } | |
| 33 | |
| 34 private static void dbCheck(Site site) { | |
| 35 if( !site.getDb().isInTransaction() ) | |
| 36 throw new RuntimeException("not in transaction"); | |
| 37 } | |
| 38 | |
| 39 public static boolean isInGroup(User user,String group) { | |
| 40 return user.getSite().hasTags(null,user, | |
| 41 "label='group:" + encode(group) + "'" | |
| 42 ); | |
| 43 } | |
| 44 | |
| 45 public static void addToGroup(User user,String group) { | |
| 46 Site site = user.getSite(); | |
| 47 dbCheck(site); | |
| 48 site.addTag( null, user, "group:" + group ); | |
| 49 groupChangeListeners.event(site); | |
| 50 } | |
| 51 | |
| 52 public static void removeFromGroup(User user,String group) { | |
| 53 Site site = user.getSite(); | |
| 54 dbCheck(site); | |
| 55 user.getSite().deleteTags(null,user, | |
| 56 "label='group:" + encode(group) + "'" | |
| 57 ); | |
| 58 groupChangeListeners.event(site); | |
| 59 } | |
| 60 | |
| 61 public static void removeGroup(Site site,String group) { | |
| 62 site.deleteTags( | |
| 63 "node_id is null and user_id is not null and label='group:" + encode(group) + "'" | |
| 64 ); | |
| 65 groupChangeListeners.event(site); | |
| 66 } | |
| 67 | |
| 68 public static void removeGroups(User user) { | |
| 69 user.getSite().deleteTags(null,user, | |
| 70 "label like 'group:%'" | |
| 71 ); | |
| 72 groupChangeListeners.event(user.getSite()); | |
| 73 } | |
| 74 | |
| 75 private static List<String> getGroups(Site site,String sqlCondition) { | |
| 76 List<String> list = new ArrayList<String>(); | |
| 77 for( String label : site.findTagLabels(sqlCondition) ) { | |
| 78 list.add( label.substring(6) ); | |
| 79 } | |
| 80 return list; | |
| 81 } | |
| 82 | |
| 83 public static List<String> getGroups(User user) { | |
| 84 return getGroups( user.getSite(), | |
| 85 "node_id is null and user_id=" + user.getId() + " and label like 'group:%'" | |
| 86 ); | |
| 87 } | |
| 88 | |
| 89 public static List<String> getGroups(Site site) { | |
| 90 return getGroups( site, | |
| 91 "node_id is null and user_id is not null and label like 'group:%'" | |
| 92 ); | |
| 93 } | |
| 94 | |
| 95 public static List<User> getUsersInGroup(Site site,String group) { | |
| 96 return site.findTagUsers( | |
| 97 "node_id is null and user_id is not null and label='group:" + encode(group) + "'" | |
| 98 ); | |
| 99 } | |
| 100 | |
| 101 public static void addPermission(Node node,String permission) { | |
| 102 Site site = node.getSite(); | |
| 103 dbCheck(site); | |
| 104 site.addTag( node, null, "permission:" + permission ); | |
| 105 permissionChangeListeners.event(site); | |
| 106 } | |
| 107 | |
| 108 public static void addPermission(Node node,String permission,String group) { | |
| 109 Site site = node.getSite(); | |
| 110 dbCheck(site); | |
| 111 if( !nodeHasPermission(node,permission) ) | |
| 112 addPermission(node,permission); | |
| 113 site.addTag( node, null, "permission:" + permission + ":" + group ); | |
| 114 permissionChangeListeners.event(site); | |
| 115 } | |
| 116 | |
| 117 public static void addPermission(Site site,String permission,String group) { | |
| 118 dbCheck(site); | |
| 119 if( !siteHasPermission(site,permission) ) | |
| 120 site.addTag( null, null, "permission:" + permission ); | |
| 121 site.addTag( null, null, "permission:" + permission + ":" + group ); | |
| 122 permissionChangeListeners.event(site); | |
| 123 } | |
| 124 | |
| 125 public static void removePermission(Node node,String permission,String group) { | |
| 126 Site site = node.getSite(); | |
| 127 dbCheck(site); | |
| 128 site.deleteTags(node,null, | |
| 129 "label='permission:" + encode(permission) + ":" + encode(group) + "'" | |
| 130 ); | |
| 131 permissionChangeListeners.event(site); | |
| 132 } | |
| 133 | |
| 134 public static void removePermission(Node node,String permission) { | |
| 135 Site site = node.getSite(); | |
| 136 site.deleteTags(node,null, | |
| 137 "(label = 'permission:" + encode(permission) + "'" | |
| 138 + " or label like 'permission:" + encode(permission) + ":%')" | |
| 139 ); | |
| 140 permissionChangeListeners.event(site); | |
| 141 } | |
| 142 | |
| 143 public static void removePermissions(Node node) { | |
| 144 Site site = node.getSite(); | |
| 145 dbCheck(site); | |
| 146 site.deleteTags(node,null, | |
| 147 "label like 'permission:%'" | |
| 148 ); | |
| 149 permissionChangeListeners.event(site); | |
| 150 } | |
| 151 | |
| 152 public static boolean isPermissionVersion(Site site,String version) { | |
| 153 return site.hasTags(null,null, | |
| 154 "label='permission-version:" + version + "'" | |
| 155 ); | |
| 156 } | |
| 157 | |
| 158 public static void setPermissionVersion(Site site,String version) { | |
| 159 dbCheck(site); | |
| 160 deletePermissionVersion(site); | |
| 161 site.addTag( null, null, "permission-version:" + version ); | |
| 162 } | |
| 163 | |
| 164 public static void deletePermissionVersion(Site site) { | |
| 165 site.deleteTags(null,null, | |
| 166 "(label like 'permission:%' or label like 'site_default_permission:%' or label like 'permission-version:%')" | |
| 167 ); | |
| 168 } | |
| 169 | |
| 170 private static String query(Node node) { | |
| 171 return node==null ? "node_id is null" : "node_id=" + node.getId(); | |
| 172 } | |
| 173 | |
| 174 private static boolean siteHasPermission(Site site,String permission) { | |
| 175 return site.hasTags(null,null, | |
| 176 "label='permission:" + encode(permission) + "'" | |
| 177 ); | |
| 178 } | |
| 179 | |
| 180 public static boolean nodeHasPermission(Node node,String permission) { | |
| 181 return node.getSite().hasTags(node,null, | |
| 182 "label='permission:" + encode(permission) + "'" | |
| 183 ); | |
| 184 } | |
| 185 | |
| 186 public static Node getPermissionNode(Node node,String permission) { | |
| 187 for( Node n : node.getAncestors() ) { | |
| 188 if( nodeHasPermission(n,permission) ) | |
| 189 return n; | |
| 190 } | |
| 191 return null; | |
| 192 } | |
| 193 | |
| 194 public static List<String> getGroupsWithPermission(Node node,String permission) { | |
| 195 Site site = node.getSite(); | |
| 196 node = getPermissionNode(node,permission); | |
| 197 if( node == null && !siteHasPermission(site,permission) ) | |
| 198 return Collections.emptyList(); | |
| 199 List<String> list = new ArrayList<String>(); | |
| 200 String labelStart = "permission:" + encode(permission) + ":"; | |
| 201 int i = labelStart.length(); | |
| 202 for( String label : site.findTagLabels( | |
| 203 query(node) + " and user_id is null and label like '" + labelStart + "%'" | |
| 204 ) ) { | |
| 205 list.add( label.substring(i) ); | |
| 206 } | |
| 207 return list; | |
| 208 } | |
| 209 | |
| 210 public static boolean hasGroupsWithPermission(Node node,String permission) { | |
| 211 Site site = node.getSite(); | |
| 212 node = getPermissionNode(node,permission); | |
| 213 if( node == null && !siteHasPermission(site,permission) ) | |
| 214 return false; | |
| 215 return site.hasTags(node,null, | |
| 216 "label like 'permission:" + encode(permission) + ":%'" | |
| 217 ); | |
| 218 } | |
| 219 | |
| 220 public static final String ANYONE_GROUP = "Anyone"; | |
| 221 public static final String AUTHOR_GROUP = "Authors"; | |
| 222 public static final String ADMINISTRATORS_GROUP = "Administrators"; | |
| 223 | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
224 public static List<String> getPersonGroups(User user) { |
|
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
225 if( user==null || !user.isRegistered() ) |
|
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
226 return new ArrayList<String>(); |
|
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
227 List<String> groups = getGroups(user); |
| 0 | 228 groups.add(ANYONE_GROUP); |
| 229 return groups; | |
| 230 } | |
| 231 | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
232 public static boolean hasPermission(Node permissionNode,Node targetNode,User user,String permission) { |
| 0 | 233 Person owner = targetNode.getOwner(); |
| 234 Site site = permissionNode.getSite(); | |
| 235 permissionNode = getPermissionNode(permissionNode,permission); | |
| 236 if( permissionNode == null && !siteHasPermission(site,permission) ) | |
| 237 return false; | |
| 238 String s = "label='permission:" + encode(permission) + ":"; | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
239 List<String> groups = getPersonGroups(user); |
|
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
240 if( owner.equals(user) ) |
| 0 | 241 groups.add(AUTHOR_GROUP); |
| 242 for( String group : groups ) { | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
243 if( site.hasTags(permissionNode,null, s + encode(group) + "'" ) ) { |
| 0 | 244 return true; |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
245 } |
| 0 | 246 } |
| 247 return false; | |
| 248 } | |
| 249 | |
| 250 public static boolean hasPermission(Node node,String group,String permission) { | |
| 251 Site site = node.getSite(); | |
| 252 node = getPermissionNode(node,permission); | |
| 253 if( node == null && !siteHasPermission(site,permission) ) | |
| 254 return false; | |
| 255 return hasPermission(site,node,group,permission); | |
| 256 } | |
| 257 | |
| 258 private static boolean hasPermission(Site site,Node node,String group,String permission) { | |
| 259 return site.hasTags(node,null, | |
| 260 "label='permission:" + encode(permission) + ":" + encode(group) + "'" | |
| 261 ); | |
| 262 } | |
| 263 | |
| 264 public static boolean hasPermission(Site site,String group,String permission) { | |
| 265 return siteHasPermission(site,permission) && site.hasTags(null,null, | |
| 266 "label='permission:" + encode(permission) + ":" + encode(group) + "'" | |
| 267 ); | |
| 268 } | |
| 269 | |
| 270 public static List<User> getUsersWithPermission(Node node,String permission) { | |
| 271 Site site = node.getSite(); | |
| 272 node = getPermissionNode(node,permission); | |
| 273 if( node == null && !siteHasPermission(site,permission) ) | |
| 274 return Collections.emptyList(); | |
| 275 if (hasPermission(site, node, ANYONE_GROUP,permission)) | |
| 276 return site.getUsers("registered is not null"); | |
| 277 String labelStart = "permission:" + encode(permission) + ":"; | |
| 278 int i = labelStart.length(); | |
| 279 return site.findTagUsers( | |
| 280 "node_id is null and user_id is not null and label in (" | |
| 281 + "select 'group:' || substring(label," + (i+1) + ") from tag where " + query(node) + " and user_id is null and label like '" + labelStart + "%'" | |
| 282 +")" | |
| 283 ); | |
| 284 } | |
| 285 | |
| 286 | |
| 287 public static final String VIEW_PERMISSION = "View"; | |
| 288 | |
| 289 public static boolean isPrivate(Node node) { | |
| 290 return getPrivateNode(node) != null; | |
| 291 } | |
| 292 | |
| 293 public static boolean canBeViewedByParentViewers(Node node) { | |
| 294 if( node.getKind() != Node.Kind.APP ) | |
| 295 return true; | |
| 296 if( !nodeHasPermission(node,VIEW_PERMISSION) ) | |
| 297 return true; | |
| 298 Node parent = node.getParent(); | |
| 299 if( parent != null ) | |
| 300 parent = getPrivateNode(parent); | |
| 301 if( parent == null && !siteHasPermission(node.getSite(),VIEW_PERMISSION) ) | |
| 302 return !isPrivate(node); | |
| 303 return !node.getSite().hasTags(parent,null, | |
| 304 "label like 'permission:View:%'" | |
| 305 +" and label not in (select label from tag where node_id=" + node.getId() + " and user_id is null and label like 'permission:View:%')" | |
| 306 ); | |
| 307 } | |
| 308 | |
| 309 public static final Filter<Node> canBeViewedByParentViewersFilter = new Filter<Node>() { | |
| 310 public boolean ok(Node node) { | |
| 311 return canBeViewedByParentViewers(node); | |
| 312 } | |
| 313 }; | |
| 314 | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
315 public static boolean canBeViewedByPerson(Node node,User user) { |
|
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
316 if( user != null ) { |
| 0 | 317 if( isSysAdmin(user) ) |
| 318 return true; | |
| 319 if( isInGroup(user,ADMINISTRATORS_GROUP) ) | |
| 320 return true; | |
| 321 } | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
322 if( node.getSite().getRootNode().getOwner().equals(user) ) |
| 0 | 323 return true; |
| 324 Node permissionNode = node.getApp(); | |
| 325 if( permissionNode==null ) | |
| 326 permissionNode = node.getSite().getRootNode(); | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
327 return hasPermission(permissionNode,node,user,VIEW_PERMISSION); |
| 0 | 328 } |
| 329 | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
330 public static final Filter<Node> canBeViewedByPersonFilter(final User user) { |
| 0 | 331 return new Filter<Node>() { |
| 332 public boolean ok(Node node) { | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
333 return canBeViewedByPerson(node,user); |
| 0 | 334 } |
| 335 }; | |
| 336 } | |
| 337 | |
| 338 public static Node getPrivateNode(Node node) { | |
| 339 node = getPermissionNode(node,VIEW_PERMISSION); | |
| 340 return node==null || hasPermission(node.getSite(),node,ANYONE_GROUP,VIEW_PERMISSION) ? null : node; | |
| 341 } | |
| 342 | |
| 343 public static Node getPrivateNodeForSearch(Node node) { | |
| 344 node = getPrivateNode(node); | |
| 345 while( node != null && canBeViewedByParentViewers(node) ) { | |
| 346 node = getPrivateNode(node.getParent()); | |
| 347 } | |
| 348 return node; | |
| 349 } | |
| 350 | |
| 351 // Banning ------------------------------------------------------ | |
| 352 | |
| 353 public static boolean isBanned(User user) { | |
| 354 return user.getSite().hasTags(null,user,"label='banned'"); | |
| 355 } | |
| 356 | |
| 357 public static void ban(User user) { | |
| 358 user.getSite().addTag(null, user, "banned"); | |
| 359 } | |
| 360 | |
| 361 public static void unban(User user) { | |
| 362 user.getSite().deleteTags(null,user,"label='banned'"); | |
| 363 } | |
| 364 | |
| 365 public static List<User> getBannedUsers(Site site) { | |
| 366 return site.findTagUsers( | |
| 367 "node_id is null and user_id is not null and label='banned'" | |
| 368 ); | |
| 369 } | |
| 370 | |
| 371 | |
| 372 | |
| 373 private static final Set<String> sysadmins = Init.get("sysadmins",Collections.<String>emptySet()); | |
| 374 | |
| 375 public static boolean isSysAdmin(User user) { | |
| 376 return sysadmins.contains(user.getEmail()); | |
| 377 } | |
| 378 | |
| 379 | |
| 380 // site permissions | |
| 381 | |
| 382 public static void addSitePermission(Site site,String permission) { | |
| 383 dbCheck(site); | |
| 384 site.addTag( null, null, "site_permission:" + permission ); | |
| 385 } | |
| 386 | |
| 387 public static void addSiteDefaultPermission(Site site,String permission) { | |
| 388 dbCheck(site); | |
| 389 site.addTag( null, null, "site_default_permission:" + permission ); | |
| 390 } | |
| 391 | |
| 392 private static boolean hasSitePermission(Site site,String permission) { | |
| 393 return site.hasTags(null,null, | |
| 394 "label='site_permission:" + encode(permission) + "'" | |
| 395 ); | |
| 396 } | |
| 397 | |
| 398 public static boolean siteHasSitePermission(Site site,String permission) { | |
| 399 return hasSitePermission(site,permission); | |
| 400 } | |
| 401 | |
| 402 private static boolean hasSiteDefaultPermission(Site site,String permission) { | |
| 403 return site.hasTags(null,null, | |
| 404 "label='site_default_permission:" + encode(permission) + "'" | |
| 405 ); | |
| 406 } | |
| 407 | |
| 408 public static void addSitePermission(Site site,String permission,String group) { | |
| 409 dbCheck(site); | |
| 410 if( !hasSitePermission(site,permission) ) | |
| 411 site.addTag( null, null, "site_permission:" + permission ); | |
| 412 site.addTag( null, null, "site_permission:" + permission + ":" + group ); | |
| 413 } | |
| 414 | |
| 415 public static void addSiteDefaultPermission(Site site,String permission,String group) { | |
| 416 dbCheck(site); | |
| 417 if( !hasSiteDefaultPermission(site,permission) ) | |
| 418 site.addTag( null, null, "site_default_permission:" + permission ); | |
| 419 site.addTag( null, null, "site_default_permission:" + permission + ":" + group ); | |
| 420 } | |
| 421 | |
| 422 public static void removeSitePermission(Site site,String permission,String group) { | |
| 423 dbCheck(site); | |
| 424 site.deleteTags(null,null, | |
| 425 "label='site_permission:" + encode(permission) + ":" + encode(group) + "'" | |
| 426 ); | |
| 427 } | |
| 428 | |
| 429 public static void removeSitePermission(Site site,String permission) { | |
| 430 site.deleteTags(null,null, | |
| 431 "(label = 'site_permission:" + encode(permission) + "'" | |
| 432 + " or label like 'site_permission:" + encode(permission) + ":%')" | |
| 433 ); | |
| 434 } | |
| 435 | |
| 436 public static void removeSitePermissions(Site site) { | |
| 437 dbCheck(site); | |
| 438 site.deleteTags(null,null, | |
| 439 "label like 'site_permission:%'" | |
| 440 ); | |
| 441 } | |
| 442 | |
| 443 private static String sitePermissionLabel(Site site,String permission) { | |
| 444 if( hasSitePermission(site,permission) ) | |
| 445 return "site_permission:" + encode(permission); | |
| 446 else if( hasSiteDefaultPermission(site,permission) ) | |
| 447 return "site_default_permission:" + encode(permission); | |
| 448 else | |
| 449 return null; | |
| 450 } | |
| 451 | |
| 452 public static List<String> getGroupsWithSitePermission(Site site,String permission) { | |
| 453 String labelStart = sitePermissionLabel(site,permission); | |
| 454 if( labelStart == null ) | |
| 455 return Collections.emptyList(); | |
| 456 List<String> list = new ArrayList<String>(); | |
| 457 int i = labelStart.length(); | |
| 458 for( String label : site.findTagLabels( | |
| 459 "node_id is null and user_id is null and label like '" + labelStart + ":%'" | |
| 460 ) ) { | |
| 461 list.add( label.substring(i) ); | |
| 462 } | |
| 463 return list; | |
| 464 } | |
| 465 | |
| 466 public static boolean hasGroupsWithSitePermission(Site site,String permission) { | |
| 467 String labelStart = sitePermissionLabel(site,permission); | |
| 468 if( labelStart == null ) | |
| 469 return false; | |
| 470 return site.hasTags(null,null, | |
| 471 "label like '" + labelStart + ":%'" | |
| 472 ); | |
| 473 } | |
| 474 | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
475 public static boolean hasSitePermission(Site site,User user,String permission) { |
| 0 | 476 String labelStart = sitePermissionLabel(site,permission); |
| 477 if( labelStart == null ) | |
| 478 return false; | |
| 479 String s = "label='" + labelStart + ":"; | |
|
19
18cf4872fd7f
remove anonymous posting
Franklin Schmidt <fschmidt@gmail.com>
parents:
0
diff
changeset
|
480 List<String> groups = getPersonGroups(user); |
| 0 | 481 for( String group : groups ) { |
| 482 if( site.hasTags(null,null, s + encode(group) + "'" ) ) | |
| 483 return true; | |
| 484 } | |
| 485 return false; | |
| 486 } | |
| 487 | |
| 488 public static boolean hasSitePermission(Site site,String group,String permission) { | |
| 489 String labelStart = sitePermissionLabel(site,permission); | |
| 490 if( labelStart == null ) | |
| 491 return false; | |
| 492 return site.hasTags(null,null, | |
| 493 "label='" + labelStart + ":" + encode(group) + "'" | |
| 494 ); | |
| 495 } | |
| 496 | |
| 497 public static boolean hasSiteDefaultPermission(Site site,String group,String permission) { | |
| 498 return hasSiteDefaultPermission(site,permission) && site.hasTags(null,null, | |
| 499 "label='site_default_permission:" + encode(permission) + ":" + encode(group) + "'" | |
| 500 ); | |
| 501 } | |
| 502 | |
| 503 private static final ListenerList<Site> groupChangeListeners = new ListenerList<Site>(); | |
| 504 | |
| 505 public static void addGroupChangeListener(final Listener<Site> listener) { | |
| 506 groupChangeListeners.add(listener); | |
| 507 } | |
| 508 | |
| 509 private static final ListenerList<Site> permissionChangeListeners = new ListenerList<Site>(); | |
| 510 | |
| 511 public static void addPermissionChangeListener(final Listener<Site> listener) { | |
| 512 permissionChangeListeners.add(listener); | |
| 513 } | |
| 514 | |
| 515 } |
