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 }