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 }