diff options
Diffstat (limited to 'odl-aaa-moon/aaa-idmlight/src')
14 files changed, 2259 insertions, 0 deletions
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&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"; + } + } +} |