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     */
019    package org.apache.wiki.auth.authorize;
020    
021    import java.security.Principal;
022    import java.util.Date;
023    import java.util.Vector;
024    
025    import 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     */
060    public 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            StringBuffer sb = new StringBuffer();
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    }