001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012
013package org.nuxeo.ecm.core.api;
014
015import java.security.Principal;
016
017import javax.security.auth.login.LoginContext;
018import javax.security.auth.login.LoginException;
019
020import org.nuxeo.ecm.core.api.security.SecurityConstants;
021import org.nuxeo.runtime.api.Framework;
022
023/**
024 * Helper class to run code with an unrestricted session.
025 * <p>
026 * The caller must implement the {@link #run} method, and call {@link #runUnrestricted}.
027 *
028 * @author Florent Guillaume
029 */
030public abstract class UnrestrictedSessionRunner {
031
032    protected String originatingUsername;
033
034    protected CoreSession session;
035
036    protected final boolean sessionIsAlreadyUnrestricted;
037
038    protected final String repositoryName;
039
040    /** True if a call to {@link #runUnrestricted} is in progress. */
041    public boolean isUnrestricted;
042
043    /**
044     * Constructs a {@link UnrestrictedSessionRunner} given an existing session (which may or may not be already
045     * unrestricted).
046     * <p>
047     * Originating user is taken on given session.
048     *
049     * @param session the available session
050     */
051    protected UnrestrictedSessionRunner(CoreSession session) {
052        this.session = session;
053        sessionIsAlreadyUnrestricted = isUnrestricted(session);
054        if (sessionIsAlreadyUnrestricted) {
055            repositoryName = null;
056        } else {
057            repositoryName = session.getRepositoryName();
058        }
059        Principal pal = session.getPrincipal();
060        if (pal != null) {
061            originatingUsername = pal.getName();
062        }
063    }
064
065    /**
066     * Constructs a {@link UnrestrictedSessionRunner} given a repository name.
067     *
068     * @param repositoryName the repository name
069     */
070    protected UnrestrictedSessionRunner(String repositoryName) {
071        session = null;
072        sessionIsAlreadyUnrestricted = false;
073        this.repositoryName = repositoryName;
074    }
075
076    /**
077     * Constructs a {@link UnrestrictedSessionRunner} given a repository name and an originating user name.
078     *
079     * @param repositoryName the repository name
080     * @param originatingUser the user name behind the system user
081     */
082    protected UnrestrictedSessionRunner(String repositoryName, String originatingUser) {
083        session = null;
084        sessionIsAlreadyUnrestricted = false;
085        this.repositoryName = repositoryName;
086        this.originatingUsername = originatingUser;
087    }
088
089    public String getOriginatingUsername() {
090        return originatingUsername;
091    }
092
093    public void setOriginatingUsername(String originatingUsername) {
094        this.originatingUsername = originatingUsername;
095    }
096
097    protected boolean isUnrestricted(CoreSession session) {
098        return SecurityConstants.SYSTEM_USERNAME.equals(session.getPrincipal().getName())
099                || (session.getPrincipal() instanceof NuxeoPrincipal && ((NuxeoPrincipal) session.getPrincipal()).isAdministrator());
100    }
101
102    /**
103     * Calls the {@link #run()} method with an unrestricted {@link #session}. During this call, {@link #isUnrestricted}
104     * is set to {@code true}.
105     */
106    public void runUnrestricted() {
107        isUnrestricted = true;
108        try {
109            if (sessionIsAlreadyUnrestricted) {
110                run();
111                return;
112            }
113
114            LoginContext loginContext;
115            try {
116                loginContext = Framework.loginAs(originatingUsername);
117            } catch (LoginException e) {
118                throw new NuxeoException(e);
119            }
120            try {
121                CoreSession baseSession = session;
122                if (baseSession != null && !baseSession.isStateSharedByAllThreadSessions()) {
123                    // save base session state for unrestricted one
124                    baseSession.save();
125                }
126                session = CoreInstance.openCoreSession(repositoryName);
127                if (loginContext == null && Framework.isTestModeSet()) {
128                    NuxeoPrincipal principal = (NuxeoPrincipal) session.getPrincipal();
129                    if (principal instanceof SystemPrincipal) {
130                        // we are in a test that is not using authentication
131                        // =>
132                        // we're not stacking the originating user in the
133                        // authentication stack
134                        // so we're setting manually now
135                        principal.setOriginatingUser(originatingUsername);
136                    }
137                }
138                try {
139                    run();
140                } finally {
141                    try {
142                        if (!session.isStateSharedByAllThreadSessions()) {
143                            // save unrestricted state for base session
144                            session.save();
145                        }
146                        session.close();
147                    } finally {
148                        if (baseSession != null && !baseSession.isStateSharedByAllThreadSessions()) {
149                            // process invalidations from unrestricted session
150                            baseSession.save();
151                        }
152                        session = baseSession;
153                    }
154                }
155            } finally {
156                try {
157                    // loginContext may be null in tests
158                    if (loginContext != null) {
159                        loginContext.logout();
160                    }
161                } catch (LoginException e) {
162                    throw new NuxeoException(e);
163                }
164            }
165        } finally {
166            isUnrestricted = false;
167            if (Framework.isTestModeSet() && sessionIsAlreadyUnrestricted) {
168                session.save();
169            }
170        }
171    }
172
173    /**
174     * This method will be called by {@link #runUnrestricted()} with {@link #session} available as an unrestricted
175     * session.
176     * <p>
177     * It can also be called directly in which case the {@link #session} available will be the one passed to
178     * {@code #UnrestrictedSessionRunner(CoreSession)}.
179     */
180    public abstract void run();
181
182}