Java tutorial
/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.security.sql.endpoint; import ch.entwine.weblounge.common.impl.language.LanguageUtils; import ch.entwine.weblounge.common.impl.security.PasswordEncoder; import ch.entwine.weblounge.common.impl.security.RoleImpl; import ch.entwine.weblounge.common.impl.security.SecurityUtils; import ch.entwine.weblounge.common.impl.security.SystemRole; import ch.entwine.weblounge.common.impl.security.WebloungeUserImpl; import ch.entwine.weblounge.common.language.UnknownLanguageException; import ch.entwine.weblounge.common.security.DigestType; import ch.entwine.weblounge.common.security.SecurityService; import ch.entwine.weblounge.common.security.User; import ch.entwine.weblounge.common.security.UserExistsException; import ch.entwine.weblounge.common.security.UserShadowedException; import ch.entwine.weblounge.common.security.WebloungeUser; import ch.entwine.weblounge.common.site.Site; import ch.entwine.weblounge.common.url.UrlUtils; import ch.entwine.weblounge.kernel.site.SiteManager; import ch.entwine.weblounge.security.sql.SQLDirectoryProvider; import ch.entwine.weblounge.security.sql.entities.JpaAccount; import ch.entwine.weblounge.security.sql.entities.JpaRole; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; /** * This class implements the <code>REST</code> endpoint to manage the SQL * directory provider. */ @Path("/") @Produces(MediaType.APPLICATION_XML) public class SQLDirectoryProviderEndpoint { /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(SQLDirectoryProviderEndpoint.class); /** The endpoint documentation */ private String docs = null; /** The cache configuration factory */ private SQLDirectoryProvider directory = null; /** The security service */ protected SecurityService securityService = null; /** The sites that are online */ protected SiteManager sites = null; @GET @Path("/") public Response getStatistics(@Context HttpServletRequest request) { Site site = getSite(request); try { StringBuilder stats = new StringBuilder(); stats.append("<directory id=\"").append(site.getIdentifier()).append("\">"); stats.append("<enabled>").append(directory.isSiteEnabled(site)).append("</enabled>"); stats.append("<users>").append(directory.getAccounts(site).size()).append("</users>"); stats.append("</directory>"); Response response = Response.ok(stats.toString()).build(); return response; } catch (Throwable t) { logger.warn("Error creating directory statistics: {}", t.getMessage()); return Response.serverError().build(); } } @PUT @Path("/status") public Response enableSite(@Context HttpServletRequest request) { Site site = getSite(request); // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); // Enable login try { if (directory.isSiteEnabled(site)) return Response.notModified().build(); directory.enableSite(site); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error enabling site logins: {}", t.getMessage()); return Response.serverError().build(); } } @DELETE @Path("/status") public Response disableSite(@Context HttpServletRequest request) { Site site = getSite(request); // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); // Disable login try { if (!directory.isSiteEnabled(site)) return Response.notModified().build(); directory.disableSite(site); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error disabling site logins: {}", t.getMessage()); return Response.serverError().build(); } } @POST @Path("/account") public Response createAccount(@FormParam("login") String login, @FormParam("password") String password, @FormParam("email") String eMail, @Context HttpServletRequest request) { // TODO: If not, return a one time pad that needs to be used when verifying // the e-mail // Check the arguments if (StringUtils.isBlank(login)) return Response.status(Status.BAD_REQUEST).build(); Response response = null; Site site = getSite(request); // Hash the password if (StringUtils.isNotBlank(password)) { logger.debug("Hashing password for user '{}@{}' using md5", login, site.getIdentifier()); password = PasswordEncoder.encode(StringUtils.trim(password)); } // Create the user try { JpaAccount account = directory.addAccount(site, login, password); account.setEmail(StringUtils.trimToNull(eMail)); directory.updateAccount(account); response = Response .created(new URI(UrlUtils.concat(request.getRequestURL().toString(), account.getLogin()))) .build(); } catch (UserExistsException e) { logger.warn("Error creating account: {}", e.getMessage()); return Response.status(Status.CONFLICT).build(); } catch (UserShadowedException e) { logger.warn("Error creating account: {}", e.getMessage()); return Response.status(Status.CONFLICT).build(); } catch (Throwable t) { logger.warn("Error creating account: {}", t.getMessage()); response = Response.serverError().build(); } return response; } @GET @Path("/account/{login}") public Response getAccount(@PathParam("login") String login, @Context HttpServletRequest request) { Site site = getSite(request); JpaAccount account = null; try { account = directory.getAccount(site, login); } catch (Throwable t) { logger.warn("Error accessing account '{}': {}", login, t.getMessage()); throw new WebApplicationException(); } if (account == null) throw new WebApplicationException(Status.NOT_FOUND); WebloungeUser wu = new WebloungeUserImpl(login, directory.getIdentifier()); wu.setFirstName(account.getFirstname()); wu.setLastName(account.getLastname()); wu.setEmail(account.getEmail()); wu.setInitials(account.getInitials()); wu.setLastLogin(account.getLastLoginDate(), account.getLastLoginFrom()); wu.setChallenge(account.getChallenge()); if (account.getLanguage() != null) wu.setLanguage(LanguageUtils.getLanguage(account.getLanguage())); if (account.getResponse() != null) wu.setResponse(account.getResponse().getBytes(Charset.forName("utf-8")), DigestType.md5); for (JpaRole r : account.getRoles()) { wu.addPublicCredentials(new RoleImpl(r.getContext(), r.getRolename())); } return Response.ok(wu.toXml()).build(); } @GET @Path("/account/{login}/activate") public Response activateAccount(@PathParam("login") String login, @QueryParam("activation") String activation, @Context HttpServletRequest request) { Site site = getSite(request); try { boolean success = directory.activateAccount(site, login, activation); return (success) ? Response.ok().build() : Response.status(Status.UNAUTHORIZED).build(); } catch (Throwable t) { logger.warn("Error activating account '{}': {}", login, t.getMessage()); return Response.serverError().build(); } } @PUT @Path("/account/{id}") public Response updateAccount(@PathParam("id") String login, @FormParam("password") String password, @FormParam("firstname") String firstname, @FormParam("lastname") String lastname, @FormParam("initials") String initials, @FormParam("email") String email, @FormParam("language") String language, @FormParam("challenge") String challenge, @FormParam("response") String response, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN) && !user.getLogin().equals(login)) return Response.status(Status.FORBIDDEN).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); // Hash the password if (StringUtils.isNotBlank(password)) { logger.debug("Hashing password for user '{}@{}' using md5", login, site.getIdentifier()); String digestPassword = PasswordEncoder.encode(StringUtils.trim(password)); account.setPassword(digestPassword); } account.setFirstname(StringUtils.trimToNull(firstname)); account.setLastname(StringUtils.trimToNull(lastname)); account.setInitials(StringUtils.trimToNull(initials)); account.setEmail(StringUtils.trimToNull(email)); // The language if (StringUtils.isNotBlank(language)) { try { account.setLanguage(LanguageUtils.getLanguage(language)); } catch (UnknownLanguageException e) { return Response.status(Status.BAD_REQUEST).build(); } } else { account.setLanguage(null); } // Hash the response if (StringUtils.isNotBlank(response)) { logger.debug("Hashing response for user '{}@{}' using md5", login, site.getIdentifier()); String digestResponse = PasswordEncoder.encode(StringUtils.trim(response)); account.setResponse(digestResponse); } directory.updateAccount(account); return Response.ok().build(); } catch (Throwable t) { return Response.serverError().build(); } } @PUT @Path("/account/{id}/password") public Response updateAccountPassword(@PathParam("id") String login, @FormParam("password") String password, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN) && !user.getLogin().equals(login)) return Response.status(Status.FORBIDDEN).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); // Hash the password if (StringUtils.isNotBlank(password)) { logger.debug("Hashing password for user '{}@{}' using md5", login, site.getIdentifier()); String digestPassword = PasswordEncoder.encode(StringUtils.trim(password)); account.setPassword(digestPassword); } else { account.setPassword(null); } directory.updateAccount(account); return Response.ok().build(); } catch (Throwable t) { return Response.serverError().build(); } } @PUT @Path("/account/{id}/challenge") public Response updateAccountChallenge(@PathParam("id") String login, @FormParam("challenge") String challenge, @FormParam("response") String response, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN) && !user.getLogin().equals(login)) return Response.status(Status.FORBIDDEN).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); // Set the challenge account.setChallenge(StringUtils.trimToNull(challenge)); // Hash the response if (StringUtils.isNotBlank(response)) { logger.debug("Hashing response for user '{}@{}' using md5", login, site.getIdentifier()); String digestResponse = PasswordEncoder.encode(StringUtils.trim(response)); account.setResponse(digestResponse); } else { account.setResponse(response); } directory.updateAccount(account); return Response.ok().build(); } catch (Throwable t) { return Response.serverError().build(); } } @DELETE @Path("/account/{id}") public Response deleteAccount(@PathParam("id") String login, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN) && !user.getLogin().equals(login)) return Response.status(Status.FORBIDDEN).build(); Site site = getSite(request); try { JpaAccount account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); directory.removeAccount(site, login); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error removing account '{}': {}", login, t.getMessage()); return Response.serverError().build(); } } @PUT @Path("/account/{id}/status") public Response enableAccount(@PathParam("id") String login, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); if (account.isEnabled()) return Response.status(Status.NOT_MODIFIED).build(); directory.enableAccount(site, login); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error enabling account '{}': {}", login, t.getMessage()); return Response.serverError().build(); } } @DELETE @Path("/account/{id}/status") public Response disableAccount(@PathParam("id") String login, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); if (!account.isEnabled()) return Response.status(Status.NOT_MODIFIED).build(); directory.disableAccount(site, login); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error disabling account '{}': {}", login, t.getMessage()); return Response.serverError().build(); } } @POST @Path("/account/{id}/roles/{context}") public Response addRole(@PathParam("id") String login, @PathParam("context") String context, @FormParam("role") String role, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); // Make sure a role has been provided as part of the request if (StringUtils.isBlank(role)) return Response.status(Status.BAD_REQUEST).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); if (account.hasRole(context, role)) return Response.status(Status.NOT_MODIFIED).build(); account.addRole(context, role); directory.updateAccount(account); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error adding role '{}:{}' to account {}: {}", new String[] { context, role, login, t.getMessage() }); return Response.serverError().build(); } } @DELETE @Path("/account/{id}/roles/{context}") public Response removeRole(@PathParam("id") String login, @PathParam("context") String context, @FormParam("role") String role, @Context HttpServletRequest request) { // Make sure that the user owns the roles required for this operation User user = securityService.getUser(); if (!SecurityUtils.userHasRole(user, SystemRole.SITEADMIN)) return Response.status(Status.FORBIDDEN).build(); // Make sure a role has been provided as part of the request if (StringUtils.isBlank(role)) return Response.status(Status.BAD_REQUEST).build(); JpaAccount account = null; Site site = getSite(request); try { account = directory.getAccount(site, login); if (account == null) return Response.status(Status.NOT_FOUND).build(); if (!account.hasRole(context, role)) return Response.status(Status.NOT_MODIFIED).build(); account.removeRole(context, role); directory.updateAccount(account); return Response.ok().build(); } catch (Throwable t) { logger.warn("Error adding role '{}:{}' to account: {}", new String[] { context, role, login, t.getMessage() }); return Response.serverError().build(); } } /** * Returns the endpoint documentation. * * @return the endpoint documentation */ @GET @Path("/docs") @Produces(MediaType.TEXT_HTML) public String getDocumentation(@Context HttpServletRequest request) { if (docs == null) { String docsPath = request.getRequestURI(); String docsPathExtension = request.getPathInfo(); String servicePath = request.getRequestURI().substring(0, docsPath.length() - docsPathExtension.length()); docs = SQLDirectoryProviderEndpointDocs.createDocumentation(servicePath); } return docs; } /** * Extracts the site from the request and returns it. If the site is not found * or it's not running, a corresponding <code>WebApplicationException</code> * is thrown. * * @param request * the http request * @return the site * @throws WebApplicationException * if the site is not found or is not running */ protected Site getSite(HttpServletRequest request) throws WebApplicationException { URL url = UrlUtils.toURL(request, false, false); Site site = sites.findSiteByURL(url); if (site == null) { throw new WebApplicationException(Status.NOT_FOUND); } else if (!site.isOnline()) { throw new WebApplicationException(Status.SERVICE_UNAVAILABLE); } return site; } /** * Callback for OSGi to set the site manager. * * @param siteManager * the site manager */ void setSiteManager(SiteManager siteManager) { this.sites = siteManager; } /** * Callback from the OSGi declarative services environment that will pass in a * reference to the directory provider. * * @param directoryProvider * the sql directory provider */ void setDiretoryProvider(SQLDirectoryProvider directoryProvider) { this.directory = directoryProvider; } /** * Callback from OSGi to set the security service. * * @param securityService * the security service */ void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return "SQL directory rest endpoint"; } }