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}