001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.auth.authorize; 020 021import java.security.Principal; 022import java.util.Date; 023import java.util.Vector; 024 025import org.apache.wiki.auth.GroupPrincipal; 026 027/** 028 * <p> 029 * Groups are a specialized type of ad-hoc role used by the wiki system. Unlike 030 * externally-provided roles (such as those provided by an LDAP server or web 031 * container), JSPWiki groups can be created dynamically by wiki users, without 032 * requiring special container privileges or administrator intervention. They 033 * are designed to provide a lightweight role-based access control system that 034 * complements existing role systems. 035 * </p> 036 * <p> 037 * Group names are case-insensitive, and have a few naming restrictions, which 038 * are enforced by the {@link GroupManager}: 039 * </p> 040 * <ul> 041 * <li>Groups cannot have the same name as a built-in Role (e.g., "Admin", 042 * "Authenticated" etc.)</li> 043 * <li>Groups cannot have the same name as an existing user</li> 044 * </ul> 045 * <p> 046 * <em>Note: prior to JSPWiki 2.4.19, Group was an interface; it 047 * is now a concrete, final class.</em> 048 * </p> 049 * <p> 050 * Groups are related to {@link GroupPrincipal}s. A GroupPrincipal, when 051 * injected into the Principal set of a WikiSession's Subject, means that the 052 * user is a member of a Group of the same name -- it is, in essence, an 053 * "authorization token." GroupPrincipals, unlike Groups, are thread-safe, 054 * lightweight and immutable. That's why we use them in Subjects rather than the 055 * Groups themselves. 056 * </p> 057 * 058 * @since 2.3 059 */ 060public class Group 061{ 062 063 static final String[] RESTRICTED_GROUPNAMES = new String[] 064 { "Anonymous", "All", "Asserted", "Authenticated" }; 065 066 private final Vector<Principal> m_members = new Vector<Principal>(); 067 068 private String m_creator = null; 069 070 private Date m_created = null; 071 072 private String m_modifier = null; 073 074 private Date m_modified = null; 075 076 private final String m_name; 077 078 private final Principal m_principal; 079 080 private final String m_wiki; 081 082 /** 083 * Protected constructor to prevent direct instantiation except by other 084 * package members. Callers should use 085 * {@link GroupManager#parseGroup(String, String, boolean)} or 086 * {@link GroupManager#parseGroup(org.apache.wiki.WikiContext, boolean)}. 087 * instead. 088 * @param name the name of the group 089 * @param wiki the wiki the group belongs to 090 */ 091 protected Group( String name, String wiki ) 092 { 093 m_name = name; 094 m_wiki = wiki; 095 m_principal = new GroupPrincipal( name ); 096 } 097 098 /** 099 * Adds a Principal to the group. 100 * 101 * @param user the principal to add 102 * @return <code>true</code> if the operation was successful 103 */ 104 public synchronized boolean add( Principal user ) 105 { 106 if ( isMember( user ) ) 107 { 108 return false; 109 } 110 111 m_members.add( user ); 112 return true; 113 } 114 115 /** 116 * Clears all Principals from the group list. 117 */ 118 public synchronized void clear() 119 { 120 m_members.clear(); 121 } 122 123 /** 124 * Two DefaultGroups are equal if they contain identical member Principals 125 * and have the same name. 126 * @param o the object to compare 127 * @return the comparison 128 */ 129 public boolean equals( Object o ) 130 { 131 if ( o == null || !( o instanceof Group ) ) 132 return false; 133 134 Group g = (Group) o; // Just a shortcut. 135 136 if ( g.m_members.size() != m_members.size() ) 137 return false; 138 139 if ( getName() != null && !getName().equals( g.getName() ) ) 140 { 141 return false; 142 } 143 else if ( getName() == null && g.getName() != null ) 144 { 145 return false; 146 } 147 148 for( Principal principal : m_members ) 149 { 150 if ( !g.isMember( principal ) ) 151 { 152 return false; 153 } 154 } 155 156 return true; 157 } 158 159 /** 160 * The hashcode is calculated as a XOR sum over all members of 161 * the Group. 162 * @return the hash code 163 */ 164 public int hashCode() 165 { 166 int hc = 0; 167 for( Principal member : m_members ) 168 { 169 hc ^= member.hashCode(); 170 } 171 return hc; 172 } 173 174 /** 175 * Returns the creation date. 176 * @return the creation date 177 */ 178 public synchronized Date getCreated() 179 { 180 return m_created; 181 } 182 183 /** 184 * Returns the creator of this Group. 185 * @return the creator 186 */ 187 public final synchronized String getCreator() 188 { 189 return m_creator; 190 } 191 192 /** 193 * Returns the last-modified date. 194 * @return the date and time of last modification 195 */ 196 public synchronized Date getLastModified() 197 { 198 return m_modified; 199 } 200 201 /** 202 * Returns the name of the user who last modified this group. 203 * @return the modifier 204 */ 205 public final synchronized String getModifier() 206 { 207 return m_modifier; 208 } 209 210 /** 211 * The name of the group. This is set in the class constructor. 212 * @return the name of the Group 213 */ 214 public String getName() 215 { 216 return m_name; 217 } 218 219 /** 220 * Returns the GroupPrincipal that represents this Group. 221 * @return the group principal 222 */ 223 public Principal getPrincipal() 224 { 225 return m_principal; 226 } 227 228 /** 229 * Returns the wiki name. 230 * @return the wiki name 231 */ 232 public String getWiki() 233 { 234 return m_wiki; 235 } 236 237 /** 238 * Returns <code>true</code> if a Principal is a member of the group. 239 * Specifically, the Principal's <code>getName()</code> method must return 240 * the same value as one of the Principals in the group member list. The 241 * Principal's type does <em>not</em> need to match. 242 * @param principal the principal about whom membeship status is sought 243 * @return the result of the operation 244 */ 245 public boolean isMember( Principal principal ) 246 { 247 return findMember( principal.getName() ) != null; 248 } 249 250 /** 251 * Returns the members of the group as an array of Principal objects. 252 * @return the members 253 */ 254 public Principal[] members() 255 { 256 return m_members.toArray( new Principal[m_members.size()] ); 257 } 258 259 /** 260 * Removes a Principal from the group. 261 * 262 * @param user the principal to remove 263 * @return <code>true</code> if the operation was successful 264 */ 265 public synchronized boolean remove( Principal user ) 266 { 267 user = findMember( user.getName() ); 268 269 if ( user == null ) 270 return false; 271 272 m_members.remove( user ); 273 274 return true; 275 } 276 277 /** 278 * Sets the created date. 279 * @param date the creation date 280 */ 281 public synchronized void setCreated( Date date ) 282 { 283 m_created = date; 284 } 285 286 /** 287 * Sets the creator of this Group. 288 * @param creator the creator 289 */ 290 public final synchronized void setCreator( String creator ) 291 { 292 this.m_creator = creator; 293 } 294 295 /** 296 * Sets the last-modified date 297 * @param date the last-modified date 298 */ 299 public synchronized void setLastModified( Date date ) 300 { 301 m_modified = date; 302 } 303 304 /** 305 * Sets the name of the user who last modified this group. 306 * @param modifier the modifier 307 */ 308 public final synchronized void setModifier( String modifier ) 309 { 310 this.m_modifier = modifier; 311 } 312 313 /** 314 * Returns a string representation of the Group. 315 * @return the string 316 * @see java.lang.Object#toString() 317 */ 318 public String toString() 319 { 320 StringBuilder sb = new StringBuilder(); 321 sb.append( "(Group " + getName() + ")" ); 322 return sb.toString(); 323 } 324 325 private Principal findMember( String name ) 326 { 327 for( Principal member : m_members ) 328 { 329 if ( member.getName().equals( name ) ) 330 { 331 return member; 332 } 333 } 334 335 return null; 336 } 337 338}