001 /* 002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org 003 * Copyright (C) 2011 NightLabs Consulting GmbH 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program 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 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 package org.cumulus4j.keymanager.front.webapp; 019 020 import java.io.IOException; 021 import java.util.Arrays; 022 import java.util.Set; 023 024 import javax.ws.rs.Consumes; 025 import javax.ws.rs.DELETE; 026 import javax.ws.rs.GET; 027 import javax.ws.rs.POST; 028 import javax.ws.rs.PUT; 029 import javax.ws.rs.Path; 030 import javax.ws.rs.PathParam; 031 import javax.ws.rs.Produces; 032 import javax.ws.rs.WebApplicationException; 033 import javax.ws.rs.core.MediaType; 034 import javax.ws.rs.core.Response; 035 import javax.ws.rs.core.Response.Status; 036 037 import org.cumulus4j.keymanager.front.shared.Error; 038 import org.cumulus4j.keymanager.front.shared.User; 039 import org.cumulus4j.keymanager.front.shared.UserList; 040 import org.cumulus4j.keymanager.front.shared.UserWithPassword; 041 import org.cumulus4j.keystore.AuthenticationException; 042 import org.cumulus4j.keystore.CannotDeleteLastUserException; 043 import org.cumulus4j.keystore.KeyStore; 044 import org.cumulus4j.keystore.UserAlreadyExistsException; 045 import org.cumulus4j.keystore.UserNotFoundException; 046 import org.slf4j.Logger; 047 import org.slf4j.LoggerFactory; 048 049 /** 050 * REST service for user management. 051 * 052 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 053 */ 054 @Path("User") 055 @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) 056 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) 057 public class UserService extends AbstractService 058 { 059 private static final Logger logger = LoggerFactory.getLogger(UserService.class); 060 061 /** 062 * Create a new instance. 063 */ 064 public UserService() { 065 logger.info("logger: instantiated UserService"); 066 } 067 068 /** 069 * Get a {@link KeyStore}'s user identified by the given 070 * <code>keyStoreID</code> and <code>userName</code>. 071 * @param keyStoreID identifier of the {@link KeyStore} to work with. 072 * @param userName the user's name. 073 * @return the desired user or <code>null</code>, if there is no user with the given name 074 * in the specified <code>KeyStore</code>. 075 */ 076 @GET 077 @Path("{keyStoreID}/{userName}") 078 public User getUser(@PathParam("keyStoreID") String keyStoreID, @PathParam("userName") String userName) 079 { 080 logger.debug("getUser: entered"); 081 Auth auth = getAuth(); 082 try { 083 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 084 if (keyStore.getUsers(auth.getUserName(), auth.getPassword()).contains(userName)) 085 return new User(userName); 086 else 087 return null; 088 } catch (AuthenticationException e) { 089 logger.debug("getUser: " + e, e); // debug, because not an internal error 090 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 091 } catch (IOException e) { 092 logger.error("getUser: " + e, e); 093 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 094 } finally { 095 auth.clear(); 096 } 097 } 098 099 /** 100 * Get all users of the {@link KeyStore} identified by <code>keyStoreID</code>. 101 * @param keyStoreID identifier of the {@link KeyStore} to work with. 102 * @return all users of the {@link KeyStore} identified by <code>keyStoreID</code>. 103 */ 104 @GET 105 @Path("{keyStoreID}") 106 public UserList getUsers(@PathParam("keyStoreID") String keyStoreID) 107 { 108 logger.debug("getUsers: entered"); 109 UserList userList = new UserList(); 110 Auth auth = getAuth(); 111 try { 112 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 113 Set<String> userNames = keyStore.getUsers(auth.getUserName(), auth.getPassword()); 114 for (String userName : userNames) { 115 userList.getUsers().add(new User(userName)); 116 } 117 } catch (AuthenticationException e) { 118 logger.debug("getUsers: " + e, e); // debug, because not an internal error 119 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 120 } catch (IOException e) { 121 logger.error("getUsers: " + e, e); 122 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 123 } finally { 124 auth.clear(); 125 } 126 return userList; 127 } 128 129 // @PUT 130 // @Path("{keyStoreID}/{userName}") 131 // public void putUserWithUserNamePath(@PathParam("keyStoreID") String keyStoreID, @PathParam("userName") String userName, UserWithPassword userWithPassword) 132 // { 133 // logger.debug("putUserWithUserNamePath: entered"); 134 // 135 // if (userName == null) 136 // throw new IllegalArgumentException("How the hell can userName be null?!"); 137 // 138 // if (userWithPassword == null) 139 // throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Missing request-entity!")).build()); 140 // 141 // if (userWithPassword.getUserName() == null) 142 // userWithPassword.setUserName(userName); 143 // else if (!userName.equals(userWithPassword.getUserName())) 144 // throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Path's userName='" + userName + "' does not match entity's userName='" + userWithPassword.getUserName() + "'!")).build()); 145 // 146 // putUser(keyStoreID, userWithPassword); 147 // } 148 149 /** 150 * Compatibility for clients not supporting <code>PUT</code>. This method does the same as (it delegates to) 151 * {@link #putUser(String, UserWithPassword)}. Ajax-Clients (e.g. jQuery in Firefox) seem 152 * not to support <code>PUT</code>. 153 */ 154 @POST 155 @Path("{keyStoreID}") 156 public void postUser(@PathParam("keyStoreID") String keyStoreID, UserWithPassword userWithPassword) 157 { 158 putUser(keyStoreID, userWithPassword); 159 } 160 161 /** 162 * Put a user. If a user with the same {@link UserWithPassword#getUserName() name} already exists, 163 * it is updated, otherwise the new user is added to the {@link KeyStore} identified by <code>keyStoreID</code>. 164 * @param keyStoreID identifier of the {@link KeyStore} to work with. 165 * @param userWithPassword the user's information to be stored. 166 */ 167 @PUT 168 @Path("{keyStoreID}") 169 public void putUser(@PathParam("keyStoreID") String keyStoreID, UserWithPassword userWithPassword) 170 { 171 logger.debug("putUser: entered"); 172 173 if (userWithPassword == null) 174 throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Missing request-entity!")).build()); 175 176 Auth auth = getAuth(); 177 178 try { 179 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 180 try { 181 keyStore.createUser( 182 auth.getUserName(), auth.getPassword(), 183 userWithPassword.getUserName(), userWithPassword.getPassword() == null ? null : userWithPassword.getPassword().toCharArray() 184 ); 185 } catch (UserAlreadyExistsException e) { 186 try { 187 keyStore.changeUserPassword( 188 auth.getUserName(), auth.getPassword(), 189 userWithPassword.getUserName(), userWithPassword.getPassword() == null ? null : userWithPassword.getPassword().toCharArray() 190 ); 191 } catch (UserNotFoundException e1) { 192 logger.error("Why does it not exist? Has the user just been deleted?!", e1); 193 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).build()); 194 } 195 } 196 } catch (AuthenticationException e) { 197 logger.debug("putUser: " + e, e); // debug, because not an internal error 198 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 199 } catch (IOException e) { 200 logger.error("putUser: " + e, e); 201 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 202 } finally { 203 // extra safety => overwrite passwords 204 auth.clear(); 205 206 if (userWithPassword.getPassword() != null) 207 Arrays.fill(userWithPassword.getPassword() == null ? null : userWithPassword.getPassword().toCharArray(), (char)0); 208 } 209 } 210 211 /** 212 * Delete a user. 213 * @param keyStoreID identifier of the {@link KeyStore} to work with. 214 * @param userName the {@link User#getUserName() name} of the user to be deleted. 215 */ 216 @DELETE 217 @Path("{keyStoreID}/{userName}") 218 public void deleteUser(@PathParam("keyStoreID") String keyStoreID, @PathParam("userName") String userName) 219 { 220 logger.debug("deleteUser: entered"); 221 222 Auth auth = getAuth(); 223 224 try { 225 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 226 keyStore.deleteUser(auth.getUserName(), auth.getPassword(), userName); 227 } catch (AuthenticationException e) { 228 logger.debug("deleteUser: " + e, e); // debug, because not an internal error 229 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 230 } catch (UserNotFoundException e) { 231 // ignore in order to be idempotent - only warn 232 logger.warn("deleteUser: " + e); 233 } catch (CannotDeleteLastUserException e) { 234 logger.debug("deleteUser: " + e, e); // debug, because not an internal error 235 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 236 } catch (IOException e) { 237 logger.error("deleteUser: " + e, e); 238 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 239 } finally { 240 // extra safety => overwrite password 241 auth.clear(); 242 } 243 } 244 }