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 public final boolean abort() 074 { 075 removePrincipals( m_principals ); 076 077 // Clear the principals/principalsToRemove sets 078 m_principals.clear(); 079 080 return true; 081 } 082 083 /** 084 * Commits the login. If the overall login method succeeded, adds 085 * principals to the Subject's set; generally, these will be the user's 086 * actual Principal, plus one or more Role principals. The state of the 087 * <code>m_principals</code> member variable is consulted to determine 088 * whether to add the principals. If its size is 0 (because the login 089 * failed), the login is considered to have failed; in this case, 090 * all principals in {@link #m_principals} are removed 091 * from the Subject's set. Otherwise, the principals added to 092 * <code>m_principals</code> in the {@link #login()} method are added to 093 * the Subject's set. 094 * @return <code>true</code> if the commit succeeded, or 095 * <code>false</code> if the previous call to {@link #login()} 096 * failed 097 * @see javax.security.auth.spi.LoginModule#commit() 098 */ 099 public final boolean commit() { 100 if ( succeeded() ) { 101 for ( final Principal principal : m_principals ) { 102 m_subject.getPrincipals().add( principal ); 103 if ( log.isDebugEnabled() ) { 104 log.debug("Committed Principal " + principal.getName() ); 105 } 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 public final void initialize( final Subject subject, final CallbackHandler callbackHandler, final Map<String,?> sharedState, final Map<String,?> options ) { 132 m_principals = new HashSet<>(); 133 m_subject = subject; 134 m_handler = callbackHandler; 135 m_state = sharedState; 136 m_options = options; 137 if ( subject == null ) { 138 throw new IllegalStateException( "Subject cannot be null" ); 139 } 140 if ( callbackHandler == null ) { 141 throw new IllegalStateException( "Callback handler cannot be null" ); 142 } 143 } 144 145 /** 146 * Logs in the user by calling back to the registered CallbackHandler with a 147 * series of callbacks. If the login succeeds, this method returns 148 * <code>true</code> 149 * @return <code>true</code> if the commit succeeded, or 150 * <code>false</code> if this LoginModule should be ignored. 151 * @throws LoginException if the authentication fails 152 * @see javax.security.auth.spi.LoginModule#login() 153 */ 154 public abstract boolean login() throws LoginException; 155 156 /** 157 * Logs the user out. Removes all principals in {@link #m_principals} 158 * from the Subject's principal set. 159 * @return <code>true</code> if the commit succeeded, or 160 * <code>false</code> if this LoginModule should be ignored 161 * @see javax.security.auth.spi.LoginModule#logout() 162 */ 163 public final boolean logout() { 164 removePrincipals( m_principals ); 165 166 // Clear the principals/principalsToRemove sets 167 m_principals.clear(); 168 169 return true; 170 } 171 172 /** 173 * Returns <code>true</code> if the number of principals 174 * contained in {@link #m_principals} is non-zero; 175 * <code>false</code> otherwise. 176 * @return True, if a login has succeeded. 177 */ 178 private boolean succeeded() 179 { 180 return m_principals.size() > 0; 181 } 182 183 /** 184 * Removes a specified collection of Principals from the Subject's 185 * Principal set. 186 * @param principals the principals to remove 187 */ 188 private void removePrincipals( final Collection<Principal> principals ) { 189 for ( final Principal principal : principals ) { 190 if ( m_subject.getPrincipals().contains( principal ) ) { 191 m_subject.getPrincipals().remove( principal ); 192 if ( log.isDebugEnabled() ) { 193 log.debug("Removed Principal " + principal.getName() ); 194 } 195 } 196 } 197 } 198 199}