<artifactId>connectionmanager.northbound</artifactId>
<version>${connectionmanager.version}</version>
</dependency>
-
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>usermanager.northbound</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </dependency>
<!-- Debug and logging -->
<dependency>
--- /dev/null
+<?xml version="1.0"?>
+<enunciate label="full" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.26.xsd">
+
+ <services>
+ <rest defaultRestSubcontext="/controller/nb/v2/usermanager"/>
+ </services>
+
+ <modules>
+ <docs docsDir="rest" title="User Management REST API" includeExampleXml="true" includeExampleJson="true"/>
+ </modules>
+</enunciate>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.opendaylight</artifactId>
+ <version>1.4.1-SNAPSHOT</version>
+ <relativePath>../../commons/opendaylight</relativePath>
+ </parent>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+ <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:Main</url>
+ <tag>HEAD</tag>
+ </scm>
+
+ <artifactId>usermanager.northbound</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.enunciate</groupId>
+ <artifactId>maven-enunciate-plugin</artifactId>
+ <version>${enunciate.version}</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ </Export-Package>
+ <Import-Package>
+ 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
+ </Import-Package>
+ <Export-Package>
+ </Export-Package>
+ <Web-ContextPath>/controller/nb/v2/usermanager</Web-ContextPath>
+ <Jaxrs-Resources>,${classes;ANNOTATION;javax.ws.rs.Path}</Jaxrs-Resources>
+ </instructions>
+ <manifestLocation>${project.basedir}/src/main/resources/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal</artifactId>
+ <version>0.5.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>usermanager</artifactId>
+ <version>0.4.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.northbound</artifactId>
+ <version>0.4.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.enunciate</groupId>
+ <artifactId>enunciate-core-annotations</artifactId>
+ <version>${enunciate.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+/*
+ * 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.
+ * <br>
+ * <br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b> HTTPS </b><br>
+ * <br>
+ * 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
+ *
+ * <pre>
+ * 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"
+ * ]
+ * }
+ * </pre>
+ */
+
+ @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
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * https://localhost/controller/nb/v2/usermanager/user/testuser
+ *
+ * </pre>
+ */
+ @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();
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+ <servlet>
+ <servlet-name>JAXRSUserManager</servlet-name>
+ <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value> org.opendaylight.controller.northbound.commons.NorthboundApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>JAXRSUserManager</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+ <filter>
+ <filter-name>CorsFilter</filter-name>
+ <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+ <init-param>
+ <param-name>cors.allowed.origins</param-name>
+ <param-value>*</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.allowed.methods</param-name>
+ <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.allowed.headers</param-name>
+ <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.exposed.headers</param-name>
+ <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.support.credentials</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.preflight.maxage</param-name>
+ <param-value>10</param-value>
+ </init-param>
+ </filter>
+ <filter-mapping>
+ <filter-name>CorsFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>NB api</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ <http-method>POST</http-method>
+ <http-method>GET</http-method>
+ <http-method>PUT</http-method>
+ <http-method>PATCH</http-method>
+ <http-method>DELETE</http-method>
+ <http-method>HEAD</http-method>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>System-Admin</role-name>
+ <role-name>Network-Admin</role-name>
+ </auth-constraint>
+ <!-- To enable SSL only access for this bundle, with this setting UserManager
+ northbound APIs will only be available over HTTPS-->
+ <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint>
+ </security-constraint>
+
+ <security-role>
+ <role-name>System-Admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>Network-Admin</role-name>
+ </security-role>
+
+ <login-config>
+ <auth-method>BASIC</auth-method>
+ <realm-name>opendaylight</realm-name>
+ </login-config>
+</web-app>
--- /dev/null
+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<String> roles = new ArrayList<String>();
+ 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());
+ }
+
+}
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
</Import-Package>
<Export-Package>
org.opendaylight.controller.usermanager,
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<String> 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");
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");
<module>opendaylight/northbound/networkconfiguration/bridgedomain</module>
<module>opendaylight/northbound/httpservice-bridge</module>
<module>opendaylight/northbound/connectionmanager</module>
+ <module>opendaylight/northbound/usermanager</module>
<!-- Northbound integration tests -->
<module>opendaylight/northbound/integrationtest</module>