001    package net.databinder.auth.ao;
002    
003    import java.security.MessageDigest;
004    import java.security.NoSuchAlgorithmException;
005    import java.sql.SQLException;
006    
007    import javax.servlet.http.HttpServletRequest;
008    
009    import net.databinder.ao.DataApplication;
010    import net.databinder.ao.Databinder;
011    import net.databinder.auth.AuthApplication;
012    import net.databinder.auth.AuthSession;
013    import net.databinder.auth.components.ao.DataSignInPage;
014    import net.databinder.auth.data.DataUser;
015    import net.java.ao.Query;
016    import net.java.ao.RawEntity;
017    
018    import org.apache.wicket.Component;
019    import org.apache.wicket.Request;
020    import org.apache.wicket.RequestCycle;
021    import org.apache.wicket.Response;
022    import org.apache.wicket.RestartResponseAtInterceptPageException;
023    import org.apache.wicket.Session;
024    import org.apache.wicket.WicketRuntimeException;
025    import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
026    import org.apache.wicket.authorization.UnauthorizedInstantiationException;
027    import org.apache.wicket.authorization.strategies.role.IRoleCheckingStrategy;
028    import org.apache.wicket.authorization.strategies.role.RoleAuthorizationStrategy;
029    import org.apache.wicket.authorization.strategies.role.Roles;
030    import org.apache.wicket.markup.html.WebPage;
031    import org.apache.wicket.protocol.http.WebRequest;
032    import org.apache.wicket.util.crypt.Base64UrlSafe;
033    
034    /** Optional base class for ActiveObjects applications using Databinder authentication. */
035    public abstract class AuthDataApplication extends DataApplication implements IUnauthorizedComponentInstantiationListener, IRoleCheckingStrategy, AuthApplication {
036            
037            /**
038             * Internal initialization. Client applications should not normally override
039             * or call this method.
040             */
041            @Override
042            protected void internalInit() {
043                    super.internalInit();
044                    authInit();
045            }
046            
047            /**
048             * Sets Wicket's security strategy for role authorization and appoints this 
049             * object as the unauthorized instatiation listener. Called automatically on start-up.
050             */
051            protected void authInit() {
052                    getSecuritySettings().setAuthorizationStrategy(new RoleAuthorizationStrategy(this));
053                    getSecuritySettings().setUnauthorizedComponentInstantiationListener(this);
054            }
055    
056            /**
057             * @return new AuthDataSession
058             * @see AuthDataSession
059             */
060            @Override
061            public Session newSession(Request request, Response response) {
062                    return new AuthDataSession((WebRequest) request);
063            }
064            
065            /**
066             * Sends to sign in page if not signed in, otherwise throws UnauthorizedInstantiationException.
067             */
068            public void onUnauthorizedInstantiation(Component component) {
069                    if (((AuthSession)Session.get()).isSignedIn()) {
070                            throw new UnauthorizedInstantiationException(component.getClass());
071                    }
072                    else {
073                            throw new RestartResponseAtInterceptPageException(getSignInPageClass());
074                    }       
075            }
076            
077            /**
078             * Passes query on to the DataUser object if signed in.
079             */
080            public final boolean hasAnyRole(Roles roles) {
081                    DataUser user = ((AuthSession)Session.get()).getUser();
082                    if (user != null)
083                            for (String role : roles)
084                                    if (user.hasRole(role))
085                                            return true;
086                    return false;
087            }
088            
089            /** @return DataUser implementation used by this application. */
090            public abstract Class<? extends DataUser> getUserClass();
091    
092            /**
093             * Return user object by matching against a "username" property. Override
094             * if you have a differently named property.
095             * @return DataUser for the given username. 
096             */
097            @SuppressWarnings("unchecked")
098            public DataUser getUser(String username) {
099                    try {
100                            Query q = Query.select().where("username = ?", username).limit(1);
101                            DataUser[] users = (DataUser[]) Databinder.getEntityManager().find(
102                                            (Class<? extends RawEntity>)getUserClass(),q);
103                            if (users.length == 0)
104                                    return null;
105                            return users[0];
106                    } catch (SQLException e) {
107                            throw new WicketRuntimeException(e);
108                    }
109            }
110    
111            /**
112             * Override if you need to customize the sign-in page.
113             * @return page to sign in users
114             */
115            public Class< ? extends WebPage> getSignInPageClass() {
116                    return DataSignInPage.class;
117            }
118            
119            /**
120             * @return app-salted MessageDigest.  
121             */
122            public MessageDigest getDigest() {
123                    try {
124                            MessageDigest digest = MessageDigest.getInstance("SHA");
125                            digest.update(getSalt());
126                            return digest;
127                    } catch (NoSuchAlgorithmException e) {
128                            throw new RuntimeException("SHA Hash algorithm not found.", e);
129                    }
130            }
131            
132            /**
133             * Get the restricted token for a user, using IP addresses as location parameter. This implementation
134             * combines the "X-Forwarded-For" header with the remote address value so that unique
135             * values result with and without proxying. (The forwarded header is not trusted on its own
136             * because it can be most easily spoofed.)
137             * @param user source of token
138             * @return restricted token
139             */
140            public String getToken(DataUser user) {
141                    HttpServletRequest req = ((WebRequest) RequestCycle.get().getRequest()).getHttpServletRequest();
142                    String fwd = req.getHeader("X-Forwarded-For");
143                    if (fwd == null)
144                            fwd = "nil";
145                    MessageDigest digest = getDigest();
146                    user.getPassword().update(digest);
147                    digest.update((fwd + "-" + req.getRemoteAddr()).getBytes());
148                    byte[] hash = digest.digest(user.getUsername().getBytes());
149                    return new String(Base64UrlSafe.encodeBase64(hash));
150            }
151            
152    }