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.login; 020 021import java.security.Principal; 022import java.util.Collection; 023import java.util.HashSet; 024import java.util.Map; 025 026import javax.security.auth.Subject; 027import javax.security.auth.callback.CallbackHandler; 028import javax.security.auth.login.LoginException; 029import javax.security.auth.spi.LoginModule; 030 031import org.apache.log4j.Logger; 032 033import org.apache.wiki.auth.WikiPrincipal; 034 035/** 036 * Abstract JAAS {@link javax.security.auth.spi.LoginModule}that implements 037 * base functionality. The methods {@link #login()} and {@link #commit()} must 038 * be implemented by subclasses. The default implementations of 039 * {@link #initialize(Subject, CallbackHandler, Map, Map)}, {@link #abort()} and 040 * {@link #logout()} should be sufficient for most purposes. 041 * @since 2.3 042 */ 043public abstract class AbstractLoginModule implements LoginModule 044{ 045 046 private static final Logger log = Logger.getLogger( AbstractLoginModule.class ); 047 048 protected CallbackHandler m_handler; 049 050 protected Map<String,?> m_options; 051 052 /** 053 * Collection of Principals set during login module initialization. 054 * These represent the user's identities prior to the overall login. 055 * Typically these will contain earlier, less-authoritative principals 056 * like a WikiPrincipal for the user cookie, or an IP address. 057 * These Principals are forcibly removed during the commit phase 058 * if login succeeds. 059 * @deprecated 060 */ 061 protected Collection<Principal> m_previousWikiPrincipals; 062 063 /** 064 * Implementing classes should add Principals to this collection; these 065 * will be added to the principal set when the overall login succeeds. 066 * These Principals will be added to the Subject 067 * during the {@link #commit()} phase of login. 068 */ 069 protected Collection<Principal> m_principals; 070 071 /** 072 * Implementing classes should add Principals to this collection 073 * to specify what Principals <em>must</em> be removed if login for 074 * this module, or for the entire login configuration overall, fails. 075 * Generally, these will be Principals of type 076 * {@link org.apache.wiki.auth.authorize.Role}. 077 * @deprecated 078 */ 079 protected Collection<Principal> m_principalsToRemove; 080 081 /** 082 * Implementing classes should add Principals to this collection to specify 083 * what Principals, perhaps suppled by other LoginModules, <em>must</em> 084 * be removed if login for this module, or for the entire login 085 * configuration overall, succeeds. Generally, these will be Principals of 086 * type {@link org.apache.wiki.auth.authorize.Role}. For example, 087 * {@link CookieAssertionLoginModule} adds 088 * {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS} to its 089 * <code>m_principalsToOverwrite</code> collection because when it 090 * succeeds, its own {@link org.apache.wiki.auth.authorize.Role#AUTHENTICATED} 091 * should over-write {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}. 092 * @deprecated 093 */ 094 protected Collection<Principal> m_principalsToOverwrite; 095 096 protected Map<String,?> 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 * @return True, always. 113 */ 114 public final boolean abort() throws LoginException 115 { 116 removePrincipals( m_principals ); 117 removePrincipals( m_principalsToRemove ); 118 119 // Clear the principals/principalsToRemove sets 120 m_principals.clear(); 121 m_principalsToRemove.clear(); 122 123 return true; 124 } 125 126 /** 127 * Commits the login. If the overall login method succeeded, adds 128 * principals to the Subject's set; generally, these will be the user's 129 * actual Principal, plus one or more Role principals. The state of the 130 * <code>m_principals</code> member variable is consulted to determine 131 * whether to add the principals. If its size is 0 (because the login 132 * failed), the login is considered to have failed; in this case, 133 * all principals in {@link #m_principalsToRemove} are removed 134 * from the Subject's set. Otherwise, the principals added to 135 * <code>m_principals</code> in the {@link #login()} method are added to 136 * the Subject's set. 137 * @return <code>true</code> if the commit succeeded, or 138 * <code>false</code> if the previous call to {@link #login()} 139 * failed 140 * @see javax.security.auth.spi.LoginModule#commit() 141 */ 142 public final boolean commit() 143 { 144 if ( succeeded() ) 145 { 146 removePrincipals( m_previousWikiPrincipals ); 147 for ( Principal principal : m_principals ) 148 { 149 m_subject.getPrincipals().add( principal ); 150 if ( log.isDebugEnabled() ) 151 { 152 log.debug("Committed Principal " + principal.getName() ); 153 } 154 } 155 removePrincipals( m_principalsToOverwrite ); 156 return true; 157 } 158 159 // If login did not succeed, clean up after ourselves 160 removePrincipals( m_principals ); 161 removePrincipals( m_principalsToRemove ); 162 163 // Clear the principals/principalsToRemove sets 164 m_principals.clear(); 165 m_principalsToRemove.clear(); 166 167 return false; 168 } 169 170 /** 171 * Initializes the LoginModule with a given <code>Subject</code>, 172 * callback handler, options and shared state. In particular, the member 173 * variable <code>m_principals</code> is initialized as a blank Set. 174 * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, 175 * javax.security.auth.callback.CallbackHandler, java.util.Map, 176 * java.util.Map) 177 * 178 * @param subject {@inheritDoc} 179 * @param callbackHandler {@inheritDoc} 180 * @param sharedState {@inheritDoc} 181 * @param options {@inheritDoc} 182 */ 183 public final void initialize( Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options ) 184 { 185 m_previousWikiPrincipals = new HashSet<Principal>(); 186 m_principals = new HashSet<Principal>(); 187 m_principalsToRemove = new HashSet<Principal>(); 188 m_principalsToOverwrite = new HashSet<Principal>(); 189 m_subject = subject; 190 m_handler = callbackHandler; 191 m_state = sharedState; 192 m_options = options; 193 if ( subject == null ) 194 { 195 throw new IllegalStateException( "Subject cannot be null" ); 196 } 197 if ( callbackHandler == null ) 198 { 199 throw new IllegalStateException( "Callback handler cannot be null" ); 200 } 201 // Stash the previous WikiPrincipals; we will flush these if login succeeds 202 m_previousWikiPrincipals.addAll( subject.getPrincipals( WikiPrincipal.class ) ); 203 } 204 205 /** 206 * Logs in the user by calling back to the registered CallbackHandler with a 207 * series of callbacks. If the login succeeds, this method returns 208 * <code>true</code> 209 * @return <code>true</code> if the commit succeeded, or 210 * <code>false</code> if this LoginModule should be ignored. 211 * @throws LoginException if the authentication fails 212 * @see javax.security.auth.spi.LoginModule#login() 213 */ 214 public abstract boolean login() throws LoginException; 215 216 /** 217 * Logs the user out. Removes all principals in {@link #m_principalsToRemove} 218 * from the Subject's principal set. 219 * @return <code>true</code> if the commit succeeded, or 220 * <code>false</code> if this LoginModule should be ignored 221 * @throws LoginException if the logout itself fails 222 * @see javax.security.auth.spi.LoginModule#logout() 223 */ 224 public final boolean logout() throws LoginException 225 { 226 removePrincipals( m_principals ); 227 removePrincipals( m_principalsToRemove ); 228 229 // Clear the principals/principalsToRemove sets 230 m_principals.clear(); 231 m_principalsToRemove.clear(); 232 233 return true; 234 } 235 236 /** 237 * Returns <code>true</code> if the number of principals 238 * contained in {@link #m_principals} is non-zero; 239 * <code>false</code> otherwise. 240 * @return True, if a login has succeeded. 241 */ 242 private boolean succeeded() 243 { 244 return m_principals.size() > 0; 245 } 246 247 /** 248 * Removes a specified collection of Principals from the Subject's 249 * Principal set. 250 * @param principals the principals to remove 251 */ 252 private void removePrincipals( Collection<Principal> principals ) 253 { 254 for ( Principal principal : principals ) 255 { 256 if ( m_subject.getPrincipals().contains( principal ) ) 257 { 258 m_subject.getPrincipals().remove( principal ); 259 if ( log.isDebugEnabled() ) 260 { 261 log.debug("Removed Principal " + principal.getName() ); 262 } 263 } 264 } 265 } 266 267}