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 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 090 } catch (IOException e) { 091 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 092 } finally { 093 auth.clear(); 094 } 095 } 096 097 /** 098 * Get all users of the {@link KeyStore} identified by <code>keyStoreID</code>. 099 * @param keyStoreID identifier of the {@link KeyStore} to work with. 100 * @return all users of the {@link KeyStore} identified by <code>keyStoreID</code>. 101 */ 102 @GET 103 @Path("{keyStoreID}") 104 public UserList getUsers(@PathParam("keyStoreID") String keyStoreID) 105 { 106 logger.debug("getUsers: entered"); 107 UserList userList = new UserList(); 108 Auth auth = getAuth(); 109 try { 110 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 111 Set<String> userNames = keyStore.getUsers(auth.getUserName(), auth.getPassword()); 112 for (String userName : userNames) { 113 userList.getUsers().add(new User(userName)); 114 } 115 } catch (AuthenticationException e) { 116 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 117 } catch (IOException e) { 118 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 119 } finally { 120 auth.clear(); 121 } 122 return userList; 123 } 124 125 // @PUT 126 // @Path("{keyStoreID}/{userName}") 127 // public void putUserWithUserNamePath(@PathParam("keyStoreID") String keyStoreID, @PathParam("userName") String userName, UserWithPassword userWithPassword) 128 // { 129 // logger.debug("putUserWithUserNamePath: entered"); 130 // 131 // if (userName == null) 132 // throw new IllegalArgumentException("How the hell can userName be null?!"); 133 // 134 // if (userWithPassword == null) 135 // throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Missing request-entity!")).build()); 136 // 137 // if (userWithPassword.getUserName() == null) 138 // userWithPassword.setUserName(userName); 139 // else if (!userName.equals(userWithPassword.getUserName())) 140 // throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Path's userName='" + userName + "' does not match entity's userName='" + userWithPassword.getUserName() + "'!")).build()); 141 // 142 // putUser(keyStoreID, userWithPassword); 143 // } 144 145 /** 146 * Compatibility for clients not supporting <code>PUT</code>. This method does the same as (it delegates to) 147 * {@link #putUser(String, UserWithPassword)}. Ajax-Clients (e.g. jQuery in Firefox) seem 148 * not to support <code>PUT</code>. 149 */ 150 @POST 151 @Path("{keyStoreID}") 152 public void postUser(@PathParam("keyStoreID") String keyStoreID, UserWithPassword userWithPassword) 153 { 154 putUser(keyStoreID, userWithPassword); 155 } 156 157 /** 158 * Put a user. If a user with the same {@link UserWithPassword#getUserName() name} already exists, 159 * it is updated, otherwise the new user is added to the {@link KeyStore} identified by <code>keyStoreID</code>. 160 * @param keyStoreID identifier of the {@link KeyStore} to work with. 161 * @param userWithPassword the user's information to be stored. 162 */ 163 @PUT 164 @Path("{keyStoreID}") 165 public void putUser(@PathParam("keyStoreID") String keyStoreID, UserWithPassword userWithPassword) 166 { 167 logger.debug("putUser: entered"); 168 169 if (userWithPassword == null) 170 throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Missing request-entity!")).build()); 171 172 Auth auth = getAuth(); 173 174 try { 175 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 176 try { 177 keyStore.createUser( 178 auth.getUserName(), auth.getPassword(), 179 userWithPassword.getUserName(), userWithPassword.getPassword() 180 ); 181 } catch (UserAlreadyExistsException e) { 182 try { 183 keyStore.changeUserPassword( 184 auth.getUserName(), auth.getPassword(), 185 userWithPassword.getUserName(), userWithPassword.getPassword() 186 ); 187 } catch (UserNotFoundException e1) { 188 logger.error("Why does it not exist? Has the user just been deleted?!", e1); 189 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).build()); 190 } 191 } 192 } catch (AuthenticationException e) { 193 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 194 } catch (IOException e) { 195 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 196 } finally { 197 // extra safety => overwrite passwords 198 auth.clear(); 199 200 if (userWithPassword.getPassword() != null) 201 Arrays.fill(userWithPassword.getPassword(), (char)0); 202 } 203 } 204 205 /** 206 * Delete a user. 207 * @param keyStoreID identifier of the {@link KeyStore} to work with. 208 * @param userName the {@link User#getUserName() name} of the user to be deleted. 209 */ 210 @DELETE 211 @Path("{keyStoreID}/{userName}") 212 public void deleteUser(@PathParam("keyStoreID") String keyStoreID, @PathParam("userName") String userName) 213 { 214 logger.debug("deleteUser: entered"); 215 216 Auth auth = getAuth(); 217 218 try { 219 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID); 220 keyStore.deleteUser(auth.getUserName(), auth.getPassword(), userName); 221 } catch (AuthenticationException e) { 222 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 223 } catch (UserNotFoundException e) { 224 // ignore in order to be idempotent - only warn 225 logger.warn("deleteUser: " + e); 226 } catch (CannotDeleteLastUserException e) { 227 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build()); 228 } catch (IOException e) { 229 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build()); 230 } finally { 231 // extra safety => overwrite password 232 auth.clear(); 233 } 234 } 235 }