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.hib;
020
021 import java.security.MessageDigest;
022 import java.security.NoSuchAlgorithmException;
023
024 import javax.servlet.http.HttpServletRequest;
025
026 import net.databinder.auth.AuthApplication;
027 import net.databinder.auth.AuthSession;
028 import net.databinder.auth.components.hib.DataSignInPage;
029 import net.databinder.auth.data.DataUser;
030 import net.databinder.hib.DataApplication;
031 import net.databinder.hib.Databinder;
032
033 import org.apache.wicket.Component;
034 import org.apache.wicket.Request;
035 import org.apache.wicket.RequestCycle;
036 import org.apache.wicket.Response;
037 import org.apache.wicket.RestartResponseAtInterceptPageException;
038 import org.apache.wicket.Session;
039 import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
040 import org.apache.wicket.authorization.UnauthorizedInstantiationException;
041 import org.apache.wicket.authorization.strategies.role.IRoleCheckingStrategy;
042 import org.apache.wicket.authorization.strategies.role.RoleAuthorizationStrategy;
043 import org.apache.wicket.authorization.strategies.role.Roles;
044 import org.apache.wicket.markup.html.WebPage;
045 import org.apache.wicket.protocol.http.WebRequest;
046 import org.apache.wicket.util.crypt.Base64UrlSafe;
047 import org.hibernate.cfg.AnnotationConfiguration;
048 import org.hibernate.criterion.Restrictions;
049
050 /**
051 * Adds basic authentication functionality to DataApplication. This class is a derivative
052 * of Wicket's AuthenticatedWebApplication, brought into the DataApplication hierarchy
053 * and including light user specifications in DataUser. You are encouraged to override
054 * getUserClass() to implement your own user entity, possibly by extending UserBase.
055 * It is also possible to use Databinder authentication without extending this base class
056 * by implementing IAuthSettings.
057 * <p>Text appearing in authentication components can be overriden for any language, using
058 * resource keys listed in their documentation. Except as otherwise noted, these resources
059 * can be housed in the application class's properties file, so that subclasses of the pages
060 * and panels are not necessarily required.
061 * @see AuthApplication
062 * @see DataUser
063 * @author Nathan Hamblen
064 */
065 public abstract class AuthDataApplication extends DataApplication
066 implements IUnauthorizedComponentInstantiationListener, IRoleCheckingStrategy, AuthApplication {
067
068 /**
069 * Internal initialization. Client applications should not normally override
070 * or call this method.
071 */
072 @Override
073 protected void internalInit() {
074 super.internalInit();
075 authInit();
076 }
077
078 /**
079 * Sets Wicket's security strategy for role authorization and appoints this
080 * object as the unauthorized instatiation listener. Called automatically on start-up.
081 */
082 protected void authInit() {
083 getSecuritySettings().setAuthorizationStrategy(new RoleAuthorizationStrategy(this));
084 getSecuritySettings().setUnauthorizedComponentInstantiationListener(this);
085 }
086
087 /**
088 * @return new AuthDataSession
089 * @see AuthDataSession
090 */
091 @Override
092 public Session newSession(Request request, Response response) {
093 return new AuthDataSession(request);
094 }
095 /**
096 * Adds to the configuration whatever DataUser class is defined.
097 */
098 @Override
099 protected void configureHibernate(AnnotationConfiguration config) {
100 super.configureHibernate(config);
101 config.addAnnotatedClass(getUserClass());
102 }
103
104 /**
105 * Sends to sign in page if not signed in, otherwise throws UnauthorizedInstantiationException.
106 */
107 public void onUnauthorizedInstantiation(Component component) {
108 if (((AuthSession)Session.get()).isSignedIn()) {
109 throw new UnauthorizedInstantiationException(component.getClass());
110 }
111 else {
112 throw new RestartResponseAtInterceptPageException(getSignInPageClass());
113 }
114 }
115
116 /**
117 * Passes query on to the DataUser object if signed in.
118 */
119 public final boolean hasAnyRole(Roles roles) {
120 DataUser user = ((AuthSession)Session.get()).getUser();
121 if (user != null)
122 for (String role : roles)
123 if (user.hasRole(role))
124 return true;
125 return false;
126 }
127
128 /**
129 * Return user object by matching against a "username" property. Override
130 * if you have a differently named property.
131 * @return DataUser for the given username.
132 */
133 public DataUser getUser(String username) {
134 return (DataUser) Databinder.getHibernateSession().createCriteria(getUserClass())
135 .add(Restrictions.eq("username", username)).uniqueResult();
136 }
137
138 /**
139 * Override if you need to customize the sign-in page.
140 * @return page to sign in users
141 */
142 public Class< ? extends WebPage> getSignInPageClass() {
143 return DataSignInPage.class;
144 }
145
146 /**
147 * @return app-salted MessageDigest.
148 */
149 public MessageDigest getDigest() {
150 try {
151 MessageDigest digest = MessageDigest.getInstance("SHA");
152 digest.update(getSalt());
153 return digest;
154 } catch (NoSuchAlgorithmException e) {
155 throw new RuntimeException("SHA Hash algorithm not found.", e);
156 }
157 }
158
159 /**
160 * Get the restricted token for a user, using IP addresses as location parameter. This implementation
161 * combines the "X-Forwarded-For" header with the remote address value so that unique
162 * values result with and without proxying. (The forwarded header is not trusted on its own
163 * because it can be most easily spoofed.)
164 * @param user source of token
165 * @return restricted token
166 */
167 public String getToken(DataUser user) {
168 HttpServletRequest req = ((WebRequest) RequestCycle.get().getRequest()).getHttpServletRequest();
169 String fwd = req.getHeader("X-Forwarded-For");
170 if (fwd == null)
171 fwd = "nil";
172 MessageDigest digest = getDigest();
173 user.getPassword().update(digest);
174 digest.update((fwd + "-" + req.getRemoteAddr()).getBytes());
175 byte[] hash = digest.digest(user.getUsername().getBytes());
176 return new String(Base64UrlSafe.encodeBase64(hash));
177 }
178 }