001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance 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, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.auth.common;
019
020import static org.modeshape.jcr.ModeShapePermissions.READ;
021import static org.modeshape.jcr.ModeShapePermissions.REGISTER_NAMESPACE;
022import static org.modeshape.jcr.ModeShapePermissions.REGISTER_TYPE;
023import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT;
024
025import java.security.Principal;
026import java.util.Set;
027
028import org.modeshape.jcr.security.AdvancedAuthorizationProvider;
029import org.modeshape.jcr.security.SecurityContext;
030import org.modeshape.jcr.value.Path;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import com.google.common.collect.Sets;
035
036/**
037 * The security context for Fedora servlet users. These users are not
038 * necessarily authenticated by the container, i.e. users may include the
039 * general public. This security context delegates all access decisions to the
040 * configured authorization delegate.
041 *
042 * @author Gregory Jansen
043 */
044public class FedoraUserSecurityContext implements SecurityContext,
045        AdvancedAuthorizationProvider {
046
047    private static final Logger LOGGER = LoggerFactory
048            .getLogger(FedoraUserSecurityContext.class);
049
050    private Principal userPrincipal = null;
051
052    private FedoraAuthorizationDelegate fad = null;
053
054    private boolean loggedIn = true;
055
056    /**
057     * Constructs a new security context.
058     *
059     * @param userPrincipal the user principal associated with this security
060     *        context
061     * @param fad the authorization delegate
062     */
063    protected FedoraUserSecurityContext(final Principal userPrincipal,
064            final FedoraAuthorizationDelegate fad) {
065        this.fad = fad;
066        this.userPrincipal = userPrincipal;
067
068        if (this.fad == null) {
069            LOGGER.warn("This security context must have a FAD injected");
070            throw new IllegalArgumentException(
071                    "This security context must have a FAD injected");
072        }
073    }
074
075    /**
076     * {@inheritDoc}
077     *
078     * @see org.modeshape.jcr.security.SecurityContext#isAnonymous()
079     */
080    @Override
081    public boolean isAnonymous() {
082        return this.userPrincipal == null;
083    }
084
085    /**
086     * {@inheritDoc}
087     *
088     * @see SecurityContext#getUserName()
089     */
090    @Override
091    public final String getUserName() {
092        return getEffectiveUserPrincipal().getName();
093    }
094
095    /**
096     * {@inheritDoc}
097     *
098     * @see SecurityContext#hasRole(String)
099     */
100    @Override
101    public final boolean hasRole(final String roleName) {
102        // Under this custom PEP regime, all users have modeshape read and write
103        // roles.
104        if ("read".equals(roleName)) {
105            return true;
106        } else if ("write".equals(roleName)) {
107            return true;
108        } else if ("admin".equals(roleName)) {
109            return true;
110        }
111        return false;
112    }
113
114    /**
115     * Get the user principal associated with this context.
116     *
117     * @return the user principal associated with this security context
118     */
119    public Principal getEffectiveUserPrincipal() {
120        if (this.loggedIn && this.userPrincipal != null) {
121            return this.userPrincipal;
122        }
123        return fad.getEveryonePrincipal();
124    }
125
126    /**
127     * {@inheritDoc}
128     *
129     * @see org.modeshape.jcr.security.SecurityContext#logout()
130     */
131    @Override
132    public void logout() {
133        this.loggedIn = false;
134    }
135
136    /*
137     * (non-Javadoc)
138     * @see
139     * org.modeshape.jcr.security.AdvancedAuthorizationProvider#hasPermission
140     * (org.modeshape.jcr.security.AdvancedAuthorizationProvider.Context,
141     * org.modeshape.jcr.value.Path, java.lang.String[])
142     */
143    @Override
144    public boolean hasPermission(final Context context, final Path absPath,
145            final String... actions) {
146        if (!this.loggedIn) {
147            return false;
148        }
149
150        if (absPath == null) {
151            // this permission is required for login
152            if (actions.length == 1 && READ.equals(actions[0])) {
153                return true;
154            }
155            // The REGISTER_NAMESPACE action and the REGISTER_TYPE action don't include
156            // a path and are allowed for all users.  The fedora 4 code base doesn't expose
157            // any endpoint that *JUST* registers a namespace or type, so the operations
158            // that perform these actions will have to be authorized in context (for instance
159            // setting a property).
160            final Set<String> filteredActions = Sets.newHashSet(actions);
161            filteredActions.remove(REGISTER_NAMESPACE);
162            filteredActions.remove(REGISTER_TYPE);
163            return filteredActions.isEmpty();
164        }
165
166        // Trim jcr:content from paths, if necessary
167        final Path path;
168        if (null != absPath.getLastSegment() && absPath.getLastSegment().getString().equals(JCR_CONTENT)) {
169            path = absPath.subpath(0, absPath.size() - 1);
170            LOGGER.debug("..new path to be verified: {}", path);
171        } else {
172            path = absPath;
173        }
174
175        // delegate
176        if (fad != null) {
177            return fad.hasPermission(context.getSession(), path, actions);
178        }
179        return false;
180    }
181}