Merge "Untangle the XML/Hello message decoders"
[controller.git] / opendaylight / web / root / src / main / java / org / opendaylight / controller / web / DaylightWebAdmin.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.web;
10
11 import java.net.InetAddress;
12 import java.net.UnknownHostException;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.Set;
16
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpSession;
19
20 import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
21 import org.opendaylight.controller.connectionmanager.IConnectionManager;
22 import org.opendaylight.controller.sal.authorization.UserLevel;
23 import org.opendaylight.controller.sal.core.Description;
24 import org.opendaylight.controller.sal.core.Node;
25 import org.opendaylight.controller.sal.utils.GlobalConstants;
26 import org.opendaylight.controller.sal.utils.ServiceHelper;
27 import org.opendaylight.controller.sal.utils.Status;
28 import org.opendaylight.controller.sal.utils.StatusCode;
29 import org.opendaylight.controller.switchmanager.ISwitchManager;
30 import org.opendaylight.controller.usermanager.IUserManager;
31 import org.opendaylight.controller.usermanager.UserConfig;
32 import org.springframework.stereotype.Controller;
33 import org.springframework.web.bind.annotation.PathVariable;
34 import org.springframework.web.bind.annotation.RequestMapping;
35 import org.springframework.web.bind.annotation.RequestMethod;
36 import org.springframework.web.bind.annotation.RequestParam;
37 import org.springframework.web.bind.annotation.ResponseBody;
38
39 import com.google.gson.Gson;
40
41 @Controller
42 @RequestMapping("/admin")
43 public class DaylightWebAdmin {
44     Gson gson = new Gson();
45
46     /**
47      * Returns list of clustered controllers. Highlights "this" controller and
48      * if controller is coordinator
49      *
50      * @return List<ClusterBean>
51      */
52     @RequestMapping("/cluster")
53     @ResponseBody
54     public String getClusteredControllers() {
55         IClusterGlobalServices clusterServices = (IClusterGlobalServices) ServiceHelper.getGlobalInstance(
56                 IClusterGlobalServices.class, this);
57         if (clusterServices == null) {
58             return null;
59         }
60         IConnectionManager connectionManager = (IConnectionManager) ServiceHelper.getGlobalInstance(
61                 IConnectionManager.class, this);
62         if (connectionManager == null) {
63             return null;
64         }
65
66         List<ClusterNodeBean> clusterNodes = new ArrayList<ClusterNodeBean>();
67
68         List<InetAddress> controllers = clusterServices.getClusteredControllers();
69         for (InetAddress controller : controllers) {
70             ClusterNodeBean.Builder clusterBeanBuilder = new ClusterNodeBean.Builder(controller);
71
72             // get number of connected nodes
73             Set<Node> connectedNodes = connectionManager.getNodes(controller);
74             int numNodes = connectedNodes == null ? 0 : connectedNodes.size();
75             clusterBeanBuilder.nodesConnected(numNodes);
76
77             // determine if this is the executing controller
78             if (controller.equals(clusterServices.getMyAddress())) {
79                 clusterBeanBuilder.highlightMe();
80             }
81
82             // determine whether this is coordinator
83             if (clusterServices.getCoordinatorAddress().equals(controller)) {
84                 clusterBeanBuilder.iAmCoordinator();
85             }
86             clusterNodes.add(clusterBeanBuilder.build());
87         }
88
89         return gson.toJson(clusterNodes);
90     }
91
92     /**
93      * Return nodes connected to controller {controller}
94      *
95      * @param controller
96      *            - byte[] of the address of the controller
97      * @return List<NodeBean>
98      */
99     @RequestMapping("/cluster/controller/{controller}")
100     @ResponseBody
101     public String getNodesConnectedToController(@PathVariable("controller") String controller) {
102         IClusterGlobalServices clusterServices = (IClusterGlobalServices) ServiceHelper.getGlobalInstance(
103                 IClusterGlobalServices.class, this);
104         if (clusterServices == null) {
105             return null;
106         }
107         IConnectionManager connectionManager = (IConnectionManager) ServiceHelper.getGlobalInstance(
108                 IConnectionManager.class, this);
109         if (connectionManager == null) {
110             return null;
111         }
112         ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class,
113                 GlobalConstants.DEFAULT.toString(), this);
114         if (switchManager == null) {
115             return null;
116         }
117
118         byte[] address = gson.fromJson(controller, byte[].class);
119         InetAddress controllerAddress = null;
120         try {
121             controllerAddress = InetAddress.getByAddress(address);
122         } catch (UnknownHostException e) {
123             return null;
124         }
125
126         List<NodeBean> result = new ArrayList<NodeBean>();
127
128         Set<Node> nodes = connectionManager.getNodes(controllerAddress);
129         if (nodes == null) {
130             return gson.toJson(result);
131         }
132         for (Node node : nodes) {
133             Description description = (Description) switchManager.getNodeProp(node, Description.propertyName);
134             NodeBean nodeBean;
135             if (description == null || description.getValue().equals("None")) {
136                 nodeBean = new NodeBean(node);
137             } else {
138                 nodeBean = new NodeBean(node, description.getValue());
139             }
140             result.add(nodeBean);
141         }
142
143         return gson.toJson(result);
144     }
145
146     @RequestMapping(value = "/users", method = RequestMethod.GET)
147     @ResponseBody
148     public List<UserBean> getUsers() {
149         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
150         if (userManager == null) {
151             return null;
152         }
153
154         List<UserBean> result = new ArrayList<UserBean>();
155         List<UserConfig> configs = userManager.getLocalUserList();
156         for (UserConfig config : configs) {
157             UserBean bean = new UserBean(config);
158             result.add(bean);
159         }
160
161         return result;
162     }
163
164     /*
165      * Password in clear text, moving to HTTP/SSL soon
166      */
167     @RequestMapping(value = "/users", method = RequestMethod.POST)
168     @ResponseBody
169     public Status saveLocalUserConfig(@RequestParam(required = true) String json,
170             @RequestParam(required = true) String action, HttpServletRequest request) {
171
172         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
173         if (userManager == null) {
174             return new Status(StatusCode.NOSERVICE, "User Manager unavailable");
175         }
176
177         if (!authorize(userManager, UserLevel.NETWORKADMIN, request)) {
178             return new Status(StatusCode.UNAUTHORIZED, "Operation not permitted");
179         }
180
181         Gson gson = new Gson();
182         UserConfig plainConfig = gson.fromJson(json, UserConfig.class);
183         // Recreate using the proper constructor which will hash the password
184         UserConfig config = new UserConfig(plainConfig.getUser(), plainConfig.getPassword(), plainConfig.getRoles());
185
186         Status result = (action.equals("add")) ? userManager.addLocalUser(config) : userManager.removeLocalUser(config);
187         if (result.isSuccess()) {
188             if (action.equals("add")) {
189                 DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "added", config.getUser()
190                         + " as " + config.getRoles().toString());
191             } else {
192                 DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "removed", config.getUser());
193             }
194         }
195         return result;
196     }
197
198     @RequestMapping(value = "/user/modify", method = RequestMethod.POST)
199     @ResponseBody
200     public Status modifyUser(@RequestParam(required = true) String json,
201             @RequestParam(required = true) String action, HttpServletRequest request) {
202
203         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
204         if (userManager == null) {
205             return new Status(StatusCode.NOSERVICE, "User Manager unavailable");
206         }
207
208         if (!authorize(userManager, UserLevel.NETWORKADMIN, request)) {
209             return new Status(StatusCode.UNAUTHORIZED, "Operation not permitted");
210         }
211
212         UserConfig newConfig = gson.fromJson(json, UserConfig.class);
213         List<UserConfig> currentUserConfig = userManager.getLocalUserList();
214         String password = null;
215         byte[] salt = null;
216         String user = newConfig.getUser();
217         for (UserConfig userConfig : currentUserConfig) {
218             if(userConfig.getUser().equals(user)){
219                 password = userConfig.getPassword();
220                 salt = userConfig.getSalt();
221                 break;
222             }
223         }
224         if (password == null) {
225             String msg = String.format("User %s not found in configuration database", user);
226             return new Status(StatusCode.NOTFOUND, msg);
227         }
228
229         //While modifying a user role, the password is not provided from GUI for any user.
230         //The password is stored in hash mode, hence it cannot be retrieved and added to UserConfig object
231         //The hashed password is injected below to the json string containing username and new roles before
232         //converting to UserConfig object.
233         Gson gson = new Gson();
234         json = json.replace("\"roles\"", "\"salt\":" + gson.toJson(salt, salt.getClass()) + ",\"password\":\""+ password + "\",\"roles\"");
235
236         newConfig = gson.fromJson(json, UserConfig.class);
237
238         Status result = userManager.modifyLocalUser(newConfig);
239         if (result.isSuccess()) {
240             DaylightWebUtil.auditlog("Roles of", request.getUserPrincipal().getName(), "updated", newConfig.getUser()
241                     + " to " + newConfig.getRoles().toString());
242         }
243         return result;
244     }
245
246
247     @RequestMapping(value = "/users/{username}", method = RequestMethod.POST)
248     @ResponseBody
249     public Status removeLocalUser(@PathVariable("username") String userName, HttpServletRequest request) {
250
251         String loggedInUser = request.getUserPrincipal().getName();
252         if (loggedInUser.equals(userName)) {
253             String msg = "Invalid Request: User cannot delete itself";
254             return new Status(StatusCode.NOTALLOWED, msg);
255         }
256
257         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
258         if (userManager == null) {
259             return new Status(StatusCode.NOSERVICE, "User Manager unavailable");
260         }
261
262         if (!authorize(userManager, UserLevel.NETWORKADMIN, request)) {
263             return new Status(StatusCode.UNAUTHORIZED, "Operation not permitted");
264         }
265
266         Status status = userManager.removeLocalUser(userName);
267         if (status.isSuccess()) {
268             DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "removed", userName);
269             return status;
270         }
271         return status;
272     }
273
274     @RequestMapping(value = "/users/password/{username}", method = RequestMethod.POST)
275     @ResponseBody
276     public Status changePassword(
277             @PathVariable("username") String username, HttpServletRequest request,
278             @RequestParam(value = "currentPassword", required=false) String currentPassword,
279             @RequestParam("newPassword") String newPassword) {
280         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
281         if (userManager == null) {
282             return new Status(StatusCode.NOSERVICE, "User Manager unavailable");
283         }
284
285         Status status;
286         String requestingUser = request.getUserPrincipal().getName();
287
288         //changing own password
289         if (requestingUser.equals(username) ) {
290             status = userManager.changeLocalUserPassword(username, currentPassword, newPassword);
291             //enforce the user to re-login with new password
292             if (status.isSuccess() && !newPassword.equals(currentPassword)) {
293                 userManager.userLogout(username);
294                 HttpSession session = request.getSession(false);
295                 if ( session != null) {
296                     session.invalidate();
297                 }
298             }
299
300         //admin level user resetting other's password
301         } else if (authorize(userManager, UserLevel.NETWORKADMIN, request)) {
302
303             //Since User Manager doesn't have an unprotected password change API,
304             //we re-create the user with the new password (and current roles).
305             List<String> roles = userManager.getUserRoles(username);
306             UserConfig newConfig = new UserConfig(username, newPassword, roles);
307
308             //validate before removing existing config, so we don't remove but fail to add
309             status = newConfig.validate();
310             if (!status.isSuccess()) {
311                 return status;
312             }
313
314             userManager.userLogout(username);
315             status = userManager.removeLocalUser(username);
316             if (!status.isSuccess()) {
317                 return status;
318             }
319             if (userManager.addLocalUser(newConfig).isSuccess()) {
320                 status = new Status(StatusCode.SUCCESS, "Password for user " + username + " reset successfully.");
321             } else {
322                 //unexpected
323                 status = new Status(StatusCode.INTERNALERROR, "Failed resetting password for user " + username + ". User is now removed.");
324             }
325
326         //unauthorized
327         } else {
328             status = new Status(StatusCode.UNAUTHORIZED, "Operation not permitted");
329         }
330
331         if (status.isSuccess()) {
332             DaylightWebUtil.auditlog("User", request.getUserPrincipal().getName(), "changed password for",
333                     username);
334         }
335         return status;
336     }
337
338     /**
339      * Is the operation permitted for the given level
340      *
341      * @param level
342      */
343     private boolean authorize(IUserManager userManager, UserLevel level, HttpServletRequest request) {
344         String username = request.getUserPrincipal().getName();
345         UserLevel userLevel = userManager.getUserLevel(username);
346         return userLevel.toNumber() <= level.toNumber();
347     }
348 }