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 membership 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}