001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2007 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.auth.login;
021:
022: import java.security.Principal;
023: import java.util.Collection;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.Map;
027:
028: import javax.security.auth.Subject;
029: import javax.security.auth.callback.CallbackHandler;
030: import javax.security.auth.login.LoginException;
031: import javax.security.auth.spi.LoginModule;
032:
033: import org.apache.log4j.Logger;
034:
035: import com.ecyrd.jspwiki.auth.WikiPrincipal;
036:
037: /**
038: * Abstract JAAS {@link javax.security.auth.spi.LoginModule}that implements
039: * base functionality. The methods {@link #login()} and {@link #commit()} must
040: * be implemented by subclasses. The default implementations of
041: * {@link #initialize(Subject, CallbackHandler, Map, Map)}, {@link #abort()} and
042: * {@link #logout()} should be sufficient for most purposes.
043: * @author Andrew Jaquith
044: * @since 2.3
045: */
046: public abstract class AbstractLoginModule implements LoginModule {
047:
048: private static final Logger log = Logger
049: .getLogger(AbstractLoginModule.class);
050:
051: protected CallbackHandler m_handler;
052:
053: protected Map m_options;
054:
055: /**
056: * Collection of Principals set during login module initialization.
057: * These represent the user's identities prior to the overall login.
058: * Typically these will contain earlier, less-authoritative principals
059: * like a WikiPrincipal for the user cookie, or an IP address.
060: * These Principals are forcibly removed during the commit phase
061: * if login succeeds.
062: */
063: protected Collection m_previousWikiPrincipals;
064:
065: /**
066: * Implementing classes should add Principals to this collection; these
067: * will be added to the principal set when the overall login succeeds.
068: * These Principals will be added to the Subject
069: * during the {@link #commit()} phase of login.
070: */
071: protected Collection m_principals;
072:
073: /**
074: * Implementing classes should add Principals to this collection
075: * to specify what Principals <em>must</em> be removed if login for
076: * this module, or for the entire login configuration overall, fails.
077: * Generally, these will be Principals of type
078: * {@link com.ecyrd.jspwiki.auth.authorize.Role}.
079: */
080: protected Collection m_principalsToRemove;
081:
082: /**
083: * Implementing classes should add Principals to this collection to specify
084: * what Principals, perhaps suppled by other LoginModules, <em>must</em>
085: * be removed if login for this module, or for the entire login
086: * configuration overall, succeeds. Generally, these will be Principals of
087: * type {@link com.ecyrd.jspwiki.auth.authorize.Role}. For example,
088: * {@link CookieAssertionLoginModule} adds
089: * {@link com.ecyrd.jspwiki.auth.authorize.Role#ANONYMOUS} to its
090: * <code>m_principalsToOverwrite</code> collection because when it
091: * succeeds, its own {@link com.ecyrd.jspwiki.auth.authorize.Role#AUTHENTICATED}
092: * should over-write {@link com.ecyrd.jspwiki.auth.authorize.Role#ANONYMOUS}.
093: */
094: protected Collection m_principalsToOverwrite;
095:
096: protected Map m_state;
097:
098: protected Subject m_subject;
099:
100: protected static final String NULL = "(null)";
101:
102: /**
103: * Aborts the login; called if the LoginContext's overall authentication
104: * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
105: * LoginModules did not succeed). Specifically, it removes
106: * Principals from the Subject that are associated with the
107: * individual LoginModule; these will be those contained in
108: * {@link #m_principalsToRemove}.
109: * It always returns <code>true</code>.
110: * @see javax.security.auth.spi.LoginModule#abort()
111: * @throws LoginException if the abort itself fails
112: */
113: public final boolean abort() throws LoginException {
114: removePrincipals(m_principals);
115: removePrincipals(m_principalsToRemove);
116:
117: // Clear the principals/principalsToRemove sets
118: m_principals.clear();
119: m_principalsToRemove.clear();
120:
121: return true;
122: }
123:
124: /**
125: * Commits the login. If the overall login method succeeded, adds
126: * principals to the Subject's set; generally, these will be the user's
127: * actual Principal, plus one or more Role principals. The state of the
128: * <code>m_principals</code> member variable is consulted to determine
129: * whether to add the principals. If its size is 0 (because the login
130: * failed), the login is considered to have failed; in this case,
131: * all principals in {@link #m_principalsToRemove} are removed
132: * from the Subject's set. Otherwise, the principals added to
133: * <code>m_principals</code> in the {@link #login()} method are added to
134: * the Subject's set.
135: * @return <code>true</code> if the commit succeeded, or
136: * <code>false</code> if the previous call to {@link #login()}
137: * failed
138: * @see javax.security.auth.spi.LoginModule#commit()
139: */
140: /**
141: * @see javax.security.auth.spi.LoginModule#commit()
142: */
143: public final boolean commit() throws LoginException {
144: if (succeeded()) {
145: removePrincipals(m_previousWikiPrincipals);
146: for (Iterator it = m_principals.iterator(); it.hasNext();) {
147: Principal principal = (Principal) it.next();
148: m_subject.getPrincipals().add(principal);
149: if (log.isDebugEnabled()) {
150: log.debug("Committed Principal "
151: + principal.getName());
152: }
153: }
154: removePrincipals(m_principalsToOverwrite);
155: return true;
156: }
157:
158: // If login did not succeed, clean up after ourselves
159: removePrincipals(m_principals);
160: removePrincipals(m_principalsToRemove);
161:
162: // Clear the principals/principalsToRemove sets
163: m_principals.clear();
164: m_principalsToRemove.clear();
165:
166: return false;
167: }
168:
169: /**
170: * Initializes the LoginModule with a given <code>Subject</code>,
171: * callback handler, options and shared state. In particular, the member
172: * variable <code>m_principals</code> is initialized as a blank Set.
173: * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
174: * javax.security.auth.callback.CallbackHandler, java.util.Map,
175: * java.util.Map)
176: */
177: public final void initialize(Subject subject,
178: CallbackHandler callbackHandler, Map sharedState,
179: Map options) {
180: m_previousWikiPrincipals = new HashSet();
181: m_principals = new HashSet();
182: m_principalsToRemove = new HashSet();
183: m_principalsToOverwrite = new HashSet();
184: m_subject = subject;
185: m_handler = callbackHandler;
186: m_state = sharedState;
187: m_options = options;
188: if (subject == null) {
189: throw new IllegalStateException("Subject cannot be null");
190: }
191: if (callbackHandler == null) {
192: throw new IllegalStateException(
193: "Callback handler cannot be null");
194: }
195: // Stash the previous WikiPrincipals; we will flush these if login succeeds
196: m_previousWikiPrincipals.addAll(subject
197: .getPrincipals(WikiPrincipal.class));
198: }
199:
200: /**
201: * Logs in the user by calling back to the registered CallbackHandler with a
202: * series of callbacks. If the login succeeds, this method returns
203: * <code>true</code>
204: * @return <code>true</code> if the commit succeeded, or
205: * <code>false</code> if this LoginModule should be ignored.
206: * @throws LoginException if the authentication fails
207: * @see javax.security.auth.spi.LoginModule#login()
208: */
209: public abstract boolean login() throws LoginException;
210:
211: /**
212: * Logs the user out. Removes all principals in {@link #m_principalsToRemove}
213: * from the Subject's principal set.
214: * @return <code>true</code> if the commit succeeded, or
215: * <code>false</code> if this LoginModule should be ignored
216: * @throws LoginException if the logout itself fails
217: * @see javax.security.auth.spi.LoginModule#logout()
218: */
219: public final boolean logout() throws LoginException {
220: removePrincipals(m_principals);
221: removePrincipals(m_principalsToRemove);
222:
223: // Clear the principals/principalsToRemove sets
224: m_principals.clear();
225: m_principalsToRemove.clear();
226:
227: return true;
228: }
229:
230: /**
231: * Returns <code>true</code> if the number of principals
232: * contained in {@link #m_principals} is non-zero;
233: * <code>false</code> otherwise.
234: * @return
235: */
236: private final boolean succeeded() {
237: return m_principals.size() > 0;
238: }
239:
240: /**
241: * Removes a specified collection of Principals from the Subject's
242: * Principal set.
243: * @param principals the principals to remove
244: */
245: private final void removePrincipals(Collection principals) {
246: for (Iterator it = principals.iterator(); it.hasNext();) {
247: Principal principal = (Principal) it.next();
248: if (m_subject.getPrincipals().contains(principal)) {
249: m_subject.getPrincipals().remove(principal);
250: if (log.isDebugEnabled()) {
251: log.debug("Removed Principal "
252: + principal.getName());
253: }
254: }
255: }
256: }
257:
258: }
|