aboutsummaryrefslogtreecommitdiffstats
path: root/odl-aaa-moon/aaa-idmlight
diff options
context:
space:
mode:
Diffstat (limited to 'odl-aaa-moon/aaa-idmlight')
-rw-r--r--odl-aaa-moon/aaa-idmlight/pom.xml229
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java57
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java208
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java118
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java591
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java228
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java419
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java46
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java90
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java29
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml79
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py247
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml26
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang28
-rw-r--r--odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java93
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/cleardb.sh5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/domain.json5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/domain2.json5
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/grant.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/grant2.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/result.json1
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/role-admin.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/role-user.json4
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/test.sh308
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/user.json7
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/user2.json7
-rw-r--r--odl-aaa-moon/aaa-idmlight/tests/userpwd.json4
27 files changed, 2846 insertions, 0 deletions
diff --git a/odl-aaa-moon/aaa-idmlight/pom.xml b/odl-aaa-moon/aaa-idmlight/pom.xml
new file mode 100644
index 00000000..5a86b0d1
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/pom.xml
@@ -0,0 +1,229 @@
+<?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.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.1-Beryllium-SR1</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-idmlight</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!--Yang Binding -->
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-api</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+
+ <!-- JSON JAXB Stuff -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-json-org</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlets</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>com.sun.jersey.jersey-test-framework</groupId>
+ <artifactId>jersey-test-framework-grizzly2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <executions>
+ <execution>
+ <id>config</id>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator</codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang</namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ <generator>
+ <codeGeneratorClass>org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl</codeGeneratorClass>
+ <outputBaseDir>${salGeneratorPath}</outputBaseDir>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>maven-sal-api-gen-plugin</artifactId>
+ <version>${yangtools.version}</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>${config.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/08-aaa-idmlight-config.xml</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ <execution>
+ <id>attach-artifacts-idmtool</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/idmtool.py</file>
+ <type>py</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <!-- override default version so we don't use bnd 2.3.0 when embedding sqlite -->
+
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>org.opendaylight.aaa.shiro.realm,org.apache.shiro.web.env,org.apache.shiro.authc,org.opendaylight.aaa.shiro.web.env,org.opendaylight.aaa.shiro.filters,javax.servlet.http,javax.ws.rs,javax.ws.rs.core,javax.xml.bind.annotation,org.apache.felix.dm,org.opendaylight.aaa,org.opendaylight.aaa.api.*,org.osgi.framework,org.slf4j,org.eclipse.jetty.servlets,com.sun.jersey.spi.container.servlet,com.google.*,org.opendaylight.*,org.osgi.util.tracker</Import-Package>
+ <Web-ContextPath>/auth</Web-ContextPath>
+ <!--<Web-Connectors>adminConn</Web-Connectors> -->
+ <!--Bundle-Activator>org.opendaylight.aaa.idm.Activator</Bundle-Activator-->
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java
new file mode 100644
index 00000000..6fcba5d6
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightApplication.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+import org.opendaylight.aaa.idm.rest.DomainHandler;
+import org.opendaylight.aaa.idm.rest.RoleHandler;
+import org.opendaylight.aaa.idm.rest.UserHandler;
+import org.opendaylight.aaa.idm.rest.VersionHandler;
+
+/**
+ * A JAX-RS application for IdmLight. The REST endpoints delivered by this
+ * application are in the form:
+ * <code>http://{HOST}:{PORT}/auth/v1/</code>
+ *
+ * For example, the users REST endpoint is:
+ * <code>http://{HOST}:{PORT}/auth/v1/users</code>
+ *
+ * This application is responsible for interaction with the backing h2
+ * database store.
+ *
+ * @author liemmn
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>org.opendaylight.aaa.idm.rest.DomainHandler</code>
+ * @see <code>org.opendaylight.aaa.idm.rest.UserHandler</code>
+ * @see <code>org.opendaylight.aaa.idm.rest.RoleHandler</code>
+ */
+public class IdmLightApplication extends Application {
+
+ //TODO create a bug to address the fact that the implementation assumes 128
+ // as the max length, even though this claims 256.
+ /**
+ * The maximum field length for identity fields.
+ */
+ public static final int MAX_FIELD_LEN = 256;
+ public IdmLightApplication() {
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return new HashSet<Class<?>>(Arrays.asList(VersionHandler.class,
+ DomainHandler.class,
+ RoleHandler.class,
+ UserHandler.class));
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java
new file mode 100644
index 00000000..d17d2b13
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/IdmLightProxy.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm;
+
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.opendaylight.aaa.ClaimBuilder;
+import org.opendaylight.aaa.api.AuthenticationException;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.IIDMStore;
+import org.opendaylight.aaa.api.IdMService;
+import org.opendaylight.aaa.api.PasswordCredentials;
+import org.opendaylight.aaa.api.SHA256Calculator;
+import org.opendaylight.aaa.api.model.Domain;
+import org.opendaylight.aaa.api.model.Grant;
+import org.opendaylight.aaa.api.model.Grants;
+import org.opendaylight.aaa.api.model.Role;
+import org.opendaylight.aaa.api.model.User;
+import org.opendaylight.aaa.api.model.Users;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An OSGi proxy for the IdmLight server.
+ *
+ */
+public class IdmLightProxy implements CredentialAuth<PasswordCredentials>, IdMService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IdmLightProxy.class);
+
+ /**
+ * claimCache is responsible for storing the active claims per domain. The
+ * outer map is keyed by domain, and the inner map is keyed by
+ * <code>PasswordCredentials</code>.
+ */
+ private static Map<String, Map<PasswordCredentials, Claim>> claimCache = new ConcurrentHashMap<>();
+
+ // adds a store for the default "sdn" domain
+ static {
+ claimCache.put(IIDMStore.DEFAULT_DOMAIN,
+ new ConcurrentHashMap<PasswordCredentials, Claim>());
+ }
+
+ @Override
+ public Claim authenticate(PasswordCredentials creds) {
+ Preconditions.checkNotNull(creds);
+ Preconditions.checkNotNull(creds.username());
+ Preconditions.checkNotNull(creds.password());
+ String domain = creds.domain() == null ? IIDMStore.DEFAULT_DOMAIN : creds.domain();
+ // FIXME: Add cache invalidation
+ Map<PasswordCredentials, Claim> cache = claimCache.get(domain);
+ if (cache == null) {
+ cache = new ConcurrentHashMap<PasswordCredentials, Claim>();
+ claimCache.put(domain, cache);
+ }
+ Claim claim = cache.get(creds);
+ if (claim == null) {
+ synchronized (claimCache) {
+ claim = cache.get(creds);
+ if (claim == null) {
+ claim = dbAuthenticate(creds);
+ if (claim != null) {
+ cache.put(creds, claim);
+ }
+ }
+ }
+ }
+ return claim;
+ }
+
+ /**
+ * Clears the cache of any active claims.
+ */
+ public static synchronized void clearClaimCache() {
+ LOG.info("Clearing the claim cache");
+ for (Map<PasswordCredentials, Claim> cache : claimCache.values()) {
+ cache.clear();
+ }
+ }
+
+ private static Claim dbAuthenticate(PasswordCredentials creds) {
+ Domain domain = null;
+ User user = null;
+ String credsDomain = creds.domain() == null ? IIDMStore.DEFAULT_DOMAIN : creds.domain();
+ // check to see domain exists
+ // TODO: ensure domain names are unique change to 'getDomain'
+ LOG.debug("get domain");
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(credsDomain);
+ if (domain == null) {
+ throw new AuthenticationException("Domain :" + credsDomain + " does not exist");
+ }
+ } catch (IDMStoreException e) {
+ throw new AuthenticationException("Error while fetching domain", e);
+ }
+
+ // check to see user exists and passes cred check
+ try {
+ LOG.debug("check user / pwd");
+ Users users = AAAIDMLightModule.getStore().getUsers(creds.username(), credsDomain);
+ List<User> userList = users.getUsers();
+ if (userList.size() == 0) {
+ throw new AuthenticationException("User :" + creds.username()
+ + " does not exist in domain " + credsDomain);
+ }
+ user = userList.get(0);
+ if (!SHA256Calculator.getSHA256(creds.password(), user.getSalt()).equals(
+ user.getPassword())) {
+ throw new AuthenticationException("UserName / Password not found");
+ }
+
+ // get all grants & roles for this domain and user
+ LOG.debug("get grants");
+ List<String> roles = new ArrayList<String>();
+ Grants grants = AAAIDMLightModule.getStore().getGrants(domain.getDomainid(),
+ user.getUserid());
+ List<Grant> grantList = grants.getGrants();
+ for (int z = 0; z < grantList.size(); z++) {
+ Grant grant = grantList.get(z);
+ Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid());
+ if (role != null) {
+ roles.add(role.getName());
+ }
+ }
+
+ // build up the claim
+ LOG.debug("build a claim");
+ ClaimBuilder claim = new ClaimBuilder();
+ claim.setUserId(user.getUserid().toString());
+ claim.setUser(creds.username());
+ claim.setDomain(credsDomain);
+ for (int z = 0; z < roles.size(); z++) {
+ claim.addRole(roles.get(z));
+ }
+ return claim.build();
+ } catch (IDMStoreException se) {
+ throw new AuthenticationException("idm data store exception :" + se.toString() + se);
+ }
+ }
+
+ @Override
+ public List<String> listDomains(String userId) {
+ LOG.debug("list Domains for userId: {}", userId);
+ List<String> domains = new ArrayList<String>();
+ try {
+ Grants grants = AAAIDMLightModule.getStore().getGrants(userId);
+ List<Grant> grantList = grants.getGrants();
+ for (int z = 0; z < grantList.size(); z++) {
+ Grant grant = grantList.get(z);
+ Domain domain = AAAIDMLightModule.getStore().readDomain(grant.getDomainid());
+ domains.add(domain.getName());
+ }
+ return domains;
+ } catch (IDMStoreException se) {
+ LOG.warn("error getting domains ", se.toString(), se);
+ return domains;
+ }
+
+ }
+
+ @Override
+ public List<String> listRoles(String userId, String domainName) {
+ LOG.debug("listRoles");
+ List<String> roles = new ArrayList<String>();
+
+ try {
+ // find domain name for specied domain name
+ String did = null;
+ try {
+ Domain domain = AAAIDMLightModule.getStore().readDomain(domainName);
+ if (domain == null) {
+ LOG.debug("DomainName: {}", domainName + " Not found!");
+ return roles;
+ }
+ did = domain.getDomainid();
+ } catch (IDMStoreException e) {
+ return roles;
+ }
+
+ // find all grants for uid and did
+ Grants grants = AAAIDMLightModule.getStore().getGrants(did, userId);
+ List<Grant> grantList = grants.getGrants();
+ for (int z = 0; z < grantList.size(); z++) {
+ Grant grant = grantList.get(z);
+ Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid());
+ roles.add(role.getName());
+ }
+
+ return roles;
+ } catch (IDMStoreException se) {
+ LOG.warn("error getting roles ", se.toString(), se);
+ return roles;
+ }
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java
new file mode 100644
index 00000000..111665c6
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/StoreBuilder.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm;
+
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.IIDMStore;
+import org.opendaylight.aaa.api.model.Domain;
+import org.opendaylight.aaa.api.model.Grant;
+import org.opendaylight.aaa.api.model.Role;
+import org.opendaylight.aaa.api.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * StoreBuilder is triggered during feature installation by
+ * <code>AAAIDMLightModule.createInstance()</code>. StoreBuilder is responsible
+ * for initializing the H2 database with initial default user account
+ * information. By default, the following users are created:
+ * <ol>
+ * <li>admin</li>
+ * <li>user</li>
+ * </ol>
+ *
+ * By default, the following domain is created:
+ * <ol>
+ * <li>sdn</li>
+ * </ol>
+ *
+ * By default, the following grants are created:
+ * <ol>
+ * <li>admin with admin role on sdn</li>
+ * <li>admin with user role on sdn</li>
+ * <li>user with user role on sdn</li>
+ * </ol>
+ *
+ * @author peter.mellquist@hp.com
+ * @author saichler@cisco.com
+ */
+public class StoreBuilder {
+
+ private static final Logger LOG = LoggerFactory.getLogger(StoreBuilder.class);
+
+ public static void init(IIDMStore store) throws IDMStoreException {
+ LOG.info("creating idmlight schema in store");
+
+ // Check whether the default domain exists. If it exists, then do not
+ // create default data in the store.
+ // TODO Address the fact that someone may delete the sdn domain, or make
+ // sdn mandatory.
+ Domain defaultDomain = store.readDomain(IIDMStore.DEFAULT_DOMAIN);
+ if (defaultDomain != null) {
+ LOG.info("Found default domain in Store, skipping insertion of default data");
+ return;
+ }
+
+ // make domain
+ Domain domain = new Domain();
+ User adminUser = new User();
+ User userUser = new User();
+ Role adminRole = new Role();
+ Role userRole = new Role();
+ domain.setEnabled(true);
+ domain.setName(IIDMStore.DEFAULT_DOMAIN);
+ domain.setDescription("default odl sdn domain");
+ domain = store.writeDomain(domain);
+
+ // Create default users
+ // "admin" user
+ adminUser.setEnabled(true);
+ adminUser.setName("admin");
+ adminUser.setDomainid(domain.getDomainid());
+ adminUser.setDescription("admin user");
+ adminUser.setEmail("");
+ adminUser.setPassword("admin");
+ adminUser = store.writeUser(adminUser);
+ // "user" user
+ userUser.setEnabled(true);
+ userUser.setName("user");
+ userUser.setDomainid(domain.getDomainid());
+ userUser.setDescription("user user");
+ userUser.setEmail("");
+ userUser.setPassword("user");
+ userUser = store.writeUser(userUser);
+
+ // Create default Roles ("admin" and "user")
+ adminRole.setName("admin");
+ adminRole.setDomainid(domain.getDomainid());
+ adminRole.setDescription("a role for admins");
+ adminRole = store.writeRole(adminRole);
+ userRole.setName("user");
+ userRole.setDomainid(domain.getDomainid());
+ userRole.setDescription("a role for users");
+ userRole = store.writeRole(userRole);
+
+ // Create default grants
+ Grant grant = new Grant();
+ grant.setDomainid(domain.getDomainid());
+ grant.setUserid(userUser.getUserid());
+ grant.setRoleid(userRole.getRoleid());
+ grant = store.writeGrant(grant);
+
+ grant.setDomainid(domain.getDomainid());
+ grant.setUserid(adminUser.getUserid());
+ grant.setRoleid(userRole.getRoleid());
+ grant = store.writeGrant(grant);
+
+ grant.setDomainid(domain.getDomainid());
+ grant.setUserid(adminUser.getUserid());
+ grant.setRoleid(adminRole.getRoleid());
+ grant = store.writeGrant(grant);
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java
new file mode 100644
index 00000000..7ddc0748
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/DomainHandler.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm.rest;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.model.Claim;
+import org.opendaylight.aaa.api.model.Domain;
+import org.opendaylight.aaa.api.model.Domains;
+import org.opendaylight.aaa.api.model.Grant;
+import org.opendaylight.aaa.api.model.Grants;
+import org.opendaylight.aaa.api.model.IDMError;
+import org.opendaylight.aaa.api.model.Role;
+import org.opendaylight.aaa.api.model.Roles;
+import org.opendaylight.aaa.api.model.User;
+import org.opendaylight.aaa.api.model.UserPwd;
+import org.opendaylight.aaa.api.model.Users;
+import org.opendaylight.aaa.idm.IdmLightProxy;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST application used to manipulate the H2 database domains table. The REST
+ * endpoint is <code>/auth/v1/domains</code>.
+ *
+ * The following provides examples of curl commands and payloads to utilize the
+ * domains REST endpoint:
+ *
+ * <b>Get All Domains</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains</code>
+ *
+ * <b>Get A Specific Domain</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/domains/{id}</code>
+ *
+ * <b>Create A Domain</b>
+ * <code>curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains</code>
+ * Example domain.json <code>{
+ * "description": "new domain",
+ * "enabled", "true",
+ * "name", "not sdn"
+ * }</code>
+ *
+ * <b>Update A Domain</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}domain.json http://{HOST}:{PORT}/auth/v1/domains</code>
+ * Example domain.json <code>{
+ * "description": "new domain description",
+ * "enabled", "true",
+ * "name", "not sdn"
+ * }</code>
+ *
+ * @author peter.mellquist@hp.com
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+@Path("/v1/domains")
+public class DomainHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DomainHandler.class);
+
+ /**
+ * Extracts all domains.
+ *
+ * @return a response with all domains stored in the H2 database
+ */
+ @GET
+ @Produces("application/json")
+ public Response getDomains() {
+ LOG.info("Get /domains");
+ Domains domains = null;
+ try {
+ domains = AAAIDMLightModule.getStore().getDomains();
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domains");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ return Response.ok(domains).build();
+ }
+
+ /**
+ * Extracts the domain represented by <code>domainId</code>.
+ *
+ * @param domainId the string domain (i.e., "sdn")
+ * @return a response with the specified domain
+ */
+ @GET
+ @Path("/{id}")
+ @Produces("application/json")
+ public Response getDomain(@PathParam("id") String domainId) {
+ LOG.info("Get /domains/{}", domainId);
+ Domain domain = null;
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(domainId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ return Response.ok(domain).build();
+ }
+
+ /**
+ * Creates a domain. The name attribute is required for domain creation.
+ * Enabled and description fields are optional. Optional fields default
+ * in the following manner:
+ * <code>enabled</code>: <code>false</code>
+ * <code>description</code>: An empty string (<code>""</code>).
+ *
+ * @param info passed from Jersey
+ * @param domain designated by the REST payload
+ * @return A response stating success or failure of domain creation.
+ */
+ @POST
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response createDomain(@Context UriInfo info, Domain domain) {
+ LOG.info("Post /domains");
+ try {
+ if (domain.isEnabled() == null) {
+ domain.setEnabled(false);
+ }
+ if (domain.getName() == null) {
+ domain.setName("");
+ }
+ if (domain.getDescription() == null) {
+ domain.setDescription("");
+ }
+ domain = AAAIDMLightModule.getStore().writeDomain(domain);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error creating domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ return Response.status(201).entity(domain).build();
+ }
+
+ /**
+ * Updates a domain.
+ *
+ * @param info passed from Jersey
+ * @param domain the REST payload
+ * @param domainId the last part of the path, containing the specified domain id
+ * @return A response stating success or failure of domain update.
+ */
+ @PUT
+ @Path("/{id}")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response putDomain(@Context UriInfo info, Domain domain, @PathParam("id") String domainId) {
+ LOG.info("Put /domains/{}", domainId);
+ try {
+ domain.setDomainid(domainId);
+ domain = AAAIDMLightModule.getStore().updateDomain(domain);
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ IdmLightProxy.clearClaimCache();
+ return Response.status(200).entity(domain).build();
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error putting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ }
+
+ /**
+ * Deletes a domain.
+ *
+ * @param info passed from Jersey
+ * @param domainId the last part of the path, containing the specified domain id
+ * @return A response stating success or failure of domain deletion.
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response deleteDomain(@Context UriInfo info, @PathParam("id") String domainId) {
+ LOG.info("Delete /domains/{}", domainId);
+
+ try {
+ Domain domain = AAAIDMLightModule.getStore().deleteDomain(domainId);
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error deleting Domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ IdmLightProxy.clearClaimCache();
+ return Response.status(204).build();
+ }
+
+ /**
+ * Creates a grant. A grant defines the role a particular user is given on
+ * a particular domain. For example, by default, AAA installs a grant for
+ * the "admin" user, granting permission to act with "admin" role on the
+ * "sdn" domain.
+ *
+ * @param info passed from Jersey
+ * @param domainId the domain the user is allowed to access
+ * @param userId the user that is allowed to access the domain
+ * @param grant the payload containing role access controls
+ * @return A response stating success or failure of grant creation.
+ */
+ @POST
+ @Path("/{did}/users/{uid}/roles")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response createGrant(@Context UriInfo info, @PathParam("did") String domainId,
+ @PathParam("uid") String userId, Grant grant) {
+ LOG.info("Post /domains/{}/users/{}/roles", domainId, userId);
+ Domain domain = null;
+ User user = null;
+ Role role = null;
+ String roleId = null;
+
+ // validate domain id
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(domainId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ grant.setDomainid(domainId);
+
+ try {
+ user = AAAIDMLightModule.getStore().readUser(userId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting user");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (user == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! User id :" + userId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ grant.setUserid(userId);
+
+ // validate role id
+ try {
+ roleId = grant.getRoleid();
+ LOG.info("roleid = {}", roleId);
+ } catch (NumberFormatException nfe) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Invalid Role id :" + grant.getRoleid());
+ return Response.status(404).entity(idmerror).build();
+ }
+ try {
+ role = AAAIDMLightModule.getStore().readRole(roleId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting role");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (role == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! role :" + grant.getRoleid());
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ // see if grant already exists for this
+ try {
+ Grant existingGrant = AAAIDMLightModule.getStore().readGrant(domainId, userId, roleId);
+ if (existingGrant != null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Grant already exists for did:" + domainId + " uid:" + userId
+ + " rid:" + roleId);
+ return Response.status(403).entity(idmerror).build();
+ }
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error creating grant");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+
+ // create grant
+ try {
+ grant = AAAIDMLightModule.getStore().writeGrant(grant);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error creating grant");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+
+ IdmLightProxy.clearClaimCache();
+ return Response.status(201).entity(grant).build();
+ }
+
+ /**
+ * Used to validate user access.
+ *
+ * @param info passed from Jersey
+ * @param domainId the domain in question
+ * @param userpwd the password attempt
+ * @return A response stating success or failure of user validation.
+ */
+ @POST
+ @Path("/{did}/users/roles")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response validateUser(@Context UriInfo info, @PathParam("did") String domainId,
+ UserPwd userpwd) {
+
+ LOG.info("GET /domains/{}/users", domainId);
+ Domain domain = null;
+ Claim claim = new Claim();
+ List<Role> roleList = new ArrayList<Role>();
+
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(domainId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ // check request body for username and pwd
+ String username = userpwd.getUsername();
+ if (username == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("username not specfied in request body");
+ return Response.status(400).entity(idmerror).build();
+ }
+ String pwd = userpwd.getUserpwd();
+ if (pwd == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("userpwd not specfied in request body");
+ return Response.status(400).entity(idmerror).build();
+ }
+
+ // find userid for user
+ try {
+ Users users = AAAIDMLightModule.getStore().getUsers(username, domainId);
+ List<User> userList = users.getUsers();
+ if (userList.size() == 0) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("did not find username: " + username);
+ return Response.status(404).entity(idmerror).build();
+ }
+ User user = userList.get(0);
+ String userPwd = user.getPassword();
+ String reqPwd = userpwd.getUserpwd();
+ if (!userPwd.equals(reqPwd)) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("password does not match for username: " + username);
+ return Response.status(401).entity(idmerror).build();
+ }
+ claim.setDomainid(domainId);
+ claim.setUsername(username);
+ claim.setUserid(user.getUserid());
+ try {
+ Grants grants = AAAIDMLightModule.getStore().getGrants(domainId, user.getUserid());
+ List<Grant> grantsList = grants.getGrants();
+ for (int i = 0; i < grantsList.size(); i++) {
+ Grant grant = grantsList.get(i);
+ Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid());
+ roleList.add(role);
+ }
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting Roles");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ claim.setRoles(roleList);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting user");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+
+ return Response.ok(claim).build();
+ }
+
+ /**
+ * Get the grants for a user on a domain.
+ *
+ * @param info passed from Jersey
+ * @param domainId the domain in question
+ * @param userId the user in question
+ * @return A response containing the grants for a user on a domain.
+ */
+ @GET
+ @Path("/{did}/users/{uid}/roles")
+ @Produces("application/json")
+ public Response getRoles(@Context UriInfo info, @PathParam("did") String domainId,
+ @PathParam("uid") String userId) {
+ LOG.info("GET /domains/{}/users/{}/roles", domainId, userId);
+ Domain domain = null;
+ User user = null;
+ Roles roles = new Roles();
+ List<Role> roleList = new ArrayList<Role>();
+
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(domainId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ try {
+ user = AAAIDMLightModule.getStore().readUser(userId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting user");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (user == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! User id :" + userId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ try {
+ Grants grants = AAAIDMLightModule.getStore().getGrants(domainId, userId);
+ List<Grant> grantsList = grants.getGrants();
+ for (int i = 0; i < grantsList.size(); i++) {
+ Grant grant = grantsList.get(i);
+ Role role = AAAIDMLightModule.getStore().readRole(grant.getRoleid());
+ roleList.add(role);
+ }
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting Roles");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+
+ roles.setRoles(roleList);
+ return Response.ok(roles).build();
+ }
+
+ /**
+ * Delete a grant.
+ *
+ * @param info passed from Jersey
+ * @param domainId the domain for the grant
+ * @param userId the user for the grant
+ * @param roleId the role for the grant
+ * @return A response stating success or failure of the grant deletion.
+ */
+ @DELETE
+ @Path("/{did}/users/{uid}/roles/{rid}")
+ public Response deleteGrant(@Context UriInfo info, @PathParam("did") String domainId,
+ @PathParam("uid") String userId, @PathParam("rid") String roleId) {
+ Domain domain = null;
+ User user = null;
+ Role role = null;
+
+ try {
+ domain = AAAIDMLightModule.getStore().readDomain(domainId);
+ } catch (IDMStoreException se) {
+ LOG.error("Error deleting Grant : ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting domain");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (domain == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Domain id :" + domainId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ try {
+ user = AAAIDMLightModule.getStore().readUser(userId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException : ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting user");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (user == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! User id :" + userId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ try {
+ role = AAAIDMLightModule.getStore().readRole(roleId);
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error getting Role");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ if (role == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Not found! Role id :" + roleId);
+ return Response.status(404).entity(idmerror).build();
+ }
+
+ // see if grant already exists
+ try {
+ Grant existingGrant = AAAIDMLightModule.getStore().readGrant(domainId, userId, roleId);
+ if (existingGrant == null) {
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Grant does not exist for did:" + domainId + " uid:" + userId
+ + " rid:" + roleId);
+ return Response.status(404).entity(idmerror).build();
+ }
+ existingGrant = AAAIDMLightModule.getStore().deleteGrant(existingGrant.getGrantid());
+ } catch (IDMStoreException se) {
+ LOG.error("StoreException: ", se);
+ IDMError idmerror = new IDMError();
+ idmerror.setMessage("Internal error creating grant");
+ idmerror.setDetails(se.getMessage());
+ return Response.status(500).entity(idmerror).build();
+ }
+ IdmLightProxy.clearClaimCache();
+ return Response.status(204).build();
+ }
+
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java
new file mode 100644
index 00000000..34a60c0c
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/RoleHandler.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm.rest;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.model.IDMError;
+import org.opendaylight.aaa.api.model.Role;
+import org.opendaylight.aaa.api.model.Roles;
+import org.opendaylight.aaa.idm.IdmLightApplication;
+import org.opendaylight.aaa.idm.IdmLightProxy;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST application used to manipulate the H2 database roles table. The REST
+ * endpoint is <code>/auth/v1/roles</code>.
+ *
+ * The following provides examples of curl commands and payloads to utilize the
+ * roles REST endpoint:
+ *
+ * <b>Get All Roles</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles</code>
+ *
+ * <b>Get A Specific Role</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/roles/{id}</code>
+ *
+ * <b>Create A Role</b>
+ * <code>curl -u admin:admin -X POST -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles</code>
+ * An example of role.json:
+ * <code>{
+ * "name":"IT Administrator",
+ * "description":"A user role for IT admins"
+ * }</code>
+ *
+ * <b>Update A Role</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-Type: application/json" --data-binary {@literal @}role.json http://{HOST}:{PORT}/auth/v1/roles/{id}</code>
+ * An example of role.json:
+ * <code>{
+ * "name":"IT Administrator Limited",
+ * "description":"A user role for IT admins who can only do one thing"
+ * }</code>
+ *
+ * @author peter.mellquist@hp.com
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+@Path("/v1/roles")
+public class RoleHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(RoleHandler.class);
+
+ /**
+ * Extracts all roles.
+ *
+ * @return A response with all roles in the H2 database, or internal error if one is encountered
+ */
+ @GET
+ @Produces("application/json")
+ public Response getRoles() {
+ LOG.info("get /roles");
+ Roles roles = null;
+ try {
+ roles = AAAIDMLightModule.getStore().getRoles();
+ } catch (IDMStoreException se) {
+ return new IDMError(500, "internal error getting roles", se.getMessage()).response();
+ }
+ return Response.ok(roles).build();
+ }
+
+ /**
+ * Extract a specific role identified by <code>id</code>
+ *
+ * @param id the String id for the role
+ * @return A response with the role identified by <code>id</code>, or internal error if one is encountered
+ */
+ @GET
+ @Path("/{id}")
+ @Produces("application/json")
+ public Response getRole(@PathParam("id") String id) {
+ LOG.info("get /roles/{}", id);
+ Role role = null;
+
+ try {
+ role = AAAIDMLightModule.getStore().readRole(id);
+ } catch (IDMStoreException se) {
+ return new IDMError(500, "internal error getting roles", se.getMessage()).response();
+ }
+
+ if (role == null) {
+ return new IDMError(404, "role not found id :" + id, "").response();
+ }
+ return Response.ok(role).build();
+ }
+
+ /**
+ * Creates a role.
+ *
+ * @param info passed from Jersey
+ * @param role the role JSON payload
+ * @return A response stating success or failure of role creation, or internal error if one is encountered
+ */
+ @POST
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response createRole(@Context UriInfo info, Role role) {
+ LOG.info("Post /roles");
+ try {
+ // TODO: role names should be unique!
+ // name
+ if (role.getName() == null) {
+ return new IDMError(404, "name must be defined on role create", "").response();
+ } else if (role.getName().length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return new IDMError(400, "role name max length is :"
+ + IdmLightApplication.MAX_FIELD_LEN, "").response();
+ }
+
+ // domain
+ if (role.getDomainid() == null) {
+ return new IDMError(404,
+ "The role's domain must be defined on role when creating a role.", "")
+ .response();
+ } else if (role.getDomainid().length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return new IDMError(400, "role domain max length is :"
+ + IdmLightApplication.MAX_FIELD_LEN, "").response();
+ }
+
+ // description
+ if (role.getDescription() == null) {
+ role.setDescription("");
+ } else if (role.getDescription().length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return new IDMError(400, "role description max length is :"
+ + IdmLightApplication.MAX_FIELD_LEN, "").response();
+ }
+
+ role = AAAIDMLightModule.getStore().writeRole(role);
+ } catch (IDMStoreException se) {
+ return new IDMError(500, "internal error creating role", se.getMessage()).response();
+ }
+
+ return Response.status(201).entity(role).build();
+ }
+
+ /**
+ * Updates a specific role identified by <code>id</code>.
+ *
+ * @param info passed from Jersey
+ * @param role the role JSON payload
+ * @param id the String id for the role
+ * @return A response stating success or failure of role update, or internal error if one occurs
+ */
+ @PUT
+ @Path("/{id}")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response putRole(@Context UriInfo info, Role role, @PathParam("id") String id) {
+ LOG.info("put /roles/{}", id);
+
+ try {
+ role.setRoleid(id);
+
+ // name
+ // TODO: names should be unique
+ if ((role.getName() != null)
+ && (role.getName().length() > IdmLightApplication.MAX_FIELD_LEN)) {
+ return new IDMError(400, "role name max length is :"
+ + IdmLightApplication.MAX_FIELD_LEN, "").response();
+ }
+
+ // description
+ if ((role.getDescription() != null)
+ && (role.getDescription().length() > IdmLightApplication.MAX_FIELD_LEN)) {
+ return new IDMError(400, "role description max length is :"
+ + IdmLightApplication.MAX_FIELD_LEN, "").response();
+ }
+
+ role = AAAIDMLightModule.getStore().updateRole(role);
+ if (role == null) {
+ return new IDMError(404, "role id not found :" + id, "").response();
+ }
+ IdmLightProxy.clearClaimCache();
+ return Response.status(200).entity(role).build();
+ } catch (IDMStoreException se) {
+ return new IDMError(500, "internal error putting role", se.getMessage()).response();
+ }
+ }
+
+ /**
+ * Delete a role.
+ *
+ * @param info passed from Jersey
+ * @param id the String id for the role
+ * @return A response stating success or failure of user deletion, or internal error if one occurs
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response deleteRole(@Context UriInfo info, @PathParam("id") String id) {
+ LOG.info("Delete /roles/{}", id);
+
+ try {
+ Role role = AAAIDMLightModule.getStore().deleteRole(id);
+ if (role == null) {
+ return new IDMError(404, "role id not found :" + id, "").response();
+ }
+ } catch (IDMStoreException se) {
+ return new IDMError(500, "internal error deleting role", se.getMessage()).response();
+ }
+ IdmLightProxy.clearClaimCache();
+ return Response.status(204).build();
+ }
+
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java
new file mode 100644
index 00000000..24fefd7b
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/UserHandler.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm.rest;
+
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.model.IDMError;
+import org.opendaylight.aaa.api.model.User;
+import org.opendaylight.aaa.api.model.Users;
+import org.opendaylight.aaa.idm.IdmLightApplication;
+import org.opendaylight.aaa.idm.IdmLightProxy;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST application used to manipulate the H2 database users table. The REST
+ * endpoint is <code>/auth/v1/users</code>.
+ *
+ * The following provides examples of how curl commands and payloads to utilize
+ * the users REST endpoint:
+ *
+ * <b>Get All Users</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users</code>
+ *
+ * <b>Get A Specific User</b>
+ * <code>curl -u admin:admin http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ *
+ * <b>Create A User</b>
+ * <code>curl -u admin:admin -X POST -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users</code>
+ * An example of user.json file is:
+ * <code>{
+ * "name": "admin2",
+ * "password", "admin2",
+ * "domain": "sdn"
+ * }</code>
+ *
+ * <b>Update A User</b>
+ * <code>curl -u admin:admin -X PUT -H "Content-type: application/json" --data-binary {@literal @}user.json http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ * An example of user.json file is:
+ * <code>{
+ * "name": "admin2",
+ * "password", "admin2",
+ * "domain": "sdn",
+ * "description", "Simple description."
+ * }</code>
+ *
+ * <b>Delete A User</b>
+ * <code>curl -u admin:admin -X DELETE http://{HOST}:{PORT}/auth/v1/users/{id}</code>
+ *
+ * @author peter.mellquist@hp.com
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+@Path("/v1/users")
+public class UserHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UserHandler.class);
+
+ /**
+ * If a user is created through the <code>/auth/v1/users</code> rest
+ * endpoint without a password, the default password is assigned to the
+ * user.
+ */
+ private final static String DEFAULT_PWD = "changeme";
+
+ /**
+ * When an HTTP GET is performed on <code>/auth/v1/users</code>, the
+ * password field is replaced with <code>REDACTED_PASSWORD</code> for
+ * security reasons.
+ */
+ private static final String REDACTED_PASSWORD = "**********";
+
+ /**
+ * When an HTTP GET is performed on <code>/auth/v1/users</code>, the salt
+ * field is replaced with <code>REDACTED_SALT</code> for security reasons.
+ */
+ private static final String REDACTED_SALT = "**********";
+
+ /**
+ * When creating a user, the description is optional and defaults to an
+ * empty string.
+ */
+ private static final String DEFAULT_DESCRIPTION = "";
+
+ /**
+ * When creating a user, the email is optional and defaults to an empty
+ * string.
+ */
+ private static final String DEFAULT_EMAIL = "";
+
+ /**
+ * Extracts all users. The password and salt fields are redacted for
+ * security reasons.
+ *
+ * @return A response containing the users, or internal error if one occurs
+ */
+ @GET
+ @Produces("application/json")
+ public Response getUsers() {
+ LOG.info("GET /auth/v1/users (extracts all users)");
+
+ try {
+ final Users users = AAAIDMLightModule.getStore().getUsers();
+
+ // Redact the password and salt for security purposes.
+ final Collection<User> usersList = users.getUsers();
+ for (User user : usersList) {
+ redactUserPasswordInfo(user);
+ }
+
+ return Response.ok(users).build();
+ } catch (IDMStoreException se) {
+ return internalError("getting", se);
+ }
+ }
+
+ /**
+ * Extracts the user represented by <code>id</code>. The password and salt
+ * fields are redacted for security reasons.
+ *
+ * @param id the unique id of representing the user account
+ * @return A response with the user information, or internal error if one occurs
+ */
+ @GET
+ @Path("/{id}")
+ @Produces("application/json")
+ public Response getUser(@PathParam("id") String id) {
+ LOG.info("GET auth/v1/users/ {} (extract user with specified id)", id);
+
+ try {
+ final User user = AAAIDMLightModule.getStore().readUser(id);
+
+ if (user == null) {
+ return new IDMError(404, String.format("user not found! id: %d", id), "").response();
+ }
+
+ // Redact the password and salt for security purposes.
+ redactUserPasswordInfo(user);
+
+ return Response.ok(user).build();
+ } catch (IDMStoreException se) {
+ return internalError("getting", se);
+ }
+ }
+
+ /**
+ * REST endpoint to create a user. Name and domain are required attributes,
+ * and all other fields (description, email, password, enabled) are
+ * optional. Optional fields default in the following manner:
+ * <code>description</code>: An empty string (<code>""</code>).
+ * <code>email</code>: An empty string (<code>""</code>).
+ * <code>password</code>: <code>changeme</code> <code>enabled</code>:
+ * <code>true</code>
+ *
+ * If a password is not provided, please ensure you change the default
+ * password ASAP for security reasons!
+ *
+ * @param info passed from Jersey
+ * @param user the user defined in the JSON payload
+ * @return A response stating success or failure of user creation
+ */
+ @POST
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response createUser(@Context UriInfo info, User user) {
+ LOG.info("POST /auth/v1/users (create a user with the specified payload");
+
+ // The "enabled" field is optional, and defaults to true.
+ if (user.isEnabled() == null) {
+ user.setEnabled(true);
+ }
+
+ // The "name" field is required.
+ final String userName = user.getName();
+ if (userName == null) {
+ return missingRequiredField("name");
+ }
+ // The "name" field has a maximum length.
+ if (userName.length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return providedFieldTooLong("name", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ // The "domain field is required.
+ final String domainId = user.getDomainid();
+ if (domainId == null) {
+ return missingRequiredField("domain");
+ }
+ // The "domain" field has a maximum length.
+ if (domainId.length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return providedFieldTooLong("domain", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ // The "description" field is optional and defaults to "".
+ final String userDescription = user.getDescription();
+ if (userDescription == null) {
+ user.setDescription(DEFAULT_DESCRIPTION);
+ }
+ // The "description" field has a maximum length.
+ if (userDescription.length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return providedFieldTooLong("description", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ // The "email" field is optional and defaults to "".
+ final String userEmail = user.getEmail();
+ if (userEmail == null) {
+ user.setEmail(DEFAULT_EMAIL);
+ }
+ if (userEmail.length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return providedFieldTooLong("email", IdmLightApplication.MAX_FIELD_LEN);
+ }
+ // TODO add a check on email format here.
+
+ // The "password" field is optional and defautls to "changeme".
+ final String userPassword = user.getPassword();
+ if (userPassword == null) {
+ user.setPassword(DEFAULT_PWD);
+ } else if (userPassword.length() > IdmLightApplication.MAX_FIELD_LEN) {
+ return providedFieldTooLong("password", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ try {
+ // At this point, fields have been properly verified. Create the
+ // user account
+ final User createdUser = AAAIDMLightModule.getStore().writeUser(user);
+ user.setUserid(createdUser.getUserid());
+ } catch (IDMStoreException se) {
+ return internalError("creating", se);
+ }
+
+ // Redact the password and salt for security reasons.
+ redactUserPasswordInfo(user);
+ // TODO report back to the client a warning message to change the
+ // default password if none was specified.
+ return Response.status(201).entity(user).build();
+ }
+
+ /**
+ * REST endpoint to update a user account.
+ *
+ * @param info passed from Jersey
+ * @param user the user defined in the JSON payload
+ * @param id the unique id for the user that will be updated
+ * @return A response stating success or failure of the user update
+ */
+ @PUT
+ @Path("/{id}")
+ @Consumes("application/json")
+ @Produces("application/json")
+ public Response putUser(@Context UriInfo info, User user, @PathParam("id") String id) {
+
+ LOG.info("PUT /auth/v1/users/{} (Updates a user account)", id);
+
+ try {
+ user.setUserid(id);
+
+ if (checkInputFieldLength(user.getPassword())) {
+ return providedFieldTooLong("password", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ if (checkInputFieldLength(user.getName())) {
+ return providedFieldTooLong("name", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ if (checkInputFieldLength(user.getDescription())) {
+ return providedFieldTooLong("description", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ if (checkInputFieldLength(user.getEmail())) {
+ return providedFieldTooLong("email", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ if (checkInputFieldLength(user.getDomainid())) {
+ return providedFieldTooLong("domain", IdmLightApplication.MAX_FIELD_LEN);
+ }
+
+ user = AAAIDMLightModule.getStore().updateUser(user);
+ if (user == null) {
+ return new IDMError(404, String.format("User not found for id %s", id), "").response();
+ }
+
+ IdmLightProxy.clearClaimCache();
+
+ // Redact the password and salt for security reasons.
+ redactUserPasswordInfo(user);
+ return Response.status(200).entity(user).build();
+ } catch (IDMStoreException se) {
+ return internalError("updating", se);
+ }
+ }
+
+ /**
+ * REST endpoint to delete a user account.
+ *
+ * @param info passed from Jersey
+ * @param id the unique id of the user which is being deleted
+ * @return A response stating success or failure of user deletion
+ */
+ @DELETE
+ @Path("/{id}")
+ public Response deleteUser(@Context UriInfo info, @PathParam("id") String id) {
+ LOG.info("DELETE /auth/v1/users/{} (Delete a user account)", id);
+
+ try {
+ final User user = AAAIDMLightModule.getStore().deleteUser(id);
+
+ if (user == null) {
+ return new IDMError(404,
+ String.format("Error deleting user. " +
+ "Couldn't find user with id %s", id),
+ "").response();
+ }
+ } catch (IDMStoreException se) {
+ return internalError("deleting", se);
+ }
+
+ // Successfully deleted the user; report success to the client.
+ IdmLightProxy.clearClaimCache();
+ return Response.status(204).build();
+ }
+
+ /**
+ * Creates a <code>Response</code> related to an internal server error.
+ *
+ * @param verbal such as "creating", "deleting", "updating"
+ * @param e The exception, which is propagated in the response
+ * @return A response containing internal error with specific reasoning
+ */
+ private Response internalError(final String verbal, final Exception e) {
+ LOG.error("There was an internal error {} the user", verbal, e);
+ return new IDMError(500,
+ String.format("There was an internal error %s the user", verbal),
+ e.getMessage()).response();
+ }
+
+ /**
+ * Creates a <code>Response</code> related to the user not providing a
+ * required field.
+ *
+ * @param fieldName the name of the field which is missing
+ * @return A response explaining that the request is missing a field
+ */
+ private Response missingRequiredField(final String fieldName) {
+
+ return new IDMError(400,
+ String.format("%s is required to create the user account. " +
+ "Please provide a %s in your payload.", fieldName, fieldName),
+ "").response();
+ }
+
+ /**
+ * Creates a <code>Response</code> related to the user providing a field
+ * that is too long.
+ *
+ * @param fieldName the name of the field that is too long
+ * @param maxFieldLength the maximum length of <code>fieldName</code>
+ * @return A response containing the bad field and the maximum field length
+ */
+ private Response providedFieldTooLong(final String fieldName, final int maxFieldLength) {
+
+ return new IDMError(400,
+ getProvidedFieldTooLongMessage(fieldName, maxFieldLength),
+ "").response();
+ }
+
+ /**
+ * Creates the client-facing message related to the user providing a field
+ * that is too long.
+ *
+ * @param fieldName the name of the field that is too long
+ * @param maxFieldLength the maximum length of <code>fieldName</code>
+ * @return
+ */
+ private static String getProvidedFieldTooLongMessage(final String fieldName,
+ final int maxFieldLength) {
+
+ return String.format("The provided {} field is too long. " +
+ "The max length is {}.", fieldName, maxFieldLength);
+ }
+
+ /**
+ * Prepares a user account for output by redacting the appropriate fields.
+ * This method side-effects the <code>user</code> parameter.
+ *
+ * @param user the user account which will have fields redacted
+ */
+ private static void redactUserPasswordInfo(final User user) {
+ user.setPassword(REDACTED_PASSWORD);
+ user.setSalt(REDACTED_SALT);
+ }
+
+ /**
+ * Validate the input field length
+ *
+ * @param inputField
+ * @return true if input field bigger than the MAX_FIELD_LEN
+ */
+ private boolean checkInputFieldLength(final String inputField) {
+ return inputField != null && (inputField.length() > IdmLightApplication.MAX_FIELD_LEN);
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java
new file mode 100644
index 00000000..f865162a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/aaa/idm/rest/VersionHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. 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.aaa.idm.rest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+
+import org.opendaylight.aaa.api.model.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author peter.mellquist@hp.com
+ *
+ */
+@Deprecated
+@Path("/")
+public class VersionHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(VersionHandler.class);;
+
+ protected static String CURRENT_VERSION = "v1";
+ protected static String LAST_UPDATED = "2014-04-18T18:30:02.25Z";
+ protected static String CURRENT_STATUS = "CURRENT";
+
+ @GET
+ @Produces("application/json")
+ public Version getVersion(@Context HttpServletRequest request) {
+ LOG.info("Get /");
+ Version version = new Version();
+ version.setId(CURRENT_VERSION);
+ version.setUpdated(LAST_UPDATED);
+ version.setStatus(CURRENT_STATUS);
+ return version;
+ }
+
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java
new file mode 100644
index 00000000..d6872635
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModule.java
@@ -0,0 +1,90 @@
+package org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204;
+
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.IIDMStore;
+import org.opendaylight.aaa.api.IdMService;
+import org.opendaylight.aaa.idm.IdmLightProxy;
+import org.opendaylight.aaa.idm.StoreBuilder;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AAAIDMLightModule extends org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AbstractAAAIDMLightModule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AAAIDMLightModule.class);
+ private BundleContext bundleContext = null;
+ private static volatile IIDMStore store = null;
+
+ public AAAIDMLightModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public AAAIDMLightModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ final IdmLightProxy proxy = new IdmLightProxy();
+ final ServiceRegistration<?> idmService = bundleContext.registerService(IdMService.class.getName(), proxy, null);
+ final ServiceRegistration<?> clientAuthService = bundleContext.registerService(CredentialAuth.class.getName(), proxy, null);
+
+ final ServiceTracker<IIDMStore, IIDMStore> storeServiceTracker = new ServiceTracker<>(bundleContext, IIDMStore.class,
+ new ServiceTrackerCustomizer<IIDMStore, IIDMStore>() {
+ @Override
+ public IIDMStore addingService(ServiceReference<IIDMStore> reference) {
+ store = reference.getBundle().getBundleContext().getService(reference);
+ LOG.info("IIDMStore service {} was found", store.getClass());
+ try {
+ StoreBuilder.init(store);
+ } catch (IDMStoreException e) {
+ LOG.error("Failed to initialize data in store", e);
+ }
+
+ return store;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<IIDMStore> reference, IIDMStore service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<IIDMStore> reference, IIDMStore service) {
+ }
+ });
+
+ storeServiceTracker.open();
+
+ LOG.info("AAA IDM Light Module Initialized");
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ idmService.unregister();
+ clientAuthService.unregister();
+ storeServiceTracker.close();
+ }
+ };
+ }
+
+ public void setBundleContext(BundleContext b){
+ this.bundleContext = b;
+ }
+
+ public static final IIDMStore getStore(){
+ return store;
+ }
+
+ public static final void setStore(IIDMStore s){
+ store = s;
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java
new file mode 100644
index 00000000..de277da8
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/java/org/opendaylight/yang/gen/v1/config/aaa/authn/idmlight/rev151204/AAAIDMLightModuleFactory.java
@@ -0,0 +1,29 @@
+/*
+* Generated file
+*
+* Generated from: yang module name: aaa-idmlight yang module local name: aaa-idmlight
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Fri Dec 04 11:37:37 PST 2015
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.osgi.framework.BundleContext;
+
+public class AAAIDMLightModuleFactory extends org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AbstractAAAIDMLightModuleFactory {
+ @Override
+ public AAAIDMLightModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, AAAIDMLightModule oldModule, AutoCloseable oldInstance, BundleContext bundleContext) {
+ AAAIDMLightModule module = super.instantiateModule(instanceName, dependencyResolver, oldModule, oldInstance, bundleContext);
+ module.setBundleContext(bundleContext);
+ return module;
+ }
+
+ @Override
+ public AAAIDMLightModule instantiateModule(String instanceName, DependencyResolver dependencyResolver, BundleContext bundleContext) {
+ AAAIDMLightModule module = super.instantiateModule(instanceName, dependencyResolver, bundleContext);
+ module.setBundleContext(bundleContext);
+ return module;
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml b/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 00000000..9a19155a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,79 @@
+<?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>IdmLight</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.aaa.idm.IdmLightApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name><param-value>true</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>IdmLight</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Shiro Filter -->
+ <context-param>
+ <param-name>shiroEnvironmentClass</param-name>
+ <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+ </context-param>
+
+ <listener>
+ <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>ShiroFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.shiro.filters.AAAFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>ShiroFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <filter>
+ <filter-name>cross-origin-restconf</filter-name>
+ <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
+ <init-param>
+ <param-name>allowedOrigins</param-name>
+ <param-value>*</param-value>
+ </init-param>
+ <init-param>
+ <param-name>allowedMethods</param-name>
+ <param-value>GET,POST,OPTIONS,DELETE,PUT,HEAD</param-value>
+ </init-param>
+ <init-param>
+ <param-name>allowedHeaders</param-name>
+ <param-value>origin, content-type, accept, authorization, Authorization</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>cross-origin-restconf</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>
+ </security-constraint>
+
+</web-app> \ No newline at end of file
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py b/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py
new file mode 100644
index 00000000..d0a31ba2
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/idmtool.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+
+#
+# Copyright (c) 2016 Brocade Communications Systems 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
+#
+
+'''
+idmtool
+
+Used to manipulate ODL AAA idm on a node-per-node basis. Assumes only one domain (sdn)
+since current support in ODL is limited.
+'''
+
+__author__ = "Ryan Goulding"
+__copyright__ = "Copyright (c) 2016 Brocade Communications Systems and others"
+__credits__ = "Ryan Goulding"
+__license__ = "EPL"
+__version__ = "1.0"
+__maintainer__ = "Ryan Goulding"
+__email__ = "ryandgoulding@gmail.com"
+__status__ = "Production"
+
+import argparse, getpass, json, requests, sys
+
+parser = argparse.ArgumentParser('idmtool')
+
+user=''
+hostname='localhost'
+protocol='http'
+port='8181'
+target_host='{}://{}:{}/'.format(protocol, hostname, port)
+
+# main program arguments
+parser.add_argument('user',help='username for BSC node', nargs=1)
+parser.add_argument('--target-host', help="target host node", nargs=1)
+
+subparsers = parser.add_subparsers(help='sub-command help')
+
+# users table related
+list_users = subparsers.add_parser('list-users', help='list all users')
+list_users.set_defaults(func=list_users)
+add_user = subparsers.add_parser('add-user', help='add a user')
+add_user.set_defaults(func=add_user)
+add_user.add_argument('newUser', help='new user name', nargs=1)
+change_password = subparsers.add_parser('change-password', help='change a password')
+change_password.set_defaults(func=change_password)
+change_password.add_argument('userid', help='change the password for a particular userid', nargs=1)
+delete_user = subparsers.add_parser('delete-user', help='delete a user')
+delete_user.add_argument('userid', help='name@sdn', nargs=1)
+delete_user.set_defaults(func=delete_user)
+
+# domains table related
+# only read is defined; this was done on purpose since the "domain" concept
+# is mostly unsupported in ODL.
+list_domains = subparsers.add_parser('list-domains', help='list all domains')
+list_domains.set_defaults(func=list_domains)
+
+# roles table related
+list_roles = subparsers.add_parser('list-roles', help='list all roles')
+list_roles.set_defaults(func=list_roles)
+add_role = subparsers.add_parser('add-role', help='add a role')
+add_role.add_argument('role', help='role name', nargs=1)
+add_role.set_defaults(func=add_role)
+delete_role = subparsers.add_parser('delete-role', help='delete a role')
+delete_role.add_argument('roleid', help='rolename@sdn', nargs=1)
+delete_role.set_defaults(func=delete_role)
+add_grant = subparsers.add_parser('add-grant', help='add a grant')
+add_grant.set_defaults(func=add_grant)
+add_grant.add_argument('userid', help="username@sdn", nargs=1)
+add_grant.add_argument('roleid', help="role@sdn", nargs=1)
+get_grants = subparsers.add_parser('get-grants', help='get grants for userid on sdn')
+get_grants.set_defaults(func=get_grants)
+get_grants.add_argument('userid', help="username@sdn", nargs=1)
+delete_grant = subparsers.add_parser('delete-grant', help='delete a grant')
+delete_grant.add_argument('userid', help='username@sdn', nargs=1)
+delete_grant.add_argument('roleid', help='role@sdn', nargs=1)
+delete_grant.set_defaults(func=delete_grant)
+
+def process_result(r):
+ ''' Generic method to print result of a REST call '''
+ print ''
+ sc = r.status_code
+ if sc >= 200 and sc < 300:
+ print "command succeeded!"
+ try:
+ res = r.json()
+ if res is not None:
+ print '\njson:\n', json.dumps(res, indent=4, sort_keys=True)
+ except(ValueError):
+ pass
+ elif sc == 401:
+ print "Incorrect Credentials Provided"
+ elif sc == 404:
+ print "RESTconf is either not installed or not initialized yet"
+ elif sc >= 500 and sc < 600:
+ print "Internal Server Error Ocurred"
+ else:
+ print "Unknown error; HTTP status code: {}".format(sc)
+
+def get_request(user, password, url, description, outputResult=True):
+ if outputResult:
+ print description
+ try:
+ r = requests.get(url, auth=(user,password))
+ if outputResult:
+ process_result(r)
+ return r
+ except(requests.exceptions.ConnectionError):
+ if outputResult:
+ print "Unable to connect; are you sure the controller is up?"
+ sys.exit(1)
+
+def post_request(user, password, url, description, payload, params):
+ print description
+ try:
+ r = requests.post(url, auth=(user,password), data=payload, headers=params)
+ process_result(r)
+ except(requests.exceptions.ConnectionError):
+ print "Unable to connect; are you sure the controller is up?"
+ sys.exit(1)
+
+def put_request(user, password, url, description, payload, params):
+ print description
+ try:
+ r = requests.put(url, auth=(user,password), data=payload, headers=params)
+ process_result(r)
+ except(requests.exceptions.ConnectionError):
+ print "Unable to connect; are you sure the controller is up?"
+ sys.exit(1)
+
+def delete_request(user, password, url, description, payload='', params={'Content-Type':'application/json'}):
+ print description
+ try:
+ r = requests.delete(url, auth=(user,password), data=payload, headers=params)
+ process_result(r)
+ except(requests.exceptions.ConnectionError):
+ print "Unable to connect; are you sure the controller is up?"
+ sys.exit(1)
+
+def poll_new_password():
+ new_password = getpass.getpass(prompt="Enter new password: ")
+ new_password_repeated = getpass.getpass(prompt="Re-enter password: ")
+ if new_password != new_password_repeated:
+ print "Passwords did not match; cancelling the add_user request"
+ sys.exit(1)
+ return new_password
+
+def list_users(user, password):
+ get_request(user, password, target_host + 'auth/v1/users', 'list_users')
+
+def add_user(user, password, newUser):
+ new_password = poll_new_password()
+ description = 'add_user({})'.format(user)
+ url = target_host + 'auth/v1/users'
+ payload = {'name':newUser, 'password':new_password, 'description':'', "domainid":"sdn", 'userid':'{}@sdn'.format(newUser), 'email':''}
+ jsonpayload = json.dumps(payload)
+ headers={'Content-Type':'application/json'}
+ post_request(user, password, url, description, jsonpayload, headers)
+
+def delete_user(user, password, userid):
+ url = target_host + 'auth/v1/users/{}'.format(userid)
+ description = 'delete_user({})'.format(userid)
+ delete_request(user, password, url, description)
+
+def change_password(user, password, existingUserId):
+ url = target_host + 'auth/v1/users/{}'.format(existingUserId)
+ r = get_request(user, password, target_host + 'auth/v1/users/{}'.format(existingUserId), 'list_users', outputResult=False)
+ try:
+ existing = r.json()
+ del existing['salt']
+ del existing['password']
+ new_password = poll_new_password()
+ existing['password'] = new_password
+ description='change_password({})'.format(existingUserId)
+ headers={'Content-Type':'application/json'}
+ url = target_host + 'auth/v1/users/{}'.format(existingUserId)
+ put_request(user, password, url, 'change_password({})'.format(user), json.dumps(existing), headers)
+ except(AttributeError):
+ print "Unable to connect; are you sure the controller is up?"
+ sys.exit(1)
+
+def list_domains(user, password):
+ get_request(user, password, target_host + 'auth/v1/domains', 'list_domains')
+
+def list_roles(user, password):
+ get_request(user, password, target_host + 'auth/v1/roles', 'list_roles')
+
+def add_role(user, password, role):
+ url = target_host + 'auth/v1/roles'
+ description = 'add_role({})'.format(role)
+ payload = {"roleid":'{}@sdn'.format(role), 'name':role, 'description':'', 'domainid':'sdn'}
+ data = json.dumps(payload)
+ headers={'Content-Type':'application/json'}
+ post_request(user, password, url, description, data, headers)
+
+def delete_role(user, password, roleid):
+ url = target_host + 'auth/v1/roles/{}'.format(roleid)
+ description = 'delete_role({})'.format(roleid)
+ delete_request(user, password, url, description)
+
+def add_grant(user, password, userid, roleid):
+ description = 'add_grant(userid={},roleid={})'.format(userid, roleid)
+ payload = {"roleid":roleid, "userid":userid, "grantid":'{}@{}@{}'.format(userid, roleid, "sdn"), "domainid":"sdn"}
+ url = target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid)
+ data=json.dumps(payload)
+ headers={'Content-Type':'application/json'}
+ post_request(user, password, url, description, data, headers)
+
+def get_grants(user, password, userid):
+ get_request(user, password, target_host + 'auth/v1/domains/sdn/users/{}/roles'.format(userid), 'get_grants({})'.format(userid))
+
+def delete_grant(user, password, userid, roleid):
+ url = target_host + 'auth/v1/domains/sdn/users/{}/roles/{}'.format(userid, roleid)
+ print url
+ description = 'delete_grant(userid={},roleid={})'.format(userid, roleid)
+ delete_request(user, password, url, description)
+
+args = parser.parse_args()
+command = args.func.prog.split()[1:]
+user = args.user[0]
+password = getpass.getpass()
+if "list-users" in command:
+ list_users(user,password)
+if "list-domains" in command:
+ list_domains(user,password)
+if "list-roles" in command:
+ list_roles(user,password)
+if "add-user" in command:
+ add_user(user,password, args.newUser[0])
+if "add-grant" in command:
+ add_grant(user,password, args.userid[0], args.roleid[0])
+if "get-grants" in command:
+ get_grants(user,password, args.userid[0])
+if "change-password" in command:
+ change_password(user, password, args.userid[0])
+if "delete-user" in command:
+ delete_user(user, password, args.userid[0])
+if "delete-role" in command:
+ delete_role(user, password, args.roleid[0])
+if "add-role" in command:
+ add_role(user, password, args.role[0])
+if "delete-grant" in command:
+ delete_grant(user, password, args.userid[0], args.roleid[0])
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml b/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml
new file mode 100644
index 00000000..695ce762
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/resources/initial/08-aaa-idmlight-config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2015 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
+-->
+<snapshot>
+ <configuration>
+ <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
+ <module>
+ <type xmlns:authn="config:aaa:authn:idmlight">authn:aaa-idmlight</type>
+ <name>aaa-idmlight</name>
+ </module>
+ </modules>
+ </data>
+ </configuration>
+ <required-capabilities>
+ <capability>config:aaa:authn:idmlight?module=aaa-idmlight&amp;revision=2015-12-04</capability>
+ </required-capabilities>
+
+</snapshot>
+
diff --git a/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang b/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang
new file mode 100644
index 00000000..4f28d755
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/main/yang/aaa-idmlight.yang
@@ -0,0 +1,28 @@
+module aaa-idmlight {
+ yang-version 1;
+ namespace "config:aaa:authn:idmlight";
+ prefix "aaa-idmlight";
+ organization "OpenDayLight";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; }
+
+ contact "saichler@gmail.com";
+
+ revision 2015-12-04 {
+ description
+ "Initial revision.";
+ }
+
+ identity aaa-idmlight {
+ base config:module-type;
+ config:java-name-prefix AAAIDMLight;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case aaa-idmlight {
+ when "/config:modules/config:module/config:type = 'aaa-idmlight'";
+ }
+ }
+
+}
diff --git a/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java b/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java
new file mode 100644
index 00000000..44fadf7a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/src/test/java/org/opendaylight/aaa/idm/persistence/PasswordHashTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2015 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.aaa.idm.persistence;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.aaa.api.IDMStoreException;
+import org.opendaylight.aaa.api.IIDMStore;
+import org.opendaylight.aaa.api.PasswordCredentials;
+import org.opendaylight.aaa.api.SHA256Calculator;
+import org.opendaylight.aaa.api.model.Domain;
+import org.opendaylight.aaa.api.model.Grant;
+import org.opendaylight.aaa.api.model.Grants;
+import org.opendaylight.aaa.api.model.Role;
+import org.opendaylight.aaa.api.model.User;
+import org.opendaylight.aaa.api.model.Users;
+import org.opendaylight.aaa.idm.IdmLightProxy;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.idmlight.rev151204.AAAIDMLightModule;
+
+/*
+ * @Author - Sharon Aicler (saichler@cisco.com)
+*/
+public class PasswordHashTest {
+
+ @Before
+ public void before() throws IDMStoreException{
+ IIDMStore store = Mockito.mock(IIDMStore.class);
+ AAAIDMLightModule.setStore(store);
+ Domain domain = new Domain();
+ domain.setName("sdn");
+ domain.setDomainid("sdn");
+
+ Mockito.when(store.readDomain("sdn")).thenReturn(domain);
+ Creds c = new Creds();
+ Users users = new Users();
+ User user = new User();
+ user.setName("admin");
+ user.setUserid(c.username());
+ user.setDomainid("sdn");
+ user.setSalt("ABCD");
+ user.setPassword(SHA256Calculator.getSHA256(c.password(),user.getSalt()));
+ List<User> lu = new LinkedList<>();
+ lu.add(user);
+ users.setUsers(lu);
+
+ Grants grants = new Grants();
+ Grant grant = new Grant();
+ List<Grant> g = new ArrayList<>();
+ g.add(grant);
+ grant.setDomainid("sdn");
+ grant.setRoleid("admin");
+ grant.setUserid("admin");
+ grants.setGrants(g);
+ Role role = new Role();
+ role.setRoleid("admin");
+ role.setName("admin");
+ Mockito.when(store.readRole("admin")).thenReturn(role);
+ Mockito.when(store.getUsers(c.username(), c.domain())).thenReturn(users);
+ Mockito.when(store.getGrants(c.domain(), c.username())).thenReturn(grants);
+ }
+
+ @Test
+ public void testPasswordHash(){
+ IdmLightProxy proxy = new IdmLightProxy();
+ proxy.authenticate(new Creds());
+ }
+
+ private static class Creds implements PasswordCredentials {
+ @Override
+ public String username() {
+ return "admin";
+ }
+ @Override
+ public String password() {
+ return "admin";
+ }
+ @Override
+ public String domain() {
+ return "sdn";
+ }
+ }
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh b/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh
new file mode 100644
index 00000000..6385b48d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/cleardb.sh
@@ -0,0 +1,5 @@
+sudo service idmlight stop
+echo "dropping all tables..."
+sleep 3
+sudo sqlite3 /opt/idmlight/dmlight.db < ../sql/idmlight.sql
+sudo service idmlight start
diff --git a/odl-aaa-moon/aaa-idmlight/tests/domain.json b/odl-aaa-moon/aaa-idmlight/tests/domain.json
new file mode 100644
index 00000000..4dfd25e9
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/domain.json
@@ -0,0 +1,5 @@
+{
+ "domainid": "1",
+ "name":"R&D",
+ "enabled":"true"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/domain2.json b/odl-aaa-moon/aaa-idmlight/tests/domain2.json
new file mode 100644
index 00000000..69244b30
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/domain2.json
@@ -0,0 +1,5 @@
+{
+ "domainid": "1",
+ "name":"ATG",
+ "enabled":"true"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/grant.json b/odl-aaa-moon/aaa-idmlight/tests/grant.json
new file mode 100644
index 00000000..0c4a9e90
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/grant.json
@@ -0,0 +1,4 @@
+{
+ "roleid":"2",
+ "description":"role grant"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/grant2.json b/odl-aaa-moon/aaa-idmlight/tests/grant2.json
new file mode 100644
index 00000000..ad685b7a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/grant2.json
@@ -0,0 +1,4 @@
+{
+ "roleid":"3",
+ "description":"role grant"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/result.json b/odl-aaa-moon/aaa-idmlight/tests/result.json
new file mode 100644
index 00000000..a3dd995d
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/result.json
@@ -0,0 +1 @@
+{"domainid":2,"userid":2,"username":"peter","roles":[{"roleid":2,"name":"user","description":"A user role with limited access"},{"roleid":3,"name":"user","description":"A user role with limited access"}]} \ No newline at end of file
diff --git a/odl-aaa-moon/aaa-idmlight/tests/role-admin.json b/odl-aaa-moon/aaa-idmlight/tests/role-admin.json
new file mode 100644
index 00000000..cf93caae
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/role-admin.json
@@ -0,0 +1,4 @@
+{
+ "name":"admin",
+ "description":"An admin role with full access"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/role-user.json b/odl-aaa-moon/aaa-idmlight/tests/role-user.json
new file mode 100644
index 00000000..78588c9a
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/role-user.json
@@ -0,0 +1,4 @@
+{
+ "name":"user",
+ "description":"A user role with limited access"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/test.sh b/odl-aaa-moon/aaa-idmlight/tests/test.sh
new file mode 100644
index 00000000..3589be58
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/test.sh
@@ -0,0 +1,308 @@
+# GLOBAL VARS
+TARGET="localhost:8282/auth"
+TESTCOUNT=0
+PASSCOUNT=0
+FAILCOUNT=0
+
+getit() {
+((TESTCOUNT++))
+echo '['$TESTCOUNT']' $NAME
+echo GET $URL
+echo "Desired Result=" $PASSCODE
+STATUS=$(curl -X GET -k -s -H Accept:application/json -o result.json -w '%{http_code}' $URL)
+if [ $STATUS -eq $PASSCODE ]; then
+ ((PASSCOUNT++))
+ cat result.json | python -mjson.tool
+ echo "[PASS] Status=" $STATUS
+else
+ cat result.json | python -mjson.tool
+ echo "[FAIL] Status=" $STATUS
+ ((FAILCOUNT++))
+fi
+echo
+}
+
+
+deleteit() {
+((TESTCOUNT++))
+echo '['$TESTCOUNT']' $NAME
+echo DELETE $URL
+echo "Desired Result=" $PASSCODE
+STATUS=$(curl -X DELETE -k -s -H Accept:application/json -o result.json -w '%{http_code}' $URL)
+if [ $STATUS -eq $PASSCODE ]; then
+ ((PASSCOUNT++))
+ echo "[PASS] Status=" $STATUS
+else
+ cat result.json | python -mjson.tool
+ echo "[FAIL] Status=" $STATUS
+ ((FAILCOUNT++))
+fi
+echo
+}
+
+postit() {
+((TESTCOUNT++))
+echo '['$TESTCOUNT']' $NAME
+echo POST $URL
+echo "Desired Result=" $PASSCODE
+echo "POST File=" $POSTFILE
+STATUS=$(curl -X POST -k -s -H "Content-type:application/json" --data-binary "@"$POSTFILE -o result.json -w '%{http_code}' $URL)
+if [ $STATUS -eq $PASSCODE ]; then
+ ((PASSCOUNT++))
+ cat result.json | python -mjson.tool
+ echo "[PASS] Status=" $STATUS
+else
+ cat result.json | python -mjson.tool
+ echo "[FAIL] Status=" $STATUS
+ ((FAILCOUNT++))
+fi
+echo
+}
+
+putit() {
+((TESTCOUNT++))
+echo '['$TESTCOUNT']' $NAME
+echo PUT $URL
+echo "Desired Result=" $PASSCODE
+echo "PUT file=" $PUTFILE
+STATUS=$(curl -X PUT -k -s -H "Content-type:application/json" --data-binary "@"$PUTFILE -o result.json -w '%{http_code}' $URL)
+if [ $STATUS -eq $PASSCODE ]; then
+ ((PASSCOUNT++))
+ cat result.json | python -mjson.tool
+ echo "[PASS] Status=" $STATUS
+else
+ cat result.json | python -mjson.tool
+ echo "[FAIL] Status=" $STATUS
+ ((FAILCOUNT++))
+fi
+echo
+}
+
+
+#
+# DOMAIN TESTS
+#
+
+NAME="get all domains"
+URL="http://$TARGET/v1/domains"
+PASSCODE=200
+getit
+
+NAME="create a new domain"
+URL="http://$TARGET/v1/domains"
+POSTFILE=domain.json
+PASSCODE=201
+postit
+
+NAME="get domain 1"
+URL="http://$TARGET/v1/domains/1"
+PASSCODE=200
+getit
+
+NAME="delete domain 1"
+URL="http://$TARGET/v1/domains/1"
+PASSCODE=204
+deleteit
+
+NAME="create a new domain"
+URL="http://$TARGET/v1/domains"
+POSTFILE=domain.json
+PASSCODE=201
+postit
+
+NAME="get all domains"
+URL="http://$TARGET/v1/domains"
+PASSCODE=200
+getit
+
+NAME="update domain 2"
+URL="http://$TARGET/v1/domains/2"
+PUTFILE=domain.json
+PASSCODE=200
+putit
+
+NAME="create a new domain"
+URL="http://$TARGET/v1/domains"
+POSTFILE=domain2.json
+PASSCODE=201
+postit
+
+NAME="get all domains"
+URL="http://$TARGET/v1/domains"
+PASSCODE=200
+getit
+
+#
+# USER TESTS
+#
+
+NAME="get all users"
+URL="http://$TARGET/v1/users"
+PASSCODE=200
+getit
+
+NAME="create a new user"
+URL="http://$TARGET/v1/users"
+POSTFILE=user.json
+PASSCODE=201
+postit
+
+NAME="get all users"
+URL="http://$TARGET/v1/users"
+PASSCODE=200
+getit
+
+NAME="get user 1"
+URL="http://$TARGET/v1/users/1"
+PASSCODE=200
+getit
+
+NAME="delete user 1"
+URL="http://$TARGET/v1/users/1"
+PASSCODE=204
+deleteit
+
+NAME="get all users"
+URL="http://$TARGET/v1/users"
+PASSCODE=200
+getit
+
+NAME="create a new user"
+URL="http://$TARGET/v1/users"
+POSTFILE=user.json
+PASSCODE=201
+postit
+
+NAME="update a user"
+URL="http://$TARGET/v1/users/2"
+PUTFILE=user.json
+PASSCODE=200
+putit
+
+NAME="create a new user"
+URL="http://$TARGET/v1/users"
+POSTFILE=user2.json
+PASSCODE=201
+postit
+
+NAME="get all users"
+URL="http://$TARGET/v1/users"
+PASSCODE=200
+getit
+
+# ROLE TESTS
+
+NAME="get all roles"
+URL="http://$TARGET/v1/roles"
+PASSCODE=200
+getit
+
+NAME="create a new role"
+URL="http://$TARGET/v1/roles"
+POSTFILE=role-user.json
+PASSCODE=201
+postit
+
+NAME="get all roles"
+URL="http://$TARGET/v1/roles"
+PASSCODE=200
+getit
+
+NAME="get role 1"
+URL="http://$TARGET/v1/roles/1"
+PASSCODE=200
+getit
+
+NAME="delete role 1"
+URL="http://$TARGET/v1/roles/1"
+PASSCODE=204
+deleteit
+
+NAME="create a new role"
+URL="http://$TARGET/v1/roles"
+POSTFILE=role-user.json
+PASSCODE=201
+postit
+
+NAME="update role 2"
+URL="http://$TARGET/v1/roles/2"
+PUTFILE=role-user.json
+PASSCODE=200
+putit
+
+NAME="create a new role"
+URL="http://$TARGET/v1/roles"
+POSTFILE=role-admin.json
+PASSCODE=201
+postit
+
+NAME="get all roles"
+URL="http://$TARGET/v1/roles"
+PASSCODE=200
+getit
+
+# Grant tests
+
+NAME="grant a role"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+POSTFILE=grant.json
+PASSCODE=201
+postit
+
+NAME="try to create a double grant"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+POSTFILE=grant.json
+PASSCODE=403
+postit
+
+NAME="get all roles for domain and user"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+PASSCODE=200
+getit
+
+NAME="delete a grant"
+URL="http://$TARGET/v1/domains/2/users/2/roles/2"
+PASSCODE=204
+deleteit
+
+NAME="delete a grant"
+URL="http://$TARGET/v1/domains/2/users/2/roles/2"
+PASSCODE=404
+deleteit
+
+NAME="get all roles for domain and user"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+PASSCODE=200
+getit
+
+NAME="grant a role"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+POSTFILE=grant.json
+PASSCODE=201
+postit
+
+NAME="grant a role"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+POSTFILE=grant2.json
+PASSCODE=201
+postit
+
+NAME="get all roles for domain and user"
+URL="http://$TARGET/v1/domains/2/users/2/roles"
+PASSCODE=200
+getit
+
+NAME="get all roles for domain, user and pwd"
+URL="http://$TARGET/v1/domains/2/users/roles"
+POSTFILE=userpwd.json
+PASSCODE=200
+postit
+
+
+#
+# RESULTS
+#
+echo "SUMMARY"
+echo "======================================"
+echo 'TESTS:'$TESTCOUNT 'PASS:'$PASSCOUNT 'FAIL:'$FAILCOUNT
+
diff --git a/odl-aaa-moon/aaa-idmlight/tests/user.json b/odl-aaa-moon/aaa-idmlight/tests/user.json
new file mode 100644
index 00000000..6f30d705
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/user.json
@@ -0,0 +1,7 @@
+{
+ "name":"peter",
+ "description":"peter test user",
+ "enabled":"true",
+ "email":"user1@gmail.com",
+ "password":"foobar"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/user2.json b/odl-aaa-moon/aaa-idmlight/tests/user2.json
new file mode 100644
index 00000000..9864cdb2
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/user2.json
@@ -0,0 +1,7 @@
+{
+ "name":"liem",
+ "description":"liem test user",
+ "enabled":"true",
+ "email":"user1@gmail.com",
+ "password":"foobar"
+}
diff --git a/odl-aaa-moon/aaa-idmlight/tests/userpwd.json b/odl-aaa-moon/aaa-idmlight/tests/userpwd.json
new file mode 100644
index 00000000..e5258b98
--- /dev/null
+++ b/odl-aaa-moon/aaa-idmlight/tests/userpwd.json
@@ -0,0 +1,4 @@
+{
+ "username":"peter",
+ "userpwd":"foobar"
+}