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 }