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    }