/**
 * Copyright 2011 55 Minutes (http://www.55minutes.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fiftyfive.wicket.shiro.markup;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// TODO: docs

/**
 * @since 3.0
 */
public abstract class AbstractLoginForm extends StatelessForm<Void>
{
    private static final Logger LOGGER = LoggerFactory.getLogger(LoginForm.class);
    
    public AbstractLoginForm(String id)
    {
        super(id);
    }

    protected abstract Component getEmailField();
    protected abstract Component getPasswordField();
    
    /**
     * Delegate to {@link #loginShiro loginShiro()} to peform the authentication; if it
     * succeeds, redirect to the user's original intended destination or to the
     * application home page.
     */
    @Override
    protected void onSubmit()
    {
        String email = getEmailField().getDefaultModelObjectAsString();
        String password = getPasswordField().getDefaultModelObjectAsString();
        
        // Convert email to lowercase just in case the backend authentication system
        // is case sensitive and the user accidentally typed in uppercase.
        if(email != null)
        {
            email = email.toLowerCase();
        }

        if(loginShiro(email, password, remember()))
        {
            if(!continueToOriginalDestination())
            {
                setResponsePage(getApplication().getHomePage());
            }
        }
    }
    
    /**
     * Peform the actual authentication using Shiro's {@link Subject#login login()}.
     * <p>
     * <b>Important:</b> this method is written to ensure that the user's session
     * is replaced with a new session before authentication is performed. This is to
     * prevent a <a href="https://www.owasp.org/index.php/Session_Fixation">session fixation</a>
     * attack. As a side effect, any existing session data will therefore be lost.
     * 
     * @return {@code true} if authentication succeeded
     */
    protected boolean loginShiro(String email, String password, boolean remember)
    {
        Subject currentUser = SecurityUtils.getSubject();

        // Force a new session to prevent fixation attack.
        // We have to invalidate via both Shiro and Wicket; otherwise it doesn't work.
        currentUser.getSession().stop(); // Shiro
        getSession().replaceSession();   // Wicket
        
        UsernamePasswordToken token;
        token = new UsernamePasswordToken(email, password, remember);
        try
        {
            currentUser.login(token);
            return true;
        }
        catch (AuthenticationException ae)
        {
            onAuthenticationException(ae);
        }
        return false;
    }
    
    /**
     * Handle any exceptions that are thrown upon login failure by setting an appropriate
     * feedback message. The default implemention adds an error feedback message with the key
     * {@code loginFailed} to the email field.
     */
    protected void onAuthenticationException(AuthenticationException ae)
    {
        LOGGER.debug("Shiro Subject.login() failed", ae);
        getEmailField().error(getString("loginFailed", null, "Invalid email and/or password."));
    }
    
    /**
     * Override this method to return {@code true} if you want to enable
     * Shiro's "remember me" feature. By default this returns {@code false}.
     */
    protected boolean remember()
    {
        return false;
    }
}

@@ -998,323 +998,1427 @@
 ;%0A%0A/
-/ TODO: docs%0A%0A/**%0A * @since 3.0%0A */%0Apublic abstract class AbstractLoginForm extends StatelessForm%3CVoid%3E%0A%7B%0A    private static final Logger LOGGER = LoggerFactory.getLogger(LoginForm.class);%0A    %0A    public AbstractLoginForm(String id)%0A    %7B%0A        super(id);%0A    %7D%0A%0A    protected abstract Component getEmailField();
+**%0A * A form that authenticates the user via Shiro when an email and password are submitted.%0A * Also supports %22remember me%22. Subclasses are expected to provide the actual markup and Wicket%0A * components for the email and password fields. Refer to %7B@link LoginForm%7D for a reference%0A * implementation.%0A *%0A * @see #getEmailField%0A * @see #getPasswordField%0A * @see #remember%0A * @since 3.0%0A */%0Apublic abstract class AbstractLoginForm extends StatelessForm%3CVoid%3E%0A%7B%0A    private static final Logger LOGGER = LoggerFactory.getLogger(LoginForm.class);%0A    %0A    public AbstractLoginForm(String id)%0A    %7B%0A        super(id);%0A    %7D%0A%0A    /**%0A     * Returns the email form field component. It is the responsibility of the subclass to%0A     * construct this component, give it a String model, and insert it into the component%0A     * hierarchy according to the desired markup. When the form is submitted the model of%0A     * this email field component will be used as the email address for authentication.%0A     */%0A    protected abstract Component getEmailField();%0A%0A    /**%0A     * Returns the password form field component. It is the responsibility of the subclass to%0A     * construct this component, give it a String model, and insert it into the component%0A     * hierarchy according to the desired markup. When the form is submitted the model of%0A     * this password field component will be used as the password for authentication.%0A     */
 %0A   

Improve javadoc