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 log.debug("Committed Principal {}", principal.getName() ); 104 } 105 return true; 106 } 107 108 // If login did not succeed, clean up after ourselves 109 removePrincipals( m_principals ); 110 111 // Clear the principals/principalsToRemove sets 112 m_principals.clear(); 113 114 return false; 115 } 116 117 /** 118 * Initializes the LoginModule with a given <code>Subject</code>, 119 * callback handler, options and shared state. In particular, the member 120 * variable <code>m_principals</code> is initialized as a blank Set. 121 * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, 122 * javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) 123 * 124 * @param subject {@inheritDoc} 125 * @param callbackHandler {@inheritDoc} 126 * @param sharedState {@inheritDoc} 127 * @param options {@inheritDoc} 128 */ 129 public final void initialize( final Subject subject, final CallbackHandler callbackHandler, final Map<String,?> sharedState, final Map<String,?> options ) { 130 m_principals = new HashSet<>(); 131 m_subject = subject; 132 m_handler = callbackHandler; 133 m_state = sharedState; 134 m_options = options; 135 if ( subject == null ) { 136 throw new IllegalStateException( "Subject cannot be null" ); 137 } 138 if ( callbackHandler == null ) { 139 throw new IllegalStateException( "Callback handler cannot be null" ); 140 } 141 } 142 143 /** 144 * Logs in the user by calling back to the registered CallbackHandler with a 145 * series of callbacks. If the login succeeds, this method returns 146 * <code>true</code> 147 * @return <code>true</code> if the commit succeeded, or 148 * <code>false</code> if this LoginModule should be ignored. 149 * @throws LoginException if the authentication fails 150 * @see javax.security.auth.spi.LoginModule#login() 151 */ 152 public abstract boolean login() throws LoginException; 153 154 /** 155 * Logs the user out. Removes all principals in {@link #m_principals} 156 * from the Subject's principal set. 157 * @return <code>true</code> if the commit succeeded, or 158 * <code>false</code> if this LoginModule should be ignored 159 * @see javax.security.auth.spi.LoginModule#logout() 160 */ 161 public final boolean logout() { 162 removePrincipals( m_principals ); 163 164 // Clear the principals/principalsToRemove sets 165 m_principals.clear(); 166 167 return true; 168 } 169 170 /** 171 * Returns <code>true</code> if the number of principals 172 * contained in {@link #m_principals} is non-zero; 173 * <code>false</code> otherwise. 174 * @return True, if a login has succeeded. 175 */ 176 private boolean succeeded() 177 { 178 return m_principals.size() > 0; 179 } 180 181 /** 182 * Removes a specified collection of Principals from the Subject's 183 * Principal set. 184 * @param principals the principals to remove 185 */ 186 private void removePrincipals( final Collection<Principal> principals ) { 187 for( final Principal principal : principals ) { 188 if ( m_subject.getPrincipals().contains( principal ) ) { 189 m_subject.getPrincipals().remove( principal ); 190 log.debug("Removed Principal {}", principal.getName() ); 191 } 192 } 193 } 194 195}