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    }