From 3309a1819d3cd168bc130f0ae57f96f47ec9919d Mon Sep 17 00:00:00 2001 From: mrajvaid Date: Wed, 16 Oct 2013 17:37:32 -0700 Subject: [PATCH] Basic Northbound APIs for User Management Change-Id: Ie0d36b9faf92fd0663fe17179d66e5f471f79e38 Signed-off-by: mrajvaid --- .../distribution/opendaylight/pom.xml | 6 +- .../northbound/usermanager/enunciate.xml | 12 ++ opendaylight/northbound/usermanager/pom.xml | 91 ++++++++ .../northbound/UserManagerNorthbound.java | 195 ++++++++++++++++++ .../src/main/resources/WEB-INF/web.xml | 85 ++++++++ .../northbound/UserManagerNorthboundTest.java | 33 +++ opendaylight/usermanager/api/pom.xml | 3 +- .../controller/usermanager/UserConfig.java | 30 ++- pom.xml | 1 + 9 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 opendaylight/northbound/usermanager/enunciate.xml create mode 100644 opendaylight/northbound/usermanager/pom.xml create mode 100644 opendaylight/northbound/usermanager/src/main/java/org/opendaylight/controller/usermanager/northbound/UserManagerNorthbound.java create mode 100644 opendaylight/northbound/usermanager/src/main/resources/WEB-INF/web.xml create mode 100644 opendaylight/northbound/usermanager/src/test/java/org/opendaylight/controller/subnets/northbound/UserManagerNorthboundTest.java diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 1744144333..4122357910 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -613,7 +613,11 @@ connectionmanager.northbound ${connectionmanager.version} - + + org.opendaylight.controller + usermanager.northbound + 0.0.1-SNAPSHOT + diff --git a/opendaylight/northbound/usermanager/enunciate.xml b/opendaylight/northbound/usermanager/enunciate.xml new file mode 100644 index 0000000000..a0b9546539 --- /dev/null +++ b/opendaylight/northbound/usermanager/enunciate.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/opendaylight/northbound/usermanager/pom.xml b/opendaylight/northbound/usermanager/pom.xml new file mode 100644 index 0000000000..8a85dbb622 --- /dev/null +++ b/opendaylight/northbound/usermanager/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + org.opendaylight.controller + commons.opendaylight + 1.4.1-SNAPSHOT + ../../commons/opendaylight + + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + https://wiki.opendaylight.org/view/OpenDaylight_Controller:Main + HEAD + + + usermanager.northbound + 0.0.1-SNAPSHOT + bundle + + + + org.codehaus.enunciate + maven-enunciate-plugin + ${enunciate.version} + + + org.apache.felix + maven-bundle-plugin + ${bundle.plugin.version} + true + + + + + + org.opendaylight.controller.sal.utils, + org.opendaylight.controller.northbound.commons, + org.opendaylight.controller.northbound.commons.exception, + org.opendaylight.controller.northbound.commons.utils, + com.sun.jersey.spi.container.servlet, + org.opendaylight.controller.sal.authorization, + org.opendaylight.controller.usermanager, + javax.ws.rs, + javax.ws.rs.core, + javax.xml.bind, + javax.xml.bind.annotation, + org.slf4j, + org.apache.catalina.filters, + org.codehaus.jackson.jaxrs, + !org.codehaus.enunciate.jaxrs + + + + /controller/nb/v2/usermanager + ,${classes;ANNOTATION;javax.ws.rs.Path} + + ${project.basedir}/src/main/resources/META-INF + + + + + + + org.opendaylight.controller + sal + 0.5.1-SNAPSHOT + + + org.opendaylight.controller + usermanager + 0.4.1-SNAPSHOT + + + org.opendaylight.controller + commons.northbound + 0.4.1-SNAPSHOT + + + org.codehaus.enunciate + enunciate-core-annotations + ${enunciate.version} + + + junit + junit + ${junit.version} + test + + + diff --git a/opendaylight/northbound/usermanager/src/main/java/org/opendaylight/controller/usermanager/northbound/UserManagerNorthbound.java b/opendaylight/northbound/usermanager/src/main/java/org/opendaylight/controller/usermanager/northbound/UserManagerNorthbound.java new file mode 100644 index 0000000000..ad7e774db0 --- /dev/null +++ b/opendaylight/northbound/usermanager/src/main/java/org/opendaylight/controller/usermanager/northbound/UserManagerNorthbound.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.usermanager.northbound; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; + +import org.codehaus.enunciate.jaxrs.ResponseCode; +import org.codehaus.enunciate.jaxrs.StatusCodes; +import org.codehaus.enunciate.jaxrs.TypeHint; +import org.opendaylight.controller.northbound.commons.RestMessages; +import org.opendaylight.controller.northbound.commons.exception.BadRequestException; +import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException; +import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException; +import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException; +import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils; +import org.opendaylight.controller.sal.authorization.UserLevel; +import org.opendaylight.controller.sal.utils.ServiceHelper; +import org.opendaylight.controller.sal.utils.Status; +import org.opendaylight.controller.usermanager.IUserManager; +import org.opendaylight.controller.usermanager.UserConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides REST APIs to manage users. + * This API will only be availalbe via HTTPS. + *
+ *
+ * Authentication scheme : HTTP Basic
+ * Authentication realm : opendaylight
+ * Transport : HTTPS
+ *
+ * HTTPS Authentication is disabled by default so to + * use UserManager APIs turn on HTTPS on Web Server + */ + +@Path("/") +public class UserManagerNorthbound { + protected static final Logger logger = LoggerFactory.getLogger(UserManagerNorthbound.class); + + private String username; + + @Context + public void setSecurityContext(SecurityContext context) { + if (context != null && context.getUserPrincipal() != null) { + username = context.getUserPrincipal().getName(); + } + } + + protected String getUserName() { + return username; + } + + private void handleNameMismatch(String name, String nameinURL) { + if (name == null || nameinURL == null) { + throw new BadRequestException(RestMessages.INVALIDDATA.toString() + " : Name is null"); + } + + if (name.equals(nameinURL)) { + return; + } + throw new ResourceConflictException(RestMessages.INVALIDDATA.toString() + + " : Name in URL does not match the name in request body"); + } + + /** + * Add a user + * + * @param userName + * name of new user to be added + * @param userConfigData + * the {@link UserConfig} user config structure in request body + * + * @return Response as dictated by the HTTP Response Status code + * + *
+     * Example:
+     *
+     * Request URL:
+     * https://localhost/controller/nb/v2/usermanager/user/testuser
+     *
+     * Request body in XML:
+     *  <userConfig>
+     *      <name>testuser</name>
+     *      <roles>Network-Admin</roles>
+     *      <password>pass!23</password>
+     *  </userConfig>
+     *
+     * Request body in JSON:
+     * {
+     *  "name":"testuser",
+     *  "password":"pass!23",
+     *  "roles":[
+     *       "Network-Admin"
+     *       ]
+     * }
+     * 
+ */ + + @Path("/user/{userName}") + @PUT + @StatusCodes({ @ResponseCode(code = 201, condition = "User created successfully"), + @ResponseCode(code = 400, condition = "Invalid data passed"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), + @ResponseCode(code = 409, condition = "User name in url conflicts with name in request body"), + @ResponseCode(code = 404, condition = "User config is null"), + @ResponseCode(code = 500, condition = "Internal Server Error: Addition of user failed"), + @ResponseCode(code = 503, condition = "Service unavailable") }) + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response addLocalUser(@PathParam("userName") String newUserName,@TypeHint(UserConfig.class) UserConfig userConfigData) { + + if (!isAdminUser()) { + throw new UnauthorizedException("User is not authorized to perform user management operations "); + } + + // Reconstructing the object so password can be hashed in userConfig + UserConfig userCfgObject = new UserConfig(userConfigData.getUser(),userConfigData.getPassword(), + userConfigData.getRoles()); + + handleNameMismatch(userCfgObject.getUser(), newUserName); + + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); + if (userManager == null) { + throw new ServiceUnavailableException("UserManager " + RestMessages.SERVICEUNAVAILABLE.toString()); + } + Status status = userManager.addLocalUser(userCfgObject); + if (status.isSuccess()) { + + NorthboundUtils.auditlog("User", username, "added", newUserName); + return Response.status(Response.Status.CREATED).build(); + } + return NorthboundUtils.getResponse(status); + } + + /** + * Delete a user + * + * @param userName + * name of user to be deleted + * @return Response as dictated by the HTTP Response Status code + * + *
+     * Example:
+     *
+     * Request URL:
+     * https://localhost/controller/nb/v2/usermanager/user/testuser
+     *
+     * 
+ */ + @Path("/user/{userName}") + @DELETE + @StatusCodes({ @ResponseCode(code = 204, condition = "User Deleted Successfully"), + @ResponseCode(code = 401, condition = "User not authorized to perform this operation"), + @ResponseCode(code = 404, condition = "The userName passed was not found"), + @ResponseCode(code = 500, condition = "Internal Server Error : Removal of user failed"), + @ResponseCode(code = 503, condition = "Service unavailable") }) + public Response removeLocalUser(@PathParam("userName") String userToBeRemoved) { + + if (!isAdminUser()) { + throw new UnauthorizedException("User is not authorized to perform user management operations "); + } + + IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); + if (userManager == null) { + throw new ServiceUnavailableException("UserManager " + RestMessages.SERVICEUNAVAILABLE.toString()); + } + Status status = userManager.removeLocalUser(userToBeRemoved); + if (status.isSuccess()) { + NorthboundUtils.auditlog("User", username, "removed", userToBeRemoved); + return Response.noContent().build(); + } + return NorthboundUtils.getResponse(status); + } + + private boolean isAdminUser(){ + // get UserManager's instance + IUserManager auth = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this); + // check if logged in user has privileges of NETWORK_ADMIN or SYSTEM_ADMIN, if so return true + return auth.getUserLevel(getUserName()).ordinal() <= UserLevel.NETWORKADMIN.ordinal(); + } + +} diff --git a/opendaylight/northbound/usermanager/src/main/resources/WEB-INF/web.xml b/opendaylight/northbound/usermanager/src/main/resources/WEB-INF/web.xml new file mode 100644 index 0000000000..9a191acf63 --- /dev/null +++ b/opendaylight/northbound/usermanager/src/main/resources/WEB-INF/web.xml @@ -0,0 +1,85 @@ + + + + JAXRSUserManager + com.sun.jersey.spi.container.servlet.ServletContainer + + javax.ws.rs.Application + org.opendaylight.controller.northbound.commons.NorthboundApplication + + 1 + + + + JAXRSUserManager + /* + + + CorsFilter + org.apache.catalina.filters.CorsFilter + + cors.allowed.origins + * + + + cors.allowed.methods + GET,POST,HEAD,OPTIONS,PUT,DELETE + + + cors.allowed.headers + Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers + + + cors.exposed.headers + Access-Control-Allow-Origin,Access-Control-Allow-Credentials + + + cors.support.credentials + true + + + cors.preflight.maxage + 10 + + + + CorsFilter + /* + + + + + NB api + /* + POST + GET + PUT + PATCH + DELETE + HEAD + + + System-Admin + Network-Admin + + + + CONFIDENTIAL + + + + + System-Admin + + + Network-Admin + + + + BASIC + opendaylight + + diff --git a/opendaylight/northbound/usermanager/src/test/java/org/opendaylight/controller/subnets/northbound/UserManagerNorthboundTest.java b/opendaylight/northbound/usermanager/src/test/java/org/opendaylight/controller/subnets/northbound/UserManagerNorthboundTest.java new file mode 100644 index 0000000000..03eb8a6c0c --- /dev/null +++ b/opendaylight/northbound/usermanager/src/test/java/org/opendaylight/controller/subnets/northbound/UserManagerNorthboundTest.java @@ -0,0 +1,33 @@ +package org.opendaylight.controller.subnets.northbound; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.opendaylight.controller.sal.authorization.AuthResultEnum; +import org.opendaylight.controller.usermanager.AuthResponse; +import org.opendaylight.controller.usermanager.UserConfig; + +public class UserManagerNorthboundTest { + + @Test + public void testUserConfigs() { + List roles = new ArrayList(); + roles.add("Network-Admin"); + + UserConfig userConfig = new UserConfig("test","testPass",roles); + + Assert.assertNotNull(userConfig); + Assert.assertNotNull(userConfig.getUser()); + Assert.assertNotNull(userConfig.getPassword()); + Assert.assertTrue(userConfig.getRoles().equals(roles)); + + + AuthResponse authResponse = userConfig.authenticate("testPass"); + Assert.assertNotNull(authResponse); + + Assert.assertEquals(AuthResultEnum.AUTH_ACCEPT_LOC,authResponse.getStatus()); + } + +} diff --git a/opendaylight/usermanager/api/pom.xml b/opendaylight/usermanager/api/pom.xml index 14a3059c60..5d9e6c70ce 100644 --- a/opendaylight/usermanager/api/pom.xml +++ b/opendaylight/usermanager/api/pom.xml @@ -47,7 +47,8 @@ org.apache.commons.lang3, org.springframework.security.authentication, org.springframework.security.core.authority, - org.springframework.security.core.userdetails + org.springframework.security.core.userdetails, + javax.xml.bind.annotation org.opendaylight.controller.usermanager, diff --git a/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java b/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java index 2e03db1655..6867ef4b98 100644 --- a/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java +++ b/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java @@ -18,21 +18,48 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + import org.opendaylight.controller.sal.authorization.AuthResultEnum; import org.opendaylight.controller.sal.utils.HexEncode; import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.sal.utils.StatusCode; -import org.opendaylight.controller.usermanager.AuthResponse; /** * Configuration Java Object which represents a Local AAA user configuration * information for User Manager. */ +@XmlRootElement +@XmlAccessorType(XmlAccessType.NONE) public class UserConfig implements Serializable { private static final long serialVersionUID = 1L; + /** + * User Id + */ + @XmlElement protected String user; + + /** + * List of roles a user can have + * example + * System-Admin + * Network-Admin + * Netowrk-Operator + */ + @XmlElement protected List roles; + + /** + * Password + * Should be 8 to 256 characters long, + * contain both upper and lower case letters, at least one number, + * and at least one non alphanumeric character. + */ + @XmlElement private String password; private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck"); @@ -41,6 +68,7 @@ public class UserConfig implements Serializable { protected static final String PASSWORD_REGEX = "(?=.*[^a-zA-Z0-9])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$"; private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)"); private static MessageDigest oneWayFunction = null; + static { try { UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1"); diff --git a/pom.xml b/pom.xml index f45fb62c61..97b3088685 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,7 @@ opendaylight/northbound/networkconfiguration/bridgedomain opendaylight/northbound/httpservice-bridge opendaylight/northbound/connectionmanager + opendaylight/northbound/usermanager opendaylight/northbound/integrationtest -- 2.36.6