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    }