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