001 /*
002 * Databinder: a simple bridge from Wicket to Hibernate
003 * Copyright (C) 2006 Nathan Hamblen nathan@technically.us
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either
008 * version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public
016 * License along with this library; if not, write to the Free Software
017 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
018 */
019 package net.databinder.auth.components;
020
021 import java.util.HashMap;
022 import java.util.Map;
023
024 import net.databinder.auth.AuthSession;
025 import net.databinder.auth.AuthApplication;
026 import net.databinder.auth.components.DataSignInPageBase.ReturnPage;
027 import net.databinder.auth.data.DataUser;
028 import net.databinder.auth.valid.EqualPasswordConvertedInputValidator;
029 import net.databinder.components.NullPlug;
030 import net.databinder.models.BindingModel;
031
032 import org.apache.wicket.Application;
033 import org.apache.wicket.AttributeModifier;
034 import org.apache.wicket.Component;
035 import org.apache.wicket.markup.html.WebMarkupContainer;
036 import org.apache.wicket.markup.html.form.Button;
037 import org.apache.wicket.markup.html.border.Border;
038 import org.apache.wicket.markup.html.form.CheckBox;
039 import org.apache.wicket.markup.html.form.Form;
040 import org.apache.wicket.markup.html.form.RequiredTextField;
041 import org.apache.wicket.markup.html.form.SimpleFormComponentLabel;
042 import org.apache.wicket.markup.html.form.validation.FormComponentFeedbackBorder;
043 import org.apache.wicket.markup.html.panel.FeedbackPanel;
044 import org.apache.wicket.markup.html.panel.Panel;
045 import org.apache.wicket.model.AbstractReadOnlyModel;
046 import org.apache.wicket.model.IChainingModel;
047 import org.apache.wicket.model.IModel;
048 import org.apache.wicket.model.Model;
049 import org.apache.wicket.model.ResourceModel;
050 import org.apache.wicket.protocol.http.WebSession;
051 import org.apache.wicket.validation.IValidatable;
052 import org.apache.wicket.validation.validator.StringValidator;
053
054 /**
055 * Registration with username, password, and password confirmation.
056 * Replaceable String resources: <pre>
057 * data.auth.username
058 * data.auth.password
059 * data.auth.passwordConfirm
060 * data.auth.remember
061 * data.auth.register
062 * data.auth.update
063 * data.auth.username.taken * </pre> * Must be overriden in a containing page
064 * or a subclass of this panel.
065 */
066 public abstract class DataProfilePanelBase extends Panel {
067 private ReturnPage returnPage;
068 private Form form;
069 private RequiredTextField username;
070 private RSAPasswordTextField password, passwordConfirm;
071 private CheckBox rememberMe;
072
073 /** @return component used in base page, if needed in subclass */
074 protected RequiredTextField getUsername() { return username; }
075 /** @return component used in base page, if needed in subclass */
076 protected RSAPasswordTextField getPassword() { return password; }
077 /** @return component used in base page, if needed in subclass */
078 protected RSAPasswordTextField getPasswordConfirm() { return passwordConfirm; }
079 /** @return component used in base page, if needed in subclass */
080 protected CheckBox getRememberMe() { return rememberMe; }
081 /** @return form used in base page, if needed elsewhere */
082 public Form getForm() { return form; }
083
084 public DataProfilePanelBase(String id, ReturnPage returnPage) {
085 super(id);
086 this.returnPage = returnPage;
087 add(form = profileForm("registerForm", DataSignInPageBase.getAuthSession().getUserModel()));
088 form.add(new Profile("profile"));
089 }
090
091 /** @return new form component to be used within this panel */
092 protected abstract Form profileForm(String id, IModel userModel);
093
094 /** @return user from form component */
095 protected DataUser getUser() {
096 return (DataUser) form.getModelObject();
097 }
098
099 /** @return true if form is bound to existing user, is not registration form */
100 protected boolean existing() {
101 BindingModel model = ((BindingModel)((IChainingModel)form.getModel()).getChainedModel());
102 return model != null && model.isBound();
103 }
104
105 /** Contents of the profile form. */
106 protected class Profile extends WebMarkupContainer {
107
108 public Profile(String id) {
109 super(id);
110 add(highFormSocket("highFormSocket"));
111 add(feedbackBorder("username-border")
112 .add(username = new RequiredTextField("username")));
113 username.add(new UsernameValidator());
114 username.setLabel(new ResourceModel("data.auth.username", "Username"));
115 add(new SimpleFormComponentLabel("username-label", username));
116 add(feedbackBorder("password-border")
117 .add(password = new RSAPasswordTextField("password", new Model(), form) {
118 public boolean isRequired() {
119 return !existing();
120 }
121 }));
122 password.setLabel(new ResourceModel("data.auth.password", "Password"));
123 add(new SimpleFormComponentLabel("password-label", password));
124 add(feedbackBorder("passwordConfirm-border")
125 .add(passwordConfirm = new RSAPasswordTextField("passwordConfirm", new Model(), form) {
126 public boolean isRequired() {
127 return !existing();
128 }
129 @Override
130 protected void onModelChanged() {
131 setPassword((String) getModelObject());
132 }
133 }));
134 form.add(new EqualPasswordConvertedInputValidator(password, passwordConfirm));
135 passwordConfirm.setLabel(new ResourceModel("data.auth.passwordConfirm", "Retype Password"));
136 add(new SimpleFormComponentLabel("passwordConfirm-label", passwordConfirm));
137
138 add(new WebMarkupContainer("rememberMeRow") {
139 public boolean isVisible() {
140 return !existing();
141 }
142 }.add(rememberMe = new CheckBox("rememberMe", new Model(Boolean.TRUE))));
143
144 add(lowFormSocket("lowFormSocket"));
145
146 add(new Button("submit") {
147 }.add(new AttributeModifier("value", new AbstractReadOnlyModel() {
148 public Object getObject() {
149 return existing() ? getString("auth.data.update", null, "Update Account") :
150 getString("data.auth.register", null, "Register");
151 }
152 })));
153 }
154 }
155
156 protected void setPassword(String password) {
157 if (password != null)
158 getUser().getPassword().change(password);
159 }
160
161 /** Subclasses call this after form submission. Returns user to prior page if possible, otherwise home. */
162 protected void afterSubmit() {
163 DataSignInPageBase.getAuthSession().signIn(getUser(), (Boolean) rememberMe.getModelObject());
164
165 if (returnPage == null) {
166 if (!continueToOriginalDestination())
167 setResponsePage(getApplication().getHomePage());
168 } else
169 setResponsePage(returnPage.get());
170 }
171
172 /** @return true if username is available (can not load via AuthApplication, or is current user). */
173 public static boolean isAvailable(String username) {
174 AuthApplication authSettings = (AuthApplication)Application.get();
175
176 DataUser found = (DataUser) authSettings.getUser(username),
177 current = ((AuthSession)WebSession.get()).getUser();
178 return found == null || found.equals(current);
179 }
180
181 /** Username is valid if isAvailable(username) returns true */
182 public static class UsernameValidator extends StringValidator {
183 @Override
184 protected void onValidate(IValidatable validatable) {
185 String username = (String) validatable.getValue();
186 if (username != null && !isAvailable(username)) {
187 Map<String, String> m = new HashMap<String, String>(1);
188 m.put("username", username);
189 error(validatable,"data.auth.username.taken", m);
190 }
191 }
192 }
193
194 /** @return content to appear above form, base class returns feedback panel */
195 protected Component highFormSocket(String id) {
196 return new FeedbackPanel(id)
197 .add(new AttributeModifier("class", true, new Model("feedback")));
198 }
199
200 /** @return content to appear below form, base class returns blank */
201 protected Component lowFormSocket(String id) {
202 return new NullPlug(id);
203 }
204
205 /** @return border to go around each form component, base returns FormComponentFeedbackBorder. */
206 protected Border feedbackBorder(String id) {
207 return new FormComponentFeedbackBorder(id);
208 }
209 }