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    @Override
141    public String getActions()
142    {
143        return m_actionString;
144    }
145
146    /**
147     * Returns the name of the wiki containing the page represented by this
148     * permission; may return the wildcard string.
149     * @return the wiki
150     */
151    public String getWiki()
152    {
153        return m_wiki;
154    }
155
156    /**
157     * Returns the hash code for this WikiPermission.
158     * @return {@inheritDoc}
159     */
160    public int hashCode()
161    {
162        return m_mask + ( ( 13 * m_actionString.hashCode() ) * 23 * m_wiki.hashCode() );
163    }
164
165    /**
166     * WikiPermission can only imply other WikiPermissions; no other permission
167     * types are implied. One WikiPermission implies another if all of the other
168     * WikiPermission's actions are equal to, or a subset of, those for this
169     * permission.
170     * @param permission the permission which may (or may not) be implied by
171     * this instance
172     * @return <code>true</code> if the permission is implied,
173     * <code>false</code> otherwise
174     * @see java.security.Permission#implies(java.security.Permission)
175     */
176    @Override
177    public boolean implies(final Permission permission )
178    {
179        // Permission must be a WikiPermission
180        if ( !( permission instanceof WikiPermission ) )
181        {
182            return false;
183        }
184        final WikiPermission p = (WikiPermission) permission;
185
186        // See if the wiki is implied
187        final boolean impliedWiki = PagePermission.isSubset( m_wiki, p.m_wiki );
188
189        // Build up an "implied mask" for actions
190        final int impliedMask = impliedMask( m_mask );
191
192        // If actions aren't a proper subset, return false
193        return impliedWiki && ( impliedMask & p.m_mask ) == p.m_mask;
194    }
195
196    /**
197     * Returns a new {@link AllPermissionCollection}.
198     * @return {@inheritDoc}
199     */
200    @Override
201    public PermissionCollection newPermissionCollection()
202    {
203        return new AllPermissionCollection();
204    }
205
206    /**
207     * Prints a human-readable representation of this permission.
208     * @return {@inheritDoc}
209     */
210    public String toString()
211    {
212        return "(\"" + this.getClass().getName() + "\",\"" + m_wiki + "\",\"" + getActions() + "\")";
213    }
214
215    /**
216     * Creates an "implied mask" based on the actions originally assigned: for
217     * example, <code>createGroups</code> implies <code>createPages</code>.
218     * @param mask the initial mask
219     * @return the implied mask
220     */
221    static int impliedMask( int mask )
222    {
223        if ( ( mask & CREATE_GROUPS_MASK ) > 0 )
224        {
225            mask |= CREATE_PAGES_MASK;
226        }
227        return mask;
228    }
229
230    /**
231     * Private method that creates a binary mask based on the actions specified.
232     * This is used by {@link #implies(Permission)}.
233     * @param actions the permission actions, separated by commas
234     * @return binary mask representing the permissions
235     */
236    static int createMask(final String actions )
237    {
238        if ( actions == null || actions.isEmpty() )
239        {
240            throw new IllegalArgumentException( "Actions cannot be blank or null" );
241        }
242        int mask = 0;
243        final String[] actionList = actions.split( "," );
244        for (final String action : actionList) {
245            if (action.equalsIgnoreCase(CREATE_GROUPS_ACTION)) {
246                mask |= CREATE_GROUPS_MASK;
247            } else if (action.equalsIgnoreCase(CREATE_PAGES_ACTION)) {
248                mask |= CREATE_PAGES_MASK;
249            } else if (action.equalsIgnoreCase(LOGIN_ACTION)) {
250                mask |= LOGIN_MASK;
251            } else if (action.equalsIgnoreCase(EDIT_PREFERENCES_ACTION)) {
252                mask |= EDIT_PREFERENCES_MASK;
253            } else if (action.equalsIgnoreCase(EDIT_PROFILE_ACTION)) {
254                mask |= EDIT_PROFILE_MASK;
255            } else {
256                throw new IllegalArgumentException("Unrecognized action: " + action);
257            }
258        }
259        return mask;
260    }
261}