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 org.apache.wiki.auth.GroupPrincipal; 022 023import java.security.Principal; 024import java.util.Date; 025import java.util.Vector; 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 Session'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 static final String[] RESTRICTED_GROUPNAMES = new String[] { "Anonymous", "All", "Asserted", "Authenticated" }; 063 064 private final Vector<Principal> m_members = new Vector<>(); 065 066 private String m_creator; 067 068 private Date m_created; 069 070 private String m_modifier; 071 072 private Date m_modified; 073 074 private final String m_name; 075 076 private final Principal m_principal; 077 078 private final String m_wiki; 079 080 /** 081 * Protected constructor to prevent direct instantiation except by other 082 * package members. Callers should use 083 * {@link GroupManager#parseGroup(String, String, boolean)} or 084 * {@link GroupManager#parseGroup(org.apache.wiki.api.core.Context, boolean)}. 085 * instead. 086 * @param name the name of the group 087 * @param wiki the wiki the group belongs to 088 */ 089 protected Group( final String name, final String wiki ) { 090 m_name = name; 091 m_wiki = wiki; 092 m_principal = new GroupPrincipal( name ); 093 } 094 095 /** 096 * Adds a Principal to the group. 097 * 098 * @param user the principal to add 099 * @return <code>true</code> if the operation was successful 100 */ 101 public synchronized boolean add( final Principal user ) { 102 if( isMember( user ) ) { 103 return false; 104 } 105 106 m_members.add( user ); 107 return true; 108 } 109 110 /** 111 * Clears all Principals from the group list. 112 */ 113 public synchronized void clear() { 114 m_members.clear(); 115 } 116 117 /** 118 * Two DefaultGroups are equal if they contain identical member Principals 119 * and have the same name. 120 * @param o the object to compare 121 * @return the comparison 122 */ 123 @Override 124 public boolean equals( final Object o ) { 125 if( !( o instanceof Group ) ) { 126 return false; 127 } 128 129 final Group g = ( Group )o; // Just a shortcut. 130 131 if( g.m_members.size() != m_members.size() ) { 132 return false; 133 } 134 135 if( getName() != null && !getName().equals( g.getName() ) ) { 136 return false; 137 } else if( getName() == null && g.getName() != null ) { 138 return false; 139 } 140 141 for( final Principal principal : m_members ) { 142 if( !g.isMember( principal ) ) { 143 return false; 144 } 145 } 146 147 return true; 148 } 149 150 /** 151 * The hashcode is calculated as a XOR sum over all members of the Group. 152 * 153 * @return the hash code 154 */ 155 @Override 156 public int hashCode() { 157 int hc = 0; 158 for( final Principal member : m_members ) { 159 hc ^= member.hashCode(); 160 } 161 return hc; 162 } 163 164 /** 165 * Returns the creation date. 166 * 167 * @return the creation date 168 */ 169 public synchronized Date getCreated() { 170 return m_created; 171 } 172 173 /** 174 * Returns the creator of this Group. 175 * 176 * @return the creator 177 */ 178 public final synchronized String getCreator() { 179 return m_creator; 180 } 181 182 /** 183 * Returns the last-modified date. 184 * 185 * @return the date and time of last modification 186 */ 187 public synchronized Date getLastModified() { 188 return m_modified; 189 } 190 191 /** 192 * Returns the name of the user who last modified this group. 193 * 194 * @return the modifier 195 */ 196 public final synchronized String getModifier() { 197 return m_modifier; 198 } 199 200 /** 201 * The name of the group. This is set in the class constructor. 202 * 203 * @return the name of the Group 204 */ 205 public String getName() { 206 return m_name; 207 } 208 209 /** 210 * Returns the GroupPrincipal that represents this Group. 211 * 212 * @return the group principal 213 */ 214 public Principal getPrincipal() { 215 return m_principal; 216 } 217 218 /** 219 * Returns the wiki name. 220 * 221 * @return the wiki name 222 */ 223 public String getWiki() { 224 return m_wiki; 225 } 226 227 /** 228 * Returns <code>true</code> if a Principal is a member of the group. Specifically, the Principal's <code>getName()</code> method must 229 * return the same value as one of the Principals in the group member list. The Principal's type does <em>not</em> need to match. 230 * 231 * @param principal the principal about whom membeship status is sought 232 * @return the result of the operation 233 */ 234 public boolean isMember( final Principal principal ) { 235 return findMember( principal.getName() ) != null; 236 } 237 238 /** 239 * Returns the members of the group as an array of Principal objects. 240 * 241 * @return the members 242 */ 243 public Principal[] members() { 244 return m_members.toArray( new Principal[0] ); 245 } 246 247 /** 248 * Removes a Principal from the group. 249 * 250 * @param user the principal to remove 251 * @return <code>true</code> if the operation was successful 252 */ 253 public synchronized boolean remove( Principal user ) { 254 user = findMember( user.getName() ); 255 if( user == null ) 256 return false; 257 258 m_members.remove( user ); 259 260 return true; 261 } 262 263 /** 264 * Sets the created date. 265 * 266 * @param date the creation date 267 */ 268 public synchronized void setCreated( final Date date ) { 269 m_created = date; 270 } 271 272 /** 273 * Sets the creator of this Group. 274 * @param creator the creator 275 */ 276 public final synchronized void setCreator( final String creator ) { 277 this.m_creator = creator; 278 } 279 280 /** 281 * Sets the last-modified date 282 * 283 * @param date the last-modified date 284 */ 285 public synchronized void setLastModified( final Date date ) { 286 m_modified = date; 287 } 288 289 /** 290 * Sets the name of the user who last modified this group. 291 * 292 * @param modifier the modifier 293 */ 294 public final synchronized void setModifier( final String modifier ) { 295 this.m_modifier = modifier; 296 } 297 298 /** 299 * Returns a string representation of the Group. 300 * 301 * @return the string 302 * @see java.lang.Object#toString() 303 */ 304 @Override 305 public String toString() { 306 return "(Group " + getName() + ")"; 307 } 308 309 private Principal findMember( final String name ) { 310 for( final Principal member : m_members ) { 311 if( member.getName().equals( name ) ) { 312 return member; 313 } 314 } 315 316 return null; 317 } 318 319}