/* * Atricore IDBus * * Copyright (c) 2009, Atricore Inc. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.atricore.idbus.capabilities.sso.ui.page.authn.simple; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.PasswordTextField; import org.apache.wicket.markup.html.form.RequiredTextField; import org.apache.wicket.markup.html.form.StatelessForm; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.util.value.ValueMap; import org.atricore.idbus.capabilities.sso.main.claims.SSOCredentialClaimsRequest; import org.atricore.idbus.capabilities.sso.support.auth.AuthnCtxClass; import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding; import org.atricore.idbus.capabilities.sso.ui.components.GtFeedbackPanel; import org.atricore.idbus.capabilities.sso.ui.internal.SSOWebSession; import org.atricore.idbus.capabilities.sso.ui.page.authn.BaseSignInPanel; import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptor; import org.atricore.idbus.kernel.main.mediation.*; import org.atricore.idbus.kernel.main.mediation.camel.component.binding.AbstractMediationHttpBinding; import org.atricore.idbus.kernel.main.mediation.claim.*; import org.atricore.idbus.kernel.main.util.UUIDGenerator; import javax.servlet.http.HttpServletRequest; /** * Sign-in panel for simple authentication for collecting username and password credentials. * * @author <a href="mailto:gbrigandi@atricore.org">Gianluca Brigandi</a> */ public class UsernamePasswordSignInPanel extends BaseSignInPanel { private static final Log logger = LogFactory.getLog(UsernamePasswordSignInPanel.class); private static final long serialVersionUID = 1L; /** * Field for user name. */ protected RequiredTextField<String> username; /** * Field for password. */ protected PasswordTextField password; /** * Error information */ protected FeedbackPanel feedbackPanel; /** * Error information */ protected WebMarkupContainer feedbackBox; /** * Form being processed */ protected UsernamePasswordSignInForm form; /** * Field for remember me option */ protected CheckBox rememberMe; /** * Sign in form. */ public final class UsernamePasswordSignInForm extends StatelessForm<Void> { private static final long serialVersionUID = 3245927593457623741L; /** * Model for form. */ private final ValueMap properties = new ValueMap(); /** * Constructor. * * @param id id of the form component */ public UsernamePasswordSignInForm(final String id) { super(id); // Attach textfield components that edit properties map // in lieu of a formal beans model PropertyModel<String> m = new PropertyModel<String>(properties, "username"); SSOWebSession s = (SSOWebSession) getSession(); if (s.getLastUsername() != null) { m.setObject(s.getLastUsername()); } add(username = new RequiredTextField<String>("username", m)); username.setType(String.class); username.setOutputMarkupId(true); username.setRequired(false); add(password = new PasswordTextField("password", new PropertyModel<String>(properties, "password"))); password.setType(String.class); password.setRequired(false); add(rememberMe = new CheckBox("rememberMe", new PropertyModel<Boolean>(properties, "rememberMe"))); } @Override protected void onInitialize() { super.onInitialize(); // Since wicket does not know about form submittion yet (form.isSubmitted() always false), // we have a work-around that does the same check that wicket will perform later. boolean submitted = false; if (getRequest().getContainerRequest() instanceof HttpServletRequest) { String desiredMethod = getMethod(); String actualMethod = ((HttpServletRequest)getRequest().getContainerRequest()).getMethod(); submitted = actualMethod.equalsIgnoreCase(desiredMethod); } // If the form is being sumbitted, just clear the errors. if (submitted) { onClearError(); } else if (credentialClaimsRequest.getLastErrorId() != null) { if (logger.isDebugEnabled()) logger.info("Received last error ID : " + credentialClaimsRequest.getLastErrorId() + " ("+ credentialClaimsRequest.getLastErrorMsg()+")"); onPreviousError(); } else if (((SSOWebSession)getSession()).getLastAppErrorId() != null){ String lastAppErrorID = ((SSOWebSession)getSession()).getLastAppErrorId(); if (logger.isDebugEnabled()) logger.info("Found last app error ID : " + lastAppErrorID + " ("+lastAppErrorID+")"); onAppError(lastAppErrorID); } else { // No errors, just hide our feedback panel onNoPreviousError(); } } @Override protected void onValidate() { super.onValidate(); } /** * @see org.apache.wicket.markup.html.form.Form#onSubmit() */ @Override public final void onSubmit() { try { String claimsConsumerUrl = signIn(getUsername(), getPassword(), isRememberMe()); onSignInSucceeded(claimsConsumerUrl); } catch (Exception e) { logger.error("Fatal error during signIn : " + e.getMessage(), e); onSignInFailed(); } } } /** * @param id See Component constructor * @see org.apache.wicket.Component#Component(String) */ public UsernamePasswordSignInPanel(final String id, SSOCredentialClaimsRequest credentialClaimsRequest, MessageQueueManager artifactQueueManager, final IdentityMediationUnitRegistry idsuRegistry) { super(id); this.credentialClaimsRequest = credentialClaimsRequest; this.artifactQueueManager = artifactQueueManager; this.idsuRegistry = idsuRegistry; } @Override protected void onInitialize() { super.onInitialize(); // 1. Feedback Panel feedbackBox = buildFeedbackBox(); feedbackPanel = (FeedbackPanel) feedbackBox.get("feedback"); // 2. Sign-In form // Add sign-in form to page, passing feedback panel as // validation error handler is required form = buildSignInForm(); add(form); // If the form does not provide a feedbackBox, we should do it ! if (form.get("feedbackBox") == null) add(feedbackBox); } @Override protected void onBeforeRender() { super.onBeforeRender(); } @Override protected void onAfterRenderChildren() { super.onAfterRenderChildren(); } @Override protected void onModelChanged() { super.onModelChanged(); } protected UsernamePasswordSignInForm buildSignInForm() { UsernamePasswordSignInForm f = new UsernamePasswordSignInForm("signInForm"); f.setOutputMarkupId(true); return f; } protected void onNoPreviousError() { hideFeedback(); } protected void onClearError() { hideFeedback(); } /** * The received request contains previous error information */ protected void onPreviousError() { displayFeedbackMessage(getString("claims.text.invalidCredentials", null, "Unable to sign you in")); } /** * */ protected void onAppError(String appErrorID) { displayFeedbackMessage(getString(appErrorID, null, "Your session has expired, please try again")); } protected void displayFeedbackMessage(String errmsg) { feedbackBox.setVisible(true); feedbackPanel.error(errmsg); feedbackPanel.setVisible(true); } protected void hideFeedback() { feedbackBox.setVisible(false); feedbackPanel.setVisible(false); } /** * Build the container for error messages */ protected WebMarkupContainer buildFeedbackBox() { // Create feedback panel and add it to page feedbackBox = new WebMarkupContainer("feedbackBox"); feedbackPanel = new GtFeedbackPanel ("feedback"); feedbackPanel.setOutputMarkupId(true); feedbackBox.add(feedbackPanel); return feedbackBox; } /** * Removes persisted form data for the signin panel (forget me) */ public final void forgetMe() { // Remove persisted user data. Search for child component // of type UsernamePasswordSignInForm and remove its related persistence values. // getPage().removePersistedFormData(UsernamePasswordSignInForm.class, true); } /** * Convenience method to access the username. * * @return The user name */ public String getUsername() { return username.getDefaultModelObjectAsString(); } /** * Convenience method to access the password. * * @return The password */ public String getPassword() { return password.getDefaultModelObjectAsString(); } public boolean isRememberMe() { return (Boolean) rememberMe.getDefaultModelObject(); } /** * Convenience method set persistence for username and password. * * @param enable Whether the fields should be persistent */ public void setPersistent(final boolean enable) { // username.setPersistent(enable); // password.setPersistent(enable); } /** * Sign in user if possible. This sends credentials to the IDP * * @param username The username * @return True if sign-in was successful (doesn't imply that the credentials are valid!) */ public String signIn(String username, String password, boolean rememberMe) throws Exception { UUIDGenerator idGenerator = new UUIDGenerator(); if (logger.isDebugEnabled()) logger.debug("Claims Request = " + credentialClaimsRequest); SSOWebSession session = (SSOWebSession) getSession(); // TODO: Delay the login form if retries // if (session.getRetries() > 3) // synchronized (this) { try { wait(3000); } catch (InterruptedException e) { /**/ } } session.setRetries(session.getRetries() + 1); session.setLastUsername(username); ClaimSet claims = new ClaimSetImpl(); claims.addClaim(new CredentialClaimImpl("username", username)); claims.addClaim(new CredentialClaimImpl("password", password)); claims.addClaim(new UserClaimImpl("rememberMe", rememberMe)); //claims.addClaim(new ClaimImpl("rememberMe", cmd.isRememberMe())); CredentialClaimsResponse responseCredential = new CredentialClaimsResponseImpl(idGenerator.generateId(), null, credentialClaimsRequest.getId(), claims, credentialClaimsRequest.getRelayState()); EndpointDescriptor claimsEndpoint = resolveClaimsEndpoint(credentialClaimsRequest, AuthnCtxClass.PASSWORD_AUTHN_CTX); if (claimsEndpoint == null) claimsEndpoint = resolveClaimsEndpoint(credentialClaimsRequest, AuthnCtxClass.PPT_AUTHN_CTX); if (claimsEndpoint == null) { logger.error("No claims endpoint found!"); // TODO : Create error and redirect to error view using 'IDBusErrArt' } if (!claimsEndpoint.getBinding().equals(SSOBinding.SSO_ARTIFACT.getValue())) { logger.error("Invalid endpoint binding for claims response : " + claimsEndpoint.getBinding()); // TODO : Create error and redirect to error view using 'IDBusErrArt' } String claimsEndpointUrl = claimsEndpoint.getResponseLocation() != null ? claimsEndpoint.getResponseLocation() : claimsEndpoint.getLocation(); if (logger.isDebugEnabled()) logger.debug("Using claims endpoint URL [" + claimsEndpointUrl + "]"); Artifact a = artifactQueueManager.pushMessage(responseCredential); claimsEndpointUrl += "?SSOArt=" + a.getContent(); if (logger.isDebugEnabled()) logger.debug("Returning claims to " + claimsEndpointUrl); // The request has been used, remove it from the session session.setCredentialClaimsRequest(null); return claimsEndpointUrl; } }