    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}