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    }