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.permissions;
020
021import java.io.Serializable;
022import java.security.Permission;
023import java.security.PermissionCollection;
024import java.util.Arrays;
025
026/**
027 * <p> Permission to perform an global wiki operation, such as self-registering
028 * or creating new pages. Permission actions include: <code>createGroups</code>,
029 * <code>createPages</code>, <code>editPreferences</code>,
030 * <code>editProfile</code> and <code>login</code>. </p> <p>The target is
031 * a given wiki. The syntax for the target is the wiki name. "All wikis" can be
032 * specified using a wildcard (*). Page collections may also be specified using
033 * a wildcard. For pages, the wildcard may be a prefix, suffix, or all by
034 * itself. <p> Certain permissions imply others. Currently,
035 * <code>createGroups</code> implies <code>createPages</code>. </p>
036 * @since 2.3
037 */
038public final class WikiPermission extends Permission implements Serializable
039{
040    private static final long          serialVersionUID        = 1L;
041
042    /** Name of the action for createGroups permission. */
043    public static final String         CREATE_GROUPS_ACTION    = "createGroups";
044
045    /** Name of the action for createPages permission. */   
046    public static final String         CREATE_PAGES_ACTION     = "createPages";
047
048    /** Name of the action for login permission. */
049    public static final String         LOGIN_ACTION            = "login";
050
051    /** Name of the action for editPreferences permission. */
052    public static final String         EDIT_PREFERENCES_ACTION = "editPreferences";
053
054    /** Name of the action for editProfile permission. */
055    public static final String         EDIT_PROFILE_ACTION     = "editProfile";
056
057    /** Value for a generic wildcard. */
058    public static final String         WILDCARD                = "*";
059
060    static final int         CREATE_GROUPS_MASK      = 0x1;
061
062    static final int         CREATE_PAGES_MASK       = 0x2;
063
064    static final int         EDIT_PREFERENCES_MASK   = 0x4;
065
066    static final int         EDIT_PROFILE_MASK       = 0x8;
067
068    static final int         LOGIN_MASK              = 0x10;
069
070    /** A static instance of the createGroups permission. */
071    public static final WikiPermission CREATE_GROUPS           = new WikiPermission( WILDCARD, CREATE_GROUPS_ACTION );
072
073    /** A static instance of the createPages permission. */
074    public static final WikiPermission CREATE_PAGES            = new WikiPermission( WILDCARD, CREATE_PAGES_ACTION );
075
076    /** A static instance of the login permission. */
077    public static final WikiPermission LOGIN                   = new WikiPermission( WILDCARD, LOGIN_ACTION );
078
079    /** A static instance of the editPreferences permission. */
080    public static final WikiPermission EDIT_PREFERENCES        = new WikiPermission( WILDCARD, EDIT_PREFERENCES_ACTION );
081
082    /** A static instance of the editProfile permission. */
083    public static final WikiPermission EDIT_PROFILE            = new WikiPermission( WILDCARD, EDIT_PROFILE_ACTION );
084
085    private final String               m_actionString;
086
087    private final String               m_wiki;
088
089    private final int                  m_mask;
090
091    /**
092     * Creates a new WikiPermission for a specified set of actions.
093     * @param actions the actions for this permission
094     * @param wiki The name of the wiki the permission belongs to.
095     */
096    public WikiPermission(final String wiki, final String actions )
097    {
098        super( wiki );
099        final String[] pageActions = actions.toLowerCase().split( "," );
100        Arrays.sort( pageActions, String.CASE_INSENSITIVE_ORDER );
101        m_mask = createMask( actions );
102        final StringBuilder buffer = new StringBuilder();
103        for( int i = 0; i < pageActions.length; i++ )
104        {
105            buffer.append( pageActions[i] );
106            if ( i < ( pageActions.length - 1 ) )
107            {
108                buffer.append( "," );
109            }
110        }
111        m_actionString = buffer.toString();
112        m_wiki = ( wiki == null ) ? WILDCARD : wiki;
113    }
114
115    /**
116     * Two WikiPermission objects are considered equal if their wikis and
117     * actions (after normalization) are equal.
118     * @param obj the object to test
119     * @return the result
120     * @see java.lang.Object#equals(java.lang.Object)
121     */
122    public boolean equals(final Object obj )
123    {
124        if ( !( obj instanceof WikiPermission ) )
125        {
126            return false;
127        }
128        final WikiPermission p = (WikiPermission) obj;
129        return  p.m_mask == m_mask && p.m_wiki != null && p.m_wiki.equals( m_wiki );
130    }
131
132    /**
133     * Returns the actions for this permission: "createGroups", "createPages",
134     * "editPreferences", "editProfile", or "login". The actions
135     * will always be sorted in alphabetic order, and will always appear in
136     * lower case.
137     * @return the actions
138     * @see java.security.Permission#getActions()
139     */
140    public String getActions()
141    {
142        return m_actionString;
143    }
144
145    /**
146     * Returns the name of the wiki containing the page represented by this
147     * permission; may return the wildcard string.
148     * @return the wiki
149     */
150    public String getWiki()
151    {
152        return m_wiki;
153    }
154
155    /**
156     * Returns the hash code for this WikiPermission.
157     * @return {@inheritDoc}
158     */
159    public int hashCode()
160    {
161        return m_mask + ( ( 13 * m_actionString.hashCode() ) * 23 * m_wiki.hashCode() );
162    }
163
164    /**
165     * WikiPermission can only imply other WikiPermissions; no other permission
166     * types are implied. One WikiPermission implies another if all of the other
167     * WikiPermission's actions are equal to, or a subset of, those for this
168     * permission.
169     * @param permission the permission which may (or may not) be implied by
170     * this instance
171     * @return <code>true</code> if the permission is implied,
172     * <code>false</code> otherwise
173     * @see java.security.Permission#implies(java.security.Permission)
174     */
175    public boolean implies(final Permission permission )
176    {
177        // Permission must be a WikiPermission
178        if ( !( permission instanceof WikiPermission ) )
179        {
180            return false;
181        }
182        final WikiPermission p = (WikiPermission) permission;
183
184        // See if the wiki is implied
185        final boolean impliedWiki = PagePermission.isSubset( m_wiki, p.m_wiki );
186
187        // Build up an "implied mask" for actions
188        final int impliedMask = impliedMask( m_mask );
189
190        // If actions aren't a proper subset, return false
191        return impliedWiki && ( impliedMask & p.m_mask ) == p.m_mask;
192    }
193
194    /**
195     * Returns a new {@link AllPermissionCollection}.
196     * @return {@inheritDoc}
197     */
198    public PermissionCollection newPermissionCollection()
199    {
200        return new AllPermissionCollection();
201    }
202
203    /**
204     * Prints a human-readable representation of this permission.
205     * @return {@inheritDoc}
206     */
207    public String toString()
208    {
209        return "(\"" + this.getClass().getName() + "\",\"" + m_wiki + "\",\"" + getActions() + "\")";
210    }
211
212    /**
213     * Creates an "implied mask" based on the actions originally assigned: for
214     * example, <code>createGroups</code> implies <code>createPages</code>.
215     * @param mask the initial mask
216     * @return the implied mask
217     */
218    static int impliedMask( int mask )
219    {
220        if ( ( mask & CREATE_GROUPS_MASK ) > 0 )
221        {
222            mask |= CREATE_PAGES_MASK;
223        }
224        return mask;
225    }
226
227    /**
228     * Private method that creates a binary mask based on the actions specified.
229     * This is used by {@link #implies(Permission)}.
230     * @param actions the permission actions, separated by commas
231     * @return binary mask representing the permissions
232     */
233    static int createMask(final String actions )
234    {
235        if ( actions == null || actions.isEmpty() )
236        {
237            throw new IllegalArgumentException( "Actions cannot be blank or null" );
238        }
239        int mask = 0;
240        final String[] actionList = actions.split( "," );
241        for( int i = 0; i < actionList.length; i++ )
242        {
243            final String action = actionList[i];
244            if ( action.equalsIgnoreCase( CREATE_GROUPS_ACTION ) )
245            {
246                mask |= CREATE_GROUPS_MASK;
247            }
248            else if ( action.equalsIgnoreCase( CREATE_PAGES_ACTION ) )
249            {
250                mask |= CREATE_PAGES_MASK;
251            }
252            else if ( action.equalsIgnoreCase( LOGIN_ACTION ) )
253            {
254                mask |= LOGIN_MASK;
255            }
256            else if ( action.equalsIgnoreCase( EDIT_PREFERENCES_ACTION ) )
257            {
258                mask |= EDIT_PREFERENCES_MASK;
259            }
260            else if ( action.equalsIgnoreCase( EDIT_PROFILE_ACTION ) )
261            {
262                mask |= EDIT_PROFILE_MASK;
263            }
264            else
265            {
266                throw new IllegalArgumentException( "Unrecognized action: " + action );
267            }
268        }
269        return mask;
270    }
271}