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    }