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 org.apache.log4j.Logger; 022 023import javax.security.auth.Subject; 024import javax.security.auth.callback.CallbackHandler; 025import javax.security.auth.login.LoginException; 026import javax.security.auth.spi.LoginModule; 027import java.security.Principal; 028import java.util.Collection; 029import java.util.HashSet; 030import java.util.Map; 031 032/** 033 * Abstract JAAS {@link javax.security.auth.spi.LoginModule}that implements 034 * base functionality. The methods {@link #login()} and {@link #commit()} must 035 * be implemented by subclasses. The default implementations of 036 * {@link #initialize(Subject, CallbackHandler, Map, Map)}, {@link #abort()} and 037 * {@link #logout()} should be sufficient for most purposes. 038 * @since 2.3 039 */ 040public abstract class AbstractLoginModule implements LoginModule { 041 042 private static final Logger log = Logger.getLogger( AbstractLoginModule.class ); 043 044 protected CallbackHandler m_handler; 045 protected Map< String, ? > m_options; 046 047 /** 048 * Implementing classes should add Principals to this collection; these 049 * will be added to the principal set when the overall login succeeds. 050 * These Principals will be added to the Subject 051 * during the {@link #commit()} phase of login. 052 */ 053 protected Collection< Principal > m_principals; 054 055 protected Map< String, ? > m_state; 056 057 protected Subject m_subject; 058 059 protected static final String NULL = "(null)"; 060 061 /** 062 * Aborts the login; called if the LoginContext's overall authentication 063 * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL 064 * LoginModules did not succeed). Specifically, it removes 065 * Principals from the Subject that are associated with the 066 * individual LoginModule; these will be those contained in 067 * {@link #m_principals}. 068 * It always returns <code>true</code>. 069 * @see javax.security.auth.spi.LoginModule#abort() 070 * @return True, always. 071 */ 072 public final boolean abort() 073 { 074 removePrincipals( m_principals ); 075 076 // Clear the principals/principalsToRemove sets 077 m_principals.clear(); 078 079 return true; 080 } 081 082 /** 083 * Commits the login. If the overall login method succeeded, adds 084 * principals to the Subject's set; generally, these will be the user's 085 * actual Principal, plus one or more Role principals. The state of the 086 * <code>m_principals</code> member variable is consulted to determine 087 * whether to add the principals. If its size is 0 (because the login 088 * failed), the login is considered to have failed; in this case, 089 * all principals in {@link #m_principals} are removed 090 * from the Subject's set. Otherwise, the principals added to 091 * <code>m_principals</code> in the {@link #login()} method are added to 092 * the Subject's set. 093 * @return <code>true</code> if the commit succeeded, or 094 * <code>false</code> if the previous call to {@link #login()} 095 * failed 096 * @see javax.security.auth.spi.LoginModule#commit() 097 */ 098 public final boolean commit() { 099 if ( succeeded() ) { 100 for ( final Principal principal : m_principals ) { 101 m_subject.getPrincipals().add( principal ); 102 if ( log.isDebugEnabled() ) { 103 log.debug("Committed Principal " + principal.getName() ); 104 } 105 } 106 return true; 107 } 108 109 // If login did not succeed, clean up after ourselves 110 removePrincipals( m_principals ); 111 112 // Clear the principals/principalsToRemove sets 113 m_principals.clear(); 114 115 return false; 116 } 117 118 /** 119 * Initializes the LoginModule with a given <code>Subject</code>, 120 * callback handler, options and shared state. In particular, the member 121 * variable <code>m_principals</code> is initialized as a blank Set. 122 * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, 123 * javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) 124 * 125 * @param subject {@inheritDoc} 126 * @param callbackHandler {@inheritDoc} 127 * @param sharedState {@inheritDoc} 128 * @param options {@inheritDoc} 129 */ 130 public final void initialize( final Subject subject, final CallbackHandler callbackHandler, final Map<String,?> sharedState, final Map<String,?> options ) { 131 m_principals = new HashSet<>(); 132 m_subject = subject; 133 m_handler = callbackHandler; 134 m_state = sharedState; 135 m_options = options; 136 if ( subject == null ) { 137 throw new IllegalStateException( "Subject cannot be null" ); 138 } 139 if ( callbackHandler == null ) { 140 throw new IllegalStateException( "Callback handler cannot be null" ); 141 } 142 } 143 144 /** 145 * Logs in the user by calling back to the registered CallbackHandler with a 146 * series of callbacks. If the login succeeds, this method returns 147 * <code>true</code> 148 * @return <code>true</code> if the commit succeeded, or 149 * <code>false</code> if this LoginModule should be ignored. 150 * @throws LoginException if the authentication fails 151 * @see javax.security.auth.spi.LoginModule#login() 152 */ 153 public abstract boolean login() throws LoginException; 154 155 /** 156 * Logs the user out. Removes all principals in {@link #m_principals} 157 * from the Subject's principal set. 158 * @return <code>true</code> if the commit succeeded, or 159 * <code>false</code> if this LoginModule should be ignored 160 * @see javax.security.auth.spi.LoginModule#logout() 161 */ 162 public final boolean logout() { 163 removePrincipals( m_principals ); 164 165 // Clear the principals/principalsToRemove sets 166 m_principals.clear(); 167 168 return true; 169 } 170 171 /** 172 * Returns <code>true</code> if the number of principals 173 * contained in {@link #m_principals} is non-zero; 174 * <code>false</code> otherwise. 175 * @return True, if a login has succeeded. 176 */ 177 private boolean succeeded() 178 { 179 return m_principals.size() > 0; 180 } 181 182 /** 183 * Removes a specified collection of Principals from the Subject's 184 * Principal set. 185 * @param principals the principals to remove 186 */ 187 private void removePrincipals( final Collection<Principal> principals ) { 188 for ( final Principal principal : principals ) { 189 if ( m_subject.getPrincipals().contains( principal ) ) { 190 m_subject.getPrincipals().remove( principal ); 191 if ( log.isDebugEnabled() ) { 192 log.debug("Removed Principal " + principal.getName() ); 193 } 194 } 195 } 196 } 197 198}