package com.dexels.oauth.web; import java.io.IOException; import java.security.spec.InvalidKeySpecException; import java.util.Map; import java.util.UUID; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.dexels.oauth.api.AccessTokenStore; import com.dexels.oauth.api.ActivationTokenStore; import com.dexels.oauth.api.AuthorizationTokenStore; import com.dexels.oauth.api.ClientStore; import com.dexels.oauth.api.HelpdeskTokenStore; import com.dexels.oauth.api.PasswordResetTokenStore; import com.dexels.oauth.api.RefreshTokenStore; import com.dexels.oauth.api.ScopeStore; import com.dexels.oauth.api.SideChannel; import com.dexels.oauth.api.User; import com.dexels.oauth.api.UserStore; import com.dexels.oauth.web.commands.OAuthCommandFactory; import com.dexels.oauth.web.exceptions.OAuthClientException; import com.dexels.oauth.web.exceptions.OAuthServerException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @Component( name = "dexels.oauth.servlet", service = Servlet.class, immediate = true, property = { "alias=/oauth", "servlet-name=oauth"}, configurationPolicy = ConfigurationPolicy.REQUIRE ) public class OAuthServlet extends HttpServlet { enum PasswordHashMethod { NONE, PBKDF2 } private static final long serialVersionUID = -1948354354961917987L; private final static Logger logger = LoggerFactory.getLogger(OAuthServlet.class); private ClientStore clientStore; private AuthorizationTokenStore authorizationTokenStore; private ScopeStore scopeStore; private ActivationTokenStore activationTokenStore; private PasswordResetTokenStore passwordResetTokenStore; private AccessTokenStore accessTokenStore; private RefreshTokenStore refreshTokenStore; private HelpdeskTokenStore helpdeskTokenStore; private UserStore userStore; private SideChannel sideChannel; private static String encryptionKey; private static PasswordHashMethod passwordHashMethod; @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { OAuthCommandFactory.create(request.getPathInfo(), request, response).execute(); } catch (OAuthClientException e) { // this should be a json response with a 400, this is the fault of the client developer response.setStatus(e.getStatusCode()); response.setContentType("application/json"); ObjectMapper mapper = new ObjectMapper(); ObjectNode rootNode = mapper.createObjectNode(); rootNode.put("error", e.getMessage()); mapper.writerWithDefaultPrettyPrinter().writeValue(response.getWriter(), rootNode); } catch (OAuthServerException | RuntimeException e) { // something went wrong on the server. String uuid = UUID.randomUUID().toString(); logger.error("Exception in handling oauth request: " + uuid, e); if (e instanceof OAuthServerException && ((OAuthServerException) e).hasForwarder()) { // If this call originated from the web impl, we should // return a 200, but append the error at the end of the query string try { ((OAuthServerException) e).forward(uuid); return; } catch (Throwable t) { // forwarding went wrong, give up and fall back on json response } } // if this call originated from an api request, we should return a 500 (preferably in json format) response.setStatus(500); response.setContentType("application/json"); ObjectMapper mapper = new ObjectMapper(); ObjectNode rootNode = mapper.createObjectNode(); rootNode.put("error", uuid); mapper.writerWithDefaultPrettyPrinter().writeValue(response.getWriter(), rootNode); } } @Activate public void activate(Map settings) { encryptionKey = (String) settings.get("encryptionKey"); passwordHashMethod = PasswordHashMethod.valueOf((String) settings.get("passwordHashMethod")); if (passwordHashMethod == null) { throw new RuntimeException("No password hash method set. Please configure this in dexels.oauth.servlet.cfg"); } } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearSideChannel") public void setSideChannel(SideChannel sideChannel) { this.sideChannel = sideChannel; } public void clearSideChannel(SideChannel sideChannel) { this.sideChannel = null; } public SideChannel getSideChannel() { return sideChannel; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearRefreshTokenStore") public void setRefreshTokenStore(RefreshTokenStore refreshTokenStore) { this.refreshTokenStore = refreshTokenStore; } public void clearRefreshTokenStore(RefreshTokenStore refreshTokenStore) { this.refreshTokenStore = null; } public RefreshTokenStore getRefreshTokenStore() { return refreshTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearClientStore") public void setClientStore(ClientStore clientStore) { this.clientStore = clientStore; } public void clearClientStore(ClientStore clientStore) { this.clientStore = null; } public ClientStore getClientStore() { return clientStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearAuthorizationTokenStore") public void setAuthorizationTokenStore(AuthorizationTokenStore authorizationTokenStore) { this.authorizationTokenStore = authorizationTokenStore; } public void clearAuthorizationTokenStore(AuthorizationTokenStore authorizationTokenStore) { this.authorizationTokenStore = null; } public AuthorizationTokenStore getAuthorizationTokenStore() { return authorizationTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearScopeStore") public void setScopeStore(ScopeStore scopeStore) { this.scopeStore = scopeStore; } public void clearScopeStore(ScopeStore scopeStore) { this.scopeStore = null; } public ScopeStore getScopeStore() { return scopeStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearAccessTokenStore") public void setAccessTokenStore(AccessTokenStore accessTokenStore) { this.accessTokenStore = accessTokenStore; } public void clearAccessTokenStore(AccessTokenStore accessTokenStore) { this.accessTokenStore = null; } public AccessTokenStore getAccessTokenStore() { return accessTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearActivationTokenStore") public void setActivationTokenStore(ActivationTokenStore activationTokenStore) { this.activationTokenStore = activationTokenStore; } public void clearActivationTokenStore(ActivationTokenStore activationTokenStore) { this.activationTokenStore = null; } public ActivationTokenStore getActivationTokenStore() { return activationTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearPasswordResetTokenStore") public void setPasswordResetTokenStore(PasswordResetTokenStore passwordResetTokenStore) { this.passwordResetTokenStore = passwordResetTokenStore; } public void clearPasswordResetTokenStore(PasswordResetTokenStore passwordResetTokenStore) { this.passwordResetTokenStore = null; } public PasswordResetTokenStore getPasswordResetTokenStore() { return passwordResetTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearHelpdeskTokenStore") public void setHelpdeskTokenStore(HelpdeskTokenStore helpdeskTokenStore) { this.helpdeskTokenStore = helpdeskTokenStore; } public void clearHelpdeskTokenStore(HelpdeskTokenStore helpdeskTokenStore) { this.helpdeskTokenStore = null; } public HelpdeskTokenStore getHelpdeskTokenStore() { return helpdeskTokenStore; } @Reference(policy = ReferencePolicy.DYNAMIC, unbind = "clearUserStore") public void setUserStore(UserStore userStore) { this.userStore = userStore; } public void clearUserStore(UserStore userStore) { this.userStore = null; } public UserStore getUserStore() { return userStore; } public static String getEncryptionString() { return encryptionKey; } public static String hashPassword(String password) { switch (passwordHashMethod) { case NONE: return password; case PBKDF2: try { return PBKDF2.createHash(password); } catch (InvalidKeySpecException e) { throw new RuntimeException("Error in creating hash!", e); } } return null; } public static boolean verifyPassword(User user, String password) { switch (passwordHashMethod) { case NONE: return user.getHashedPassword().equals(password); case PBKDF2: try { return PBKDF2.validatePassword(password, user.getHashedPassword()); } catch (InvalidKeySpecException e) { throw new RuntimeException("Error in validating hash!", e); } } return false; } }