001 /*
002 * Databinder: a simple bridge from Wicket to Hibernate
003 * Copyright (C) 2008 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;
020
021 import java.io.UnsupportedEncodingException;
022 import java.net.URLDecoder;
023 import java.net.URLEncoder;
024
025 import javax.servlet.http.Cookie;
026
027 import net.databinder.CookieRequestCycle;
028 import net.databinder.auth.data.DataUser;
029
030 import org.apache.wicket.Application;
031 import org.apache.wicket.Request;
032 import org.apache.wicket.RequestCycle;
033 import org.apache.wicket.WicketRuntimeException;
034 import org.apache.wicket.model.IModel;
035 import org.apache.wicket.protocol.http.WebApplication;
036 import org.apache.wicket.protocol.http.WebResponse;
037 import org.apache.wicket.protocol.http.WebSession;
038 import org.apache.wicket.util.time.Duration;
039
040 /**
041 * Base class for Databinder implementations providing an implementation for
042 * authentication cookies and current user lookup.
043 */
044 public abstract class AuthDataSessionBase extends WebSession implements AuthSession {
045 /** Effective signed in state. */
046 private IModel userModel;
047 private static final String CHARACTER_ENCODING = "UTF-8";
048
049 /**
050 * Initialize new session.
051 * @see WebApplication
052 */
053 public AuthDataSessionBase(Request request) {
054 super(request);
055 }
056
057 protected static AuthApplication getApp() {
058 return (AuthApplication) Application.get();
059 }
060
061 public static AuthDataSessionBase get() {
062 return (AuthDataSessionBase) WebSession.get();
063 }
064
065 /**
066 * @return DataUser object for current user, or null if none signed in.
067 */
068 public DataUser getUser() {
069 if (isSignedIn()) {
070 return (DataUser) getUserModel().getObject();
071 }
072 return null;
073 }
074
075 public IModel getUserModel() {
076 return userModel;
077 }
078
079 /**
080 * @return model for current user
081 */
082 public abstract IModel createUserModel(DataUser user);
083
084 /**
085 * @return length of time sign-in cookie should persist, defined here as one month
086 */
087 protected Duration getSignInCookieMaxAge() {
088 return Duration.days(31);
089 }
090
091 /**
092 * Determine if user is signed in, or can be via cookie.
093 * @return true if signed in or cookie sign in is possible and successful
094 */
095 public boolean isSignedIn() {
096 if (userModel == null)
097 cookieSignIn();
098 return userModel != null;
099 }
100
101 /**
102 * @return true if signed in, false if credentials incorrect
103 */
104 public boolean signIn(String username, String password) {
105 return signIn(username, password, false);
106 }
107
108 /**
109 * @param setCookie if true, sets cookie to remember user
110 * @return true if signed in, false if credentials incorrect
111 */
112 public boolean signIn(final String username, final String password, boolean setCookie) {
113 signOut();
114 DataUser potential = getUser(username);
115 if (potential != null && (potential).getPassword().matches(password))
116 signIn(potential, setCookie);
117
118 return userModel != null;
119 }
120
121 /**
122 * Sign in a user whose credentials have been validated elsewhere. The user object must exist,
123 * and already have been saved, in the current request's Hibernate session.
124 * @param user validated and persisted user, must be in current Hibernate session
125 * @param setCookie if true, sets cookie to remember user
126 */
127 public void signIn(DataUser user, boolean setCookie) {
128 userModel = createUserModel(user);
129 if (setCookie)
130 setCookie();
131 }
132
133 /**
134 * Attempts cookie sign in, which will set usename field but not user.
135 * @return true if signed in, false if credentials incorrect or unavailable
136 */
137 protected boolean cookieSignIn() {
138 CookieRequestCycle requestCycle = (CookieRequestCycle) RequestCycle.get();
139 Cookie userCookie = requestCycle.getCookie(getUserCookieName()),
140 token = requestCycle.getCookie(getAuthCookieName());
141
142 if (userCookie != null && token != null) {
143 DataUser potential;
144 try {
145 potential = getUser(URLDecoder.decode(userCookie.getValue(), CHARACTER_ENCODING));
146 } catch (UnsupportedEncodingException e) {
147 throw new WicketRuntimeException(e);
148 }
149 if (potential != null && potential instanceof DataUser) {
150 String correctToken = getApp().getToken((DataUser)potential);
151 if (correctToken.equals(token.getValue()))
152 signIn(potential, false);
153 }
154 }
155 return userModel != null;
156 }
157
158 /**
159 * Looks for a persisted DataUser object matching the given username. Uses the user class
160 * and criteria builder returned from the application subclass implementing AuthApplication.
161 * @param username
162 * @return user object from persistent storage
163 * @see AuthApplication
164 */
165 protected DataUser getUser(final String username) {
166 return getApp().getUser(username);
167 }
168
169 public static String getUserCookieName() {
170 return Application.get().getClass().getSimpleName() + "_USER";
171 }
172
173 public static String getAuthCookieName() {
174 return Application.get().getClass().getSimpleName() + "_AUTH";
175 }
176
177 /**
178 * Sets cookie to remember the currently signed-in user. Sets max age to
179 * value from getSignInCookieMaxAge().
180 * @see AuthDataSessionBase#getSignInCookieMaxAge()
181 */
182 protected void setCookie() {
183 if (userModel == null)
184 throw new WicketRuntimeException("User must be signed in when calling this method");
185
186 DataUser cookieUser = (DataUser) getUser();
187 WebResponse resp = (WebResponse) RequestCycle.get().getResponse();
188
189 Cookie name, auth;
190 try {
191 name = new Cookie(getUserCookieName(),
192 URLEncoder.encode(cookieUser.getUsername(), CHARACTER_ENCODING));
193 auth = new Cookie(getAuthCookieName(), getApp().getToken(cookieUser));
194 } catch (UnsupportedEncodingException e) {
195 throw new WicketRuntimeException(e);
196 }
197
198 int maxAge = (int) getSignInCookieMaxAge().seconds();
199 name.setMaxAge(maxAge);
200 auth.setMaxAge(maxAge);
201
202 RequestCycle rc = RequestCycle.get();
203 if (rc instanceof CookieRequestCycle) {
204 CookieRequestCycle cookieRc = (CookieRequestCycle) rc;
205 cookieRc.applyScope(name);
206 cookieRc.applyScope(auth);
207 }
208
209 resp.addCookie(name);
210 resp.addCookie(auth);
211 }
212
213 /**
214 * Detach userModel manually, as it isnt' attached to any component.
215 */
216 @Override
217 protected void detach() {
218 if (userModel != null)
219 userModel.detach();
220 }
221
222 /** Nullifies userModel and clears authentication cookies. */
223 public void signOut() {
224 userModel = null;
225 CookieRequestCycle requestCycle = (CookieRequestCycle) RequestCycle.get();
226 requestCycle.clearCookie(getUserCookieName());
227 requestCycle.clearCookie(getAuthCookieName());
228 }
229
230 }