diff options
author | WuKong <rebirthmonkey@gmail.com> | 2016-05-24 17:13:17 +0200 |
---|---|---|
committer | WuKong <rebirthmonkey@gmail.com> | 2016-05-24 17:13:17 +0200 |
commit | e63b03f3d7e4851e008e4bb4d184982c2c0bd229 (patch) | |
tree | 8364e8a9c56e214ac0fe248409d21f324b1e0f18 /odl-aaa-moon/aaa-idmlight/src/main/java | |
parent | 3c1264562ec7949d008e2335b9eecc400436a70d (diff) |
odl/aaa clone
Change-Id: I2b72c16aa3245e02d985a2c6189aacee7caad36e
Signed-off-by: WuKong <rebirthmonkey@gmail.com>
Diffstat (limited to 'odl-aaa-moon/aaa-idmlight/src/main/java')
9 files changed, 1786 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; + } +} |