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; 020 021import org.apache.wiki.api.core.Session; 022import org.apache.wiki.api.engine.Initializable; 023import org.apache.wiki.auth.authorize.Role; 024import org.apache.wiki.event.WikiEventListener; 025import org.apache.wiki.event.WikiEventManager; 026 027import javax.security.auth.Subject; 028import javax.security.auth.callback.CallbackHandler; 029import javax.security.auth.spi.LoginModule; 030import jakarta.servlet.http.HttpServletRequest; 031import java.security.Principal; 032import java.util.Map; 033import java.util.Set; 034import org.apache.wiki.event.WikiSecurityEvent; 035import org.apache.wiki.security.EventUtil; 036 037 038/** 039 * Manages authentication activities for a Engine: user login, logout, and credential refreshes. This class uses JAAS to determine how 040 * users log in. 041 * <p> 042 * The login procedure is protected in addition by a mechanism which prevents a hacker to try and force-guess passwords by slowing down 043 * attempts to log in into the same account. Every login attempt is recorded, and stored for a while (currently ten minutes), and each 044 * login attempt during that time incurs a penalty of 2^login attempts milliseconds - that is, 10 login attempts incur a login penalty 045 * of 1.024 seconds. The delay is currently capped to 20 seconds. 046 * 047 * @since 2.3 048 */ 049public interface AuthenticationManager extends Initializable { 050 051 /** If this jspwiki.properties property is <code>true</code>, logs the IP address of the editor on saving. */ 052 String PROP_STOREIPADDRESS = "jspwiki.storeIPAddress"; 053 054 /** If this jspwiki.properties property is <code>true</code>, allow cookies to be used for authentication. */ 055 String PROP_ALLOW_COOKIE_AUTH = "jspwiki.cookieAuthentication"; 056 057 /** Whether logins should be throttled to limit brute-forcing attempts. Defaults to true. */ 058 String PROP_LOGIN_THROTTLING = "jspwiki.login.throttling"; 059 060 /** Prefix for LoginModule options key/value pairs. */ 061 String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options."; 062 063 /** If this jspwiki.properties property is <code>true</code>, allow cookies to be used to assert identities. */ 064 String PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions"; 065 066 /** The {@link LoginModule} to use for custom authentication. */ 067 String PROP_LOGIN_MODULE = "jspwiki.loginModule.class"; 068 069 /** 070 * Returns true if this Engine uses container-managed authentication. This method is used primarily for cosmetic purposes in the 071 * JSP tier, and performs no meaningful security function per se. Delegates to 072 * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#isContainerAuthorized()}, 073 * if used as the external authorizer; otherwise, returns <code>false</code>. 074 * 075 * @return <code>true</code> if the wiki's authentication is managed by the container, <code>false</code> otherwise 076 */ 077 boolean isContainerAuthenticated(); 078 079 /** 080 * <p>Logs in the user by attempting to populate a Session Subject from a web servlet request by examining the request 081 * for the presence of container credentials and user cookies. The processing logic is as follows: 082 * </p> 083 * <ul> 084 * <li>If the Session had previously been unauthenticated, check to see if user has subsequently authenticated. To be considered 085 * "authenticated," the request must supply one of the following (in order of preference): the container <code>userPrincipal</code>, 086 * container <code>remoteUser</code>, or authentication cookie. If the user is authenticated, this method fires event 087 * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_AUTHENTICATED} with two parameters: a Principal representing the login principal, 088 * and the current Session. In addition, if the authorizer is of type WebContainerAuthorizer, this method iterates through the 089 * container roles returned by {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#getRoles()}, tests for membership in each 090 * one, and adds those that pass to the Subject's principal set.</li> 091 * <li>If, after checking for authentication, the Session is still Anonymous, this method next checks to see if the user has 092 * "asserted" an identity by supplying an assertion cookie. If the user is found to be asserted, this method fires event 093 * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ASSERTED} with two parameters: <code>WikiPrincipal(<em>cookievalue</em>)</code>, 094 * and the current Session.</li> 095 * <li>If, after checking for authenticated and asserted status, the Session is <em>still</em> anonymous, this method fires event 096 * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ANONYMOUS} with two parameters: <code>WikiPrincipal(<em>remoteAddress</em>)</code>, 097 * and the current Session </li> 098 * </ul> 099 * 100 * @param request servlet request for this user 101 * @return always returns <code>true</code> (because anonymous login, at least, will always succeed) 102 * @throws org.apache.wiki.auth.WikiSecurityException if the user cannot be logged in for any reason 103 * @since 2.3 104 */ 105 boolean login( HttpServletRequest request ) throws WikiSecurityException; 106 107 /** 108 * Attempts to perform a Session login for the given username/password combination using JSPWiki's custom authentication mode. In 109 * order to log in, the JAAS LoginModule supplied by the Engine property {@link #PROP_LOGIN_MODULE} will be instantiated, and its 110 * {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)} method will be invoked. By default, 111 * the {@link org.apache.wiki.auth.login.UserDatabaseLoginModule} class will be used. When the LoginModule's <code>initialize</code> 112 * method is invoked, an options Map populated by properties keys prefixed by {@link #PREFIX_LOGIN_MODULE_OPTIONS} will be passed as a 113 * parameter. 114 * 115 * @param session the current wiki session; may not be <code>null</code>. 116 * @param request the user's HTTP request. This parameter may be <code>null</code>, but the configured LoginModule will not have access 117 * to the HTTP request in this case. 118 * @param username The user name. This is a login name, not a WikiName. In most cases they are the same, but in some cases, they might not be. 119 * @param password the password 120 * @return true, if the username/password is valid 121 * @throws org.apache.wiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained 122 */ 123 boolean login( Session session, HttpServletRequest request, String username, String password ) throws WikiSecurityException; 124 125 /** 126 * Logs the user out by retrieving the Session associated with the HttpServletRequest and unbinding all of the Subject's Principals, 127 * except for {@link Role#ALL}, {@link Role#ANONYMOUS}. is a cheap-and-cheerful way to do it without invoking JAAS LoginModules. 128 * The logout operation will also flush the JSESSIONID cookie from the user's browser session, if it was set. 129 * 130 * @param request the current HTTP request 131 */ 132 void logout( HttpServletRequest request ); 133 134 /** 135 * Determines whether this Engine allows users to assert identities using cookies instead of passwords. This is determined by inspecting 136 * the Engine property {@link #PROP_ALLOW_COOKIE_ASSERTIONS}. 137 * 138 * @return <code>true</code> if cookies are allowed 139 */ 140 boolean allowsCookieAssertions(); 141 142 /** 143 * Determines whether this Engine allows users to authenticate using cookies instead of passwords. This is determined by inspecting 144 * the Engine property {@link #PROP_ALLOW_COOKIE_AUTH}. 145 * 146 * @return <code>true</code> if cookies are allowed for authentication 147 * @since 2.5.62 148 */ 149 boolean allowsCookieAuthentication(); 150 151 /** 152 * Instantiates and executes a single JAAS {@link LoginModule}, and returns a Set of Principals that results from a successful login. 153 * The LoginModule is instantiated, then its {@link LoginModule#initialize(Subject, CallbackHandler, Map, Map)} method is called. The 154 * parameters passed to <code>initialize</code> is a dummy Subject, an empty shared-state Map, and an options Map the caller supplies. 155 * 156 * @param clazz the LoginModule class to instantiate 157 * @param handler the callback handler to supply to the LoginModule 158 * @param options a Map of key/value strings for initializing the LoginModule 159 * @return the set of Principals returned by the JAAS method {@link Subject#getPrincipals()} 160 * @throws WikiSecurityException if the LoginModule could not be instantiated for any reason 161 */ 162 Set< Principal > doJAASLogin( Class< ? extends LoginModule > clazz, CallbackHandler handler, Map< String, String > options) throws WikiSecurityException; 163 164 /** 165 * Determines whether the supplied Principal is a "role principal". 166 * 167 * @param principal the principal to test 168 * @return {@code true} if the Principal is of type {@link GroupPrincipal} or {@link Role}, {@code false} otherwise. 169 */ 170 static boolean isRolePrincipal( final Principal principal ) { 171 return principal instanceof Role || principal instanceof GroupPrincipal; 172 } 173 174 /** 175 * Determines whether the supplied Principal is a "user principal". 176 * 177 * @param principal the principal to test 178 * @return {@code false} if the Principal is of type {@link GroupPrincipal} or {@link Role}, {@code true} otherwise. 179 */ 180 static boolean isUserPrincipal( final Principal principal ) { 181 return !isRolePrincipal( principal ); 182 } 183 184 /** 185 * Returns the first Principal in a set that isn't a {@link Role} or {@link GroupPrincipal}. 186 * 187 * @param principals the principal set 188 * @return the login principal 189 */ 190 default Principal getLoginPrincipal( final Set< Principal > principals ) { 191 return principals.stream().filter(AuthenticationManager::isUserPrincipal).findFirst().orElse(null); 192 } 193 194 // events processing ....................................................... 195 196 /** 197 * Registers a WikiEventListener with this instance. This is a convenience method. 198 * 199 * @param listener the event listener 200 */ 201 void addWikiEventListener( WikiEventListener listener ); 202 203 /** 204 * Un-registers a WikiEventListener with this instance. This is a convenience method. 205 * 206 * @param listener the event listener 207 */ 208 void removeWikiEventListener( final WikiEventListener listener ); 209 210 /** 211 * Fires a WikiSecurityEvent of the provided type, Principal and target Object to all registered listeners. 212 * 213 * @see org.apache.wiki.event.WikiSecurityEvent 214 * @param type the event type to be fired 215 * @param principal the subject of the event, which may be <code>null</code> 216 * @param target the changed Object, which may be <code>null</code> 217 */ 218 default void fireEvent( final int type, final Principal principal, final Object target ) { 219 if ( WikiEventManager.isListening( this ) ) { 220 WikiEventManager.fireEvent( this, 221 EventUtil.applyFrom(new WikiSecurityEvent( this, type, principal, target ) ) ); 222 } 223 } 224 225 /** 226 * Fires a WikiSecurityEvent of the provided type, Principal and target Object to all registered listeners. 227 * 228 * @param request 229 * @see org.apache.wiki.event.WikiSecurityEvent 230 * @param type the event type to be fired 231 * @param principal the subject of the event, which may be <code>null</code> 232 * @param target the changed Object, which may be <code>null</code> 233 */ 234 default void fireEvent( final int type, final Principal principal, final Object target, final HttpServletRequest request ) { 235 if ( WikiEventManager.isListening( this ) ) { 236 WikiEventManager.fireEvent( this, 237 EventUtil.applyFrom(new WikiSecurityEvent( this, type, principal, target ), request ) ); 238 } 239 } 240 241}