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 return m_members.stream().allMatch(g::isMember); 142 } 143 144 /** 145 * The hashcode is calculated as a XOR sum over all members of the Group. 146 * 147 * @return the hash code 148 */ 149 @Override 150 public int hashCode() { 151 return m_members.stream().mapToInt(Principal::hashCode).reduce(0, (a, b) -> a ^ b); 152 } 153 154 /** 155 * Returns the creation date. 156 * 157 * @return the creation date 158 */ 159 public synchronized Date getCreated() { 160 return m_created; 161 } 162 163 /** 164 * Returns the creator of this Group. 165 * 166 * @return the creator 167 */ 168 public final synchronized String getCreator() { 169 return m_creator; 170 } 171 172 /** 173 * Returns the last-modified date. 174 * 175 * @return the date and time of last modification 176 */ 177 public synchronized Date getLastModified() { 178 return m_modified; 179 } 180 181 /** 182 * Returns the name of the user who last modified this group. 183 * 184 * @return the modifier 185 */ 186 public final synchronized String getModifier() { 187 return m_modifier; 188 } 189 190 /** 191 * The name of the group. This is set in the class constructor. 192 * 193 * @return the name of the Group 194 */ 195 public String getName() { 196 return m_name; 197 } 198 199 /** 200 * Returns the GroupPrincipal that represents this Group. 201 * 202 * @return the group principal 203 */ 204 public Principal getPrincipal() { 205 return m_principal; 206 } 207 208 /** 209 * Returns the wiki name. 210 * 211 * @return the wiki name 212 */ 213 public String getWiki() { 214 return m_wiki; 215 } 216 217 /** 218 * Returns <code>true</code> if a Principal is a member of the group. Specifically, the Principal's <code>getName()</code> method must 219 * 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. 220 * 221 * @param principal the principal about whom membership status is sought 222 * @return the result of the operation 223 */ 224 public boolean isMember( final Principal principal ) { 225 return findMember( principal.getName() ) != null; 226 } 227 228 /** 229 * Returns the members of the group as an array of Principal objects. 230 * 231 * @return the members 232 */ 233 public Principal[] members() { 234 return m_members.toArray( new Principal[0] ); 235 } 236 237 /** 238 * Removes a Principal from the group. 239 * 240 * @param user the principal to remove 241 * @return <code>true</code> if the operation was successful 242 */ 243 public synchronized boolean remove( Principal user ) { 244 user = findMember( user.getName() ); 245 if( user == null ) 246 return false; 247 248 m_members.remove( user ); 249 250 return true; 251 } 252 253 /** 254 * Sets the created date. 255 * 256 * @param date the creation date 257 */ 258 public synchronized void setCreated( final Date date ) { 259 m_created = date; 260 } 261 262 /** 263 * Sets the creator of this Group. 264 * @param creator the creator 265 */ 266 public final synchronized void setCreator( final String creator ) { 267 this.m_creator = creator; 268 } 269 270 /** 271 * Sets the last-modified date 272 * 273 * @param date the last-modified date 274 */ 275 public synchronized void setLastModified( final Date date ) { 276 m_modified = date; 277 } 278 279 /** 280 * Sets the name of the user who last modified this group. 281 * 282 * @param modifier the modifier 283 */ 284 public final synchronized void setModifier( final String modifier ) { 285 this.m_modifier = modifier; 286 } 287 288 /** 289 * Returns a string representation of the Group. 290 * 291 * @return the string 292 * @see java.lang.Object#toString() 293 */ 294 @Override 295 public String toString() { 296 return "(Group " + getName() + ")"; 297 } 298 299 private Principal findMember( final String name ) { 300 return m_members.stream().filter(member -> member.getName().equals(name)).findFirst().orElse(null); 301 302 } 303 304}