From 8d99c88af55041ff06e6b9372b6b1f66220bed38 Mon Sep 17 00:00:00 2001
From: rsanchez <rsanchez@curisit.net>
Date: Mon, 10 Apr 2017 16:08:58 +0000
Subject: [PATCH] #3529 feature - Added applications to user profile and upgrade to angular4

---
 securis/src/main/webapp/src/app/forms/user.form.component.ts                 |   29 ++
 securis/src/main/java/net/curisit/securis/db/User.java                       |   64 +++++
 securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java       |  288 ++++++++++++++------------
 securis/src/main/webapp/src/app/listing/user.list.component.html             |    2 
 securis/src/main/java/net/curisit/securis/db/Application.java                |   11 +
 securis/src/main/webapp/sql_update.sql                                       |   10 
 securis/src/main/java/net/curisit/securis/db/Pack.java                       |    1 
 securis/src/main/webapp/package.json                                         |   27 +-
 securis/src/main/java/net/curisit/securis/services/PackResource.java         |    9 
 securis/src/main/webapp/src/app/forms/user.form.html                         |   19 -
 securis/src/main/webapp/index.html                                           |    1 
 securis/src/main/java/net/curisit/securis/services/UserResource.java         |   36 ++
 securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java |  140 +++++++-----
 securis/src/main/webapp/src/app/app.module.ts                                |    2 
 securis/src/main/webapp/systemjs.config.js                                   |    6 
 securis/src/main/resources/db/schema.sql                                     |    7 
 16 files changed, 419 insertions(+), 233 deletions(-)

diff --git a/securis/src/main/java/net/curisit/securis/db/Application.java b/securis/src/main/java/net/curisit/securis/db/Application.java
index c994373..1dc20a5 100644
--- a/securis/src/main/java/net/curisit/securis/db/Application.java
+++ b/securis/src/main/java/net/curisit/securis/db/Application.java
@@ -10,6 +10,9 @@
 import javax.persistence.FetchType;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
@@ -68,6 +71,14 @@
 	@JsonManagedReference
 	private Set<ApplicationMetadata> metadata;
 
+	@JsonIgnore
+	// We don't include the users to limit the size of each row a the listing
+	@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
+	@JoinTable(name = "user_application", //
+			joinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") }, //
+			inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") })
+	private Set<User> users;
+
 	public Integer getId() {
 		return id;
 	}
diff --git a/securis/src/main/java/net/curisit/securis/db/Pack.java b/securis/src/main/java/net/curisit/securis/db/Pack.java
index eb11298..3ff3493 100644
--- a/securis/src/main/java/net/curisit/securis/db/Pack.java
+++ b/securis/src/main/java/net/curisit/securis/db/Pack.java
@@ -43,6 +43,7 @@
 @NamedQueries({ @NamedQuery(name = "list-packs", query = "SELECT pa FROM Pack pa"), //
 		@NamedQuery(name = "pack-by-code", query = "SELECT pa FROM Pack pa where pa.code = :code"), //
 		@NamedQuery(name = "list-packs-by-lic-type", query = "SELECT pa FROM Pack pa where pa.licenseType.id = :lt_id"), //
+		@NamedQuery(name = "list-packs-by-orgs-apps", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "), //
 		@NamedQuery(name = "list-packs-by-orgs", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids") })
 public class Pack implements Serializable {
 
diff --git a/securis/src/main/java/net/curisit/securis/db/User.java b/securis/src/main/java/net/curisit/securis/db/User.java
index e1e7396..d5b0f45 100644
--- a/securis/src/main/java/net/curisit/securis/db/User.java
+++ b/securis/src/main/java/net/curisit/securis/db/User.java
@@ -2,10 +2,12 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -76,6 +78,14 @@
 			inverseJoinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") } //
 	)
 	private Set<Organization> organizations;
+
+	@JsonIgnore
+	@ManyToMany
+	@JoinTable(name = "user_application", //
+			joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, //
+			inverseJoinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") } //
+	)
+	private Set<Application> applications;
 
 	public String getUsername() {
 		return username;
@@ -182,6 +192,14 @@
 		this.organizations = organizations;
 	}
 
+	public Set<Application> getApplications() {
+		return applications;
+	}
+
+	public void setApplications(Set<Application> applications) {
+		this.applications = applications;
+	}
+
 	@JsonProperty("organizations_ids")
 	public void setOrgsIds(List<Integer> orgsIds) {
 		organizations = new HashSet<>();
@@ -204,6 +222,28 @@
 		return ids;
 	}
 
+	@JsonProperty("applications_ids")
+	public void setAppsIds(Collection<Integer> appIds) {
+		applications = new HashSet<>();
+		for (Integer appid : appIds) {
+			Application a = new Application();
+			a.setId(appid);
+			applications.add(a);
+		}
+	}
+
+	@JsonProperty("applications_ids")
+	public Set<Integer> getAppsIds() {
+		if (applications == null) {
+			return null;
+		}
+		Set<Integer> ids = new HashSet<>();
+		for (Application app : applications) {
+			ids.add(app.getId());
+		}
+		return ids;
+	}
+
 	@JsonIgnore
 	public Set<Integer> getAllOrgsIds() {
 		if (organizations == null) {
@@ -214,6 +254,22 @@
 		return ids;
 	}
 
+	@JsonIgnore
+	public Set<Integer> getAllAppsIds() {
+		if (applications == null) {
+			return null;
+		}
+		Set<Integer> ids = this.applications.parallelStream().map(app -> app.getId()).collect(Collectors.toSet());
+
+		return ids;
+	}
+
+	/**
+	 * Walk into the organization hierarchy to include all descendants
+	 * 
+	 * @param list
+	 * @param orgIds
+	 */
 	private void includeAllOrgs(Set<Organization> list, Set<Integer> orgIds) {
 		for (Organization org : list) {
 			orgIds.add(org.getId());
@@ -229,10 +285,16 @@
 		this.email = email;
 	}
 
+	/**
+	 * Numeric rol mask. Be aware to use different bit position for each role
+	 * 
+	 * @author rob
+	 */
 	public static class Rol {
 		public static final int ADVANCE = 0x01;
 		public static final int ADMIN = 0x02;
-		public static final int[] ALL = new int[] { ADVANCE, ADMIN };
+		public static final int BASIC = 0x04;
+		public static final int[] ALL = new int[] { ADVANCE, ADMIN, BASIC };
 	}
 
 }
diff --git a/securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java b/securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
index 0ac32bb..391ec7f 100644
--- a/securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
+++ b/securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
@@ -21,12 +21,6 @@
 import javax.ws.rs.ext.WriterInterceptor;
 import javax.ws.rs.ext.WriterInterceptorContext;
 
-import net.curisit.securis.db.User;
-import net.curisit.securis.security.BasicSecurityContext;
-import net.curisit.securis.security.Securable;
-import net.curisit.securis.utils.CacheTTL;
-import net.curisit.securis.utils.TokenHelper;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.jboss.resteasy.core.Dispatcher;
@@ -36,158 +30,188 @@
 import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 
+import net.curisit.securis.db.User;
+import net.curisit.securis.security.BasicSecurityContext;
+import net.curisit.securis.security.Securable;
+import net.curisit.securis.utils.CacheTTL;
+import net.curisit.securis.utils.TokenHelper;
+
 @Provider
 @Priority(Priorities.AUTHENTICATION)
 public class RequestsInterceptor implements ContainerRequestFilter, WriterInterceptor {
-    private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
+	private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class);
 
-    @Context
-    private HttpServletResponse servletResponse;
+	@Context
+	private HttpServletResponse servletResponse;
 
-    @Context
-    private HttpServletRequest servletRequest;
+	@Context
+	private HttpServletRequest servletRequest;
 
-    @Inject
-    private CacheTTL cache;
+	@Inject
+	private CacheTTL cache;
 
-    @Inject
-    private TokenHelper tokenHelper;
+	@Inject
+	private TokenHelper tokenHelper;
 
-    @Context
-    private Dispatcher dispatcher;
+	@Context
+	private Dispatcher dispatcher;
 
-    @Inject
-    private EntityManagerProvider emProvider;
+	@Inject
+	private EntityManagerProvider emProvider;
 
-    @Override
-    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
-        EntityManager em = emProvider.getEntityManager();
-        LOG.debug("GETTING EM: {}", em);
+	@Override
+	public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+		EntityManager em = emProvider.getEntityManager();
+		LOG.debug("GETTING EM: {}", em);
 
-        ResteasyProviderFactory.pushContext(EntityManager.class, em);
+		ResteasyProviderFactory.pushContext(EntityManager.class, em);
 
-        ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext
-                .getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
-        Method method = methodInvoker.getMethod();
+		ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) containerRequestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
+		Method method = methodInvoker.getMethod();
 
-        LOG.debug("Stored in context, em: {}, {}", em, method.toGenericString());
+		LOG.debug("Stored in context, em: {}, {}", em, method.toGenericString());
 
-        boolean next = checkSecurableMethods(containerRequestContext, method);
-        if (next) {
-            prepareTransaction(containerRequestContext, method, em);
-        }
-    }
+		boolean next = checkSecurableMethods(containerRequestContext, method);
+		if (next) {
+			prepareTransaction(containerRequestContext, method, em);
+		}
+	}
 
-    private void prepareTransaction(ContainerRequestContext containerRequestContext, Method method, EntityManager em) {
+	private void prepareTransaction(ContainerRequestContext containerRequestContext, Method method, EntityManager em) {
 
-        if (method.isAnnotationPresent(EnsureTransaction.class)) {
-            LOG.debug("Beginning a new transaction");
-            em.getTransaction().begin();
-        }
-    }
+		if (method.isAnnotationPresent(EnsureTransaction.class)) {
+			LOG.debug("Beginning a new transaction");
+			em.getTransaction().begin();
+		}
+	}
 
-    private boolean checkSecurableMethods(ContainerRequestContext containerRequestContext, Method method) {
-        if (!method.isAnnotationPresent(Securable.class)) {
-            return true;
-        }
-        String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
-        if (token == null || !tokenHelper.isTokenValid(token)) {
-            LOG.warn("Access denied, Token not valid: {} for method: {}::{}", token, method.getDeclaringClass(), method.getName());
-            containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());
-            return false;
-        } else {
+	private boolean checkSecurableMethods(ContainerRequestContext containerRequestContext, Method method) {
+		if (!method.isAnnotationPresent(Securable.class)) {
+			return true;
+		}
+		String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);
+		if (token == null || !tokenHelper.isTokenValid(token)) {
+			LOG.warn("Access denied, Token not valid: {} for method: {}::{}", token, method.getDeclaringClass(), method.getName());
+			containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());
+			return false;
+		} else {
+			Securable securable = method.getAnnotation(Securable.class);
+			// If roles == 0 we only need to validate the token
+			String username = tokenHelper.extractUserFromToken(token);
+			int userRoles = getUserRoles(username);
+			if (securable.roles() != 0 && (securable.roles() & userRoles) == 0) {
+				LOG.warn("Method {} requires roles: {}, but user {} hasn't got them", method.getName(), securable.roles(), username);
+				containerRequestContext.abortWith(Response.status(Status.UNAUTHORIZED).build());
+				return false;
+			}
+			Set<Integer> orgs = getUserOrganizations(username);
+			Set<Integer> apps = getUserApplications(username);
 
-            // If roles == 0 we only need to validate the token
-            String username = tokenHelper.extractUserFromToken(token);
-            int userRoles = getUserRoles(username);
-            Set<Integer> orgs = getUserOrganizations(username);
+			BasicSecurityContext scw = new BasicSecurityContext(username, userRoles, servletRequest.isSecure());
+			scw.setOrganizationsIds(orgs);
+			scw.setApplicationsIds(apps);
+			containerRequestContext.setSecurityContext(scw);
+			// Next line provide injection in resource methods
+			ResteasyProviderFactory.pushContext(BasicSecurityContext.class, scw);
+			LOG.debug("Added custom SecurityContext for user {}, orgs: {}", username, orgs);
+		}
+		return true;
 
-            BasicSecurityContext scw = new BasicSecurityContext(username, userRoles, servletRequest.isSecure());
-            scw.setOrganizationsIds(orgs);
-            containerRequestContext.setSecurityContext(scw);
-            // Next line provide injection in resource methods
-            ResteasyProviderFactory.pushContext(BasicSecurityContext.class, scw);
-            LOG.debug("Added custom SecurityContext for user {}, orgs: {}", username, orgs);
-        }
-        return true;
+	}
 
-    }
+	private Set<Integer> getUserOrganizations(String username) {
+		@SuppressWarnings("unchecked")
+		Set<Integer> userOrgs = cache.get("orgs_" + username, Set.class);
+		if (userOrgs == null) {
+			EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
 
-    private Set<Integer> getUserOrganizations(String username) {
-        @SuppressWarnings("unchecked")
-        Set<Integer> userOrgs = cache.get("orgs_" + username, Set.class);
-        if (userOrgs == null) {
-            EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
+			// Theorically this shouldn't be never null, but just in case...
+			User user = em.find(User.class, username);
+			if (user != null) {
+				userOrgs = user.getAllOrgsIds();
+				// We store user orgs in cache only for one hour
+				cache.set("orgs_" + username, userOrgs, 3600);
+			}
+		}
 
-            // Theorically this shouldn't be never null, but just in case...
-            User user = em.find(User.class, username);
-            if (user != null) {
-                userOrgs = user.getAllOrgsIds();
-                // We store user orgs in cache only for one hour
-                cache.set("orgs_" + username, userOrgs, 3600);
-            }
-        }
+		return userOrgs;
+	}
 
-        return userOrgs;
-    }
+	private Set<Integer> getUserApplications(String username) {
+		@SuppressWarnings("unchecked")
+		Set<Integer> userApps = cache.get("apps_" + username, Set.class);
+		if (userApps == null) {
+			EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
 
-    private int getUserRoles(String username) {
-        if (username == null) {
-            return 0;
-        }
-        Integer userRoles = cache.get("roles_" + username, Integer.class);
-        if (userRoles == null) {
-            EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
+			// Theorically this shouldn't be never null, but just in case...
+			User user = em.find(User.class, username);
+			if (user != null) {
+				userApps = user.getAllAppsIds();
+				// We store user orgs in cache only for one hour
+				cache.set("apps_" + username, userApps, 3600);
+			}
+		}
 
-            User user = em.find(User.class, username);
-            if (user != null) {
-                userRoles = 0;
-                List<Integer> roles = user.getRoles();
-                if (roles != null) {
-                    for (Integer rol : roles) {
-                        userRoles += rol;
-                    }
-                }
-                // We store user roles in cache only for one hour
-                cache.set("roles_" + username, userRoles, 3600);
-                cache.set("orgs_" + username, user.getOrgsIds(), 3600);
-            }
-        }
-        return userRoles == null ? 0 : userRoles.intValue();
-    }
+		return userApps;
+	}
 
-    // @Override
-    public ServerResponse preProcess(HttpRequest request, ResourceMethodInvoker method) throws Failure, WebApplicationException {
-        return null;
-    }
+	private int getUserRoles(String username) {
+		if (username == null) {
+			return 0;
+		}
+		Integer userRoles = cache.get("roles_" + username, Integer.class);
+		if (userRoles == null) {
+			EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
 
-    @Override
-    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
-        context.proceed();
-        EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
-        try {
-            if (em != null && em.getTransaction().isActive()) {
-                if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
-                    em.getTransaction().commit();
-                    LOG.debug("COMMIT");
-                } else {
-                    // This code is never executed if there is an error the
-                    // filter chain is broken
-                    em.getTransaction().rollback();
-                    LOG.debug("ROLLBACK");
-                }
-            }
-        } finally {
-            if (em.isOpen()) {
-                LOG.debug("CLOSING EM: {}, trans: {}", em, em.isJoinedToTransaction());
-                try {
-                    em.close();
-                } catch (Exception ex) {
-                    ex.printStackTrace();
-                    LOG.error("Error closing EM: {}, {}", em, ex);
-                }
-            }
-        }
-    }
+			User user = em.find(User.class, username);
+			if (user != null) {
+				userRoles = 0;
+				List<Integer> roles = user.getRoles();
+				if (roles != null) {
+					for (Integer rol : roles) {
+						userRoles += rol;
+					}
+				}
+				// We store user roles in cache only for one hour
+				cache.set("roles_" + username, userRoles, 3600);
+				cache.set("orgs_" + username, user.getOrgsIds(), 3600);
+			}
+		}
+		return userRoles == null ? 0 : userRoles.intValue();
+	}
+
+	// @Override
+	public ServerResponse preProcess(HttpRequest request, ResourceMethodInvoker method) throws Failure, WebApplicationException {
+		return null;
+	}
+
+	@Override
+	public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
+		context.proceed();
+		EntityManager em = ResteasyProviderFactory.getContextData(EntityManager.class);
+		try {
+			if (em != null && em.getTransaction().isActive()) {
+				if (servletResponse.getStatus() == Status.OK.getStatusCode()) {
+					em.getTransaction().commit();
+					LOG.debug("COMMIT");
+				} else {
+					// This code is never executed if there is an error the
+					// filter chain is broken
+					em.getTransaction().rollback();
+					LOG.debug("ROLLBACK");
+				}
+			}
+		} finally {
+			if (em.isOpen()) {
+				LOG.debug("CLOSING EM: {}, trans: {}", em, em.isJoinedToTransaction());
+				try {
+					em.close();
+				} catch (Exception ex) {
+					ex.printStackTrace();
+					LOG.error("Error closing EM: {}, {}", em, ex);
+				}
+			}
+		}
+	}
 
 }
diff --git a/securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java b/securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java
index 3f20ddc..dc0f40b 100644
--- a/securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java
+++ b/securis/src/main/java/net/curisit/securis/security/BasicSecurityContext.java
@@ -11,83 +11,101 @@
 
 public class BasicSecurityContext implements SecurityContext {
 
-    final public static String ROL_ADVANCE = "advance";
-    final public static String ROL_ADMIN = "admin";
+	final public static String ROL_ADVANCE = "advance";
+	final public static String ROL_ADMIN = "admin";
+	final public static String ROL_BASIC = "basic";
 
-    final static Map<String, Integer> ROLES = Utils.<String, Integer> createMap(ROL_ADVANCE, User.Rol.ADVANCE, ROL_ADMIN, User.Rol.ADMIN);
+	final static Map<String, Integer> ROLES = Utils.<String, Integer> createMap(ROL_BASIC, User.Rol.BASIC, ROL_ADVANCE, User.Rol.ADVANCE, ROL_ADMIN, User.Rol.ADMIN);
 
-    Principal user = null;
-    int roles = 0;
-    boolean secure = false;
-    Set<Integer> organizationsIds = null;
-    double ran = 0;
+	Principal user = null;
+	int roles = 0;
+	boolean secure = false;
+	Set<Integer> organizationsIds = null;
+	Set<Integer> applicationsIds = null;
+	double ran = 0;
 
-    public BasicSecurityContext(String username, int roles, boolean secure) {
-        user = new UserPrincipal(username);
-        this.roles = roles;
-        this.secure = secure;
-        ran = Math.random();
-    }
+	public BasicSecurityContext(String username, int roles, boolean secure) {
+		user = new UserPrincipal(username);
+		this.roles = roles;
+		this.secure = secure;
+		ran = Math.random();
+	}
 
-    @Override
-    public Principal getUserPrincipal() {
-        return user;
-    }
+	@Override
+	public Principal getUserPrincipal() {
+		return user;
+	}
 
-    @Override
-    public boolean isUserInRole(String role) {
-        Integer introle = ROLES.get(role);
-        return introle != null && (introle & roles) != 0;
-    }
+	@Override
+	public boolean isUserInRole(String role) {
+		Integer introle = ROLES.get(role);
+		return introle != null && (introle & roles) != 0;
+	}
 
-    @Override
-    public boolean isSecure() {
-        return secure;
-    }
+	@Override
+	public boolean isSecure() {
+		return secure;
+	}
 
-    @Override
-    public String getAuthenticationScheme() {
-        return null;
-    }
+	@Override
+	public String getAuthenticationScheme() {
+		return null;
+	}
 
-    @Override
-    public String toString() {
+	@Override
+	public String toString() {
 
-        return String.format("SecurityContextWrapper(%f) %s", ran, user);
-    }
+		return String.format("SecurityContextWrapper(%f) %s", ran, user);
+	}
 
-    public void setOrganizationsIds(Set<Integer> orgs) {
-        this.organizationsIds = orgs;
-    }
+	public void setOrganizationsIds(Set<Integer> orgs) {
+		this.organizationsIds = orgs;
+	}
 
-    public Set<Integer> getOrganizationsIds() {
-        return this.organizationsIds;
-    }
+	public Set<Integer> getOrganizationsIds() {
+		return this.organizationsIds;
+	}
 
-    private class UserPrincipal implements Principal {
+	public Set<Integer> getApplicationsIds() {
+		return applicationsIds;
+	}
 
-        final String name;
+	public void setApplicationsIds(Set<Integer> applicationsIds) {
+		this.applicationsIds = applicationsIds;
+	}
 
-        public UserPrincipal(String name) {
-            this.name = name;
-        }
+	private class UserPrincipal implements Principal {
 
-        @Override
-        public String getName() {
-            return this.name;
-        }
+		final String name;
 
-        @Override
-        public String toString() {
-            return String.format("[%s]", name);
-        }
+		public UserPrincipal(String name) {
+			this.name = name;
+		}
 
-    }
+		@Override
+		public String getName() {
+			return this.name;
+		}
 
-    public boolean isOrgAccesible(Integer orgid) {
-        if (organizationsIds == null || orgid == null) {
-            return false;
-        }
-        return organizationsIds.contains(orgid);
-    }
+		@Override
+		public String toString() {
+			return String.format("[%s]", name);
+		}
+
+	}
+
+	public boolean isOrgAccesible(Integer orgid) {
+		if (organizationsIds == null || orgid == null) {
+			return false;
+		}
+		return organizationsIds.contains(orgid);
+	}
+
+	public boolean isAppAccesible(Integer appid) {
+		if (applicationsIds == null || appid == null) {
+			return false;
+		}
+		return applicationsIds.contains(appid);
+	}
+
 }
diff --git a/securis/src/main/java/net/curisit/securis/services/PackResource.java b/securis/src/main/java/net/curisit/securis/services/PackResource.java
index a417a98..07bfdfa 100644
--- a/securis/src/main/java/net/curisit/securis/services/PackResource.java
+++ b/securis/src/main/java/net/curisit/securis/services/PackResource.java
@@ -90,11 +90,14 @@
 			LOG.info("Getting all packs for user: " + bsc.getUserPrincipal());
 			q = em.createNamedQuery("list-packs", Pack.class);
 		} else {
-			if (bsc.getOrganizationsIds() == null) {
+			if (bsc.getOrganizationsIds() == null || bsc.getOrganizationsIds().isEmpty() || // 
+					bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
 				return Response.ok().build();
 			}
-			q = em.createNamedQuery("list-packs-by-orgs", Pack.class);
-			q.setParameter("list_ids", bsc.getOrganizationsIds());
+			q = em.createNamedQuery("list-packs-by-orgs-apps", Pack.class);
+			q.setParameter("list_ids_org", bsc.getOrganizationsIds());
+			q.setParameter("list_ids_app", bsc.getApplicationsIds());
+			LOG.info("Getting packs from orgs: {} and apps: {}", bsc.getOrganizationsIds(), bsc.getApplicationsIds());
 		}
 
 		List<Pack> list = q.getResultList();
diff --git a/securis/src/main/java/net/curisit/securis/services/UserResource.java b/securis/src/main/java/net/curisit/securis/services/UserResource.java
index 0c3e233..a4789d4 100644
--- a/securis/src/main/java/net/curisit/securis/services/UserResource.java
+++ b/securis/src/main/java/net/curisit/securis/services/UserResource.java
@@ -34,6 +34,7 @@
 import net.curisit.integrity.commons.Utils;
 import net.curisit.securis.DefaultExceptionHandler;
 import net.curisit.securis.SeCurisException;
+import net.curisit.securis.db.Application;
 import net.curisit.securis.db.Organization;
 import net.curisit.securis.db.User;
 import net.curisit.securis.ioc.EnsureTransaction;
@@ -131,7 +132,12 @@
 		}
 
 		try {
-			this.setUserOrg(user, user.getOrgsIds(), em);
+			this.setUserOrgs(user, user.getOrgsIds(), em);
+		} catch (SeCurisException e) {
+			return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
+		}
+		try {
+			this.setUserApps(user, user.getAppsIds(), em);
 		} catch (SeCurisException e) {
 			return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
 		}
@@ -149,7 +155,7 @@
 		return Response.ok(user).build();
 	}
 
-	private void setUserOrg(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
+	private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException {
 		Set<Organization> orgs = null;
 		if (orgsIds != null && !orgsIds.isEmpty()) {
 			orgs = new HashSet<>();
@@ -165,6 +171,23 @@
 
 		user.setOrganizations(orgs);
 
+	}
+
+	private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException {
+		Set<Application> apps = null;
+		if (appsIds != null && !appsIds.isEmpty()) {
+			apps = new HashSet<>();
+			for (Integer appId : appsIds) {
+				Application o = em.find(Application.class, appId);
+				if (o == null) {
+					LOG.error("User application with id {} not found in DB", appId);
+					throw new SeCurisException("User's application not found with ID: " + appId);
+				}
+				apps.add(o);
+			}
+		}
+
+		user.setApplications(apps);
 	}
 
 	@PUT
@@ -185,7 +208,12 @@
 		}
 
 		try {
-			this.setUserOrg(currentUser, user.getOrgsIds(), em);
+			this.setUserOrgs(currentUser, user.getOrgsIds(), em);
+		} catch (SeCurisException e) {
+			return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
+		}
+		try {
+			this.setUserApps(currentUser, user.getAppsIds(), em);
 		} catch (SeCurisException e) {
 			return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build();
 		}
@@ -199,8 +227,6 @@
 		} else {
 			// Password has not been modified
 			// return
-			// Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER,
-			// "User password is mandatory").build();
 		}
 
 		currentUser.setLastLogin(user.getLastLogin());
diff --git a/securis/src/main/resources/db/schema.sql b/securis/src/main/resources/db/schema.sql
index cbe5721..b665d34 100644
--- a/securis/src/main/resources/db/schema.sql
+++ b/securis/src/main/resources/db/schema.sql
@@ -73,6 +73,13 @@
   username VARCHAR(45) NOT NULL,
   organization_id INT NOT NULL,  
   PRIMARY KEY (username, organization_id));
+
+drop table IF EXISTS user_application;
+CREATE  TABLE IF NOT EXISTS user_application (
+  username VARCHAR(45) NOT NULL,
+  application_id INT NOT NULL,  
+  PRIMARY KEY (username, application_id));
+
   
 drop table IF EXISTS pack;
 CREATE  TABLE IF NOT EXISTS pack (
diff --git a/securis/src/main/webapp/index.html b/securis/src/main/webapp/index.html
index 32d0ecf..15087e7 100644
--- a/securis/src/main/webapp/index.html
+++ b/securis/src/main/webapp/index.html
@@ -28,6 +28,7 @@
 
     <!-- Load the Covalent/Material prebuilt theme -->
     <link href="node_modules/@covalent/core/theming/prebuilt/blue-orange.css" rel="stylesheet">
+
     <link href="node_modules/ng2-toastr/bundles/ng2-toastr.min.css" rel="stylesheet" />
     <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
   </head>
diff --git a/securis/src/main/webapp/package.json b/securis/src/main/webapp/package.json
index b294000..470b421 100644
--- a/securis/src/main/webapp/package.json
+++ b/securis/src/main/webapp/package.json
@@ -1,7 +1,7 @@
 {
   "name": "securis",
   "version": "2.0.0",
-  "description": "QuickStart package.json from the documentation, supplemented with testing support",
+  "description": "SeCuris front-end application based on Angular4 and Materail design",
   "scripts": {
     "build": "tsc -p src/",
     "build:watch": "tsc -p src/ -w",
@@ -24,18 +24,19 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
-    "@angular/common": "^2.4.9",
-    "@angular/compiler": "~2.4.9",
-    "@angular/core": "~2.4.9",
-    "@angular/forms": "~2.4.9",
-    "@angular/http": "~2.4.9",
-    "@angular/material": "^2.0.0-beta.2",
-    "@angular/platform-browser": "~2.4.9",
-    "@angular/platform-browser-dynamic": "~2.4.9",
-    "@angular/router": "^3.4.9",
-    "@covalent/core": "^1.0.0-beta.2",
-    "@covalent/dynamic-forms": "^1.0.0-beta.2",
-    "@covalent/http": "^1.0.0-beta.2",
+    "@angular/common": "^4.0.1",
+    "@angular/compiler": "~4.0.1",
+    "@angular/core": "~4.0.1",
+    "@angular/forms": "~4.0.1",
+    "@angular/http": "~4.0.1",
+    "@angular/animations": "~4.0.1",
+    "@angular/material": "^2.0.0-beta.3",
+    "@angular/platform-browser": "~4.0.1",
+    "@angular/platform-browser-dynamic": "~4.0.1",
+    "@angular/router": "^4.0.1",
+    "@covalent/core": "^1.0.0-beta.3",
+    "@covalent/dynamic-forms": "^1.0.0-beta.3",
+    "@covalent/http": "^1.0.0-beta.3",
     "angular-2-local-storage": "^1.0.1",
     "angular-in-memory-web-api": "~0.2.4",
     "core-js": "^2.4.1",
diff --git a/securis/src/main/webapp/sql_update.sql b/securis/src/main/webapp/sql_update.sql
index 08c0847..cfed213 100644
--- a/securis/src/main/webapp/sql_update.sql
+++ b/securis/src/main/webapp/sql_update.sql
@@ -1,2 +1,10 @@
 alter table pack add column frozen BOOLEAN NOT NULL default false;
-alter table license add column metadata_obsolete BOOLEAN NOT NULL default false;
\ No newline at end of file
+alter table license add column metadata_obsolete BOOLEAN NOT NULL default false;
+
+drop table IF EXISTS user_application;
+CREATE  TABLE IF NOT EXISTS user_application (
+  username VARCHAR(45) NOT NULL,
+  application_id INT NOT NULL,  
+  PRIMARY KEY (username, application_id));
+  
+  
\ No newline at end of file
diff --git a/securis/src/main/webapp/src/app/app.module.ts b/securis/src/main/webapp/src/app/app.module.ts
index fe86958..512e9ca 100644
--- a/securis/src/main/webapp/src/app/app.module.ts
+++ b/securis/src/main/webapp/src/app/app.module.ts
@@ -1,5 +1,6 @@
 import { NgModule }      from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import { CommonModule } from '@angular/common';
 import { FormsModule }   from '@angular/forms';
 import {MaterialModule} from '@angular/material';
@@ -54,6 +55,7 @@
             storageType: 'localStorage'
         }),
     BrowserModule,
+    BrowserAnimationsModule,
     FormsModule,
     MaterialModule,
     CovalentCoreModule.forRoot(),
diff --git a/securis/src/main/webapp/src/app/forms/user.form.component.ts b/securis/src/main/webapp/src/app/forms/user.form.component.ts
index c579366..bdf543b 100644
--- a/securis/src/main/webapp/src/app/forms/user.form.component.ts
+++ b/securis/src/main/webapp/src/app/forms/user.form.component.ts
@@ -29,12 +29,16 @@
 })
 export class UserFormComponent extends FormBase {
   allOrganizations: IComboOption[];
+  allApplications: IComboOption[];
   orgNames: string[] = [];
-  allRoles: any[] = [{"id":1, "code": "advance", "label":"Advance"}, {"id":2, "code": "admin","label":"Admin"}];
+  appNames: string[] = [];
+  allRoles: any[] = [{"id":4, "code": "basic","label":"Basic"}, {"id":1, "code": "advance", "label":"Advance"}, {"id":2, "code": "admin","label":"Admin"}];
   user_orgs: string[] = [];
+  user_apps: string[] = [];
   user_roles: any = {};
   constructor(private http: Http,
               private users: UsersService,
+              private applications: ApplicationsService,
               private organizations: OrganizationsService,
               router: Router,
               toaster: ToastsManager,
@@ -50,6 +54,10 @@
     this.user_orgs.forEach(orgName => {
       var selectedOrg = this.allOrganizations.find(org => org.label === orgName);
       this.data.organizations_ids.push(selectedOrg.id);
+    });
+    this.user_apps.forEach(appName => {
+      var selectedApp = this.allApplications.find(app => app.label === appName);
+      this.data.applications_ids.push(selectedApp.id);
     });
     this.user_roles.advance && this.data.roles.push(1);
     this.user_roles.admin && this.data.roles.push(2);
@@ -71,6 +79,16 @@
           },
           err => console.error('Error loading organizations')
         );
+      this.applications.get()
+        .map(list => list.map((app : any) => <IComboOption>{id: app.id, label: app.name}))
+        .subscribe(
+          data => {
+            this.allApplications = (<IComboOption[]>data).sort((e1, e2) => e1.label.localeCompare(e2.label));
+            this.appNames = this.allApplications.map(org => org.label);
+            this._loadApps();
+          },
+          err => console.error('Error loading organizations')
+        );
   }
 
   goBack(): void {
@@ -84,6 +102,14 @@
       });
     }
   }
+  _loadApps() {
+    if (this.data && this.data.applications_ids && this.allApplications && this.allApplications.length > 0) { 
+      this.data.applications_ids.forEach((appId : number) => {
+          var selectedApp = this.allApplications.find(app => app.id === appId);
+          this.user_apps.push(selectedApp.label);
+      });
+    }
+  }
   init() : void {
     this.loadCombos();
     this.user_orgs = [];
@@ -92,6 +118,7 @@
     super.reset();
     super.prepareInitialData('username', {
       organizations_ids: [],
+      applications_ids: [],
       roles: []
     }, (data) => {
        this._loadOrgs();
diff --git a/securis/src/main/webapp/src/app/forms/user.form.html b/securis/src/main/webapp/src/app/forms/user.form.html
index 1a3caa4..11ecf99 100644
--- a/securis/src/main/webapp/src/app/forms/user.form.html
+++ b/securis/src/main/webapp/src/app/forms/user.form.html
@@ -7,18 +7,6 @@
 		<span flex></span>
 		<button md-icon-button (click)="save()"><md-icon>save</md-icon></button>
 	</md-toolbar>
-	<!--
-			 username: 'rym',
-    roles: [ 1 ],
-    lastLogin: 1488885433000,
-    modificationTimestamp: 1479898458000,
-    email: 'rbouchair@curistec.com',
-    first_name: 'Rym',
-    last_name: 'Bouchair',
-    creation_timestamp: 1479898458000,
-    organizations_ids: [ 1, 2, 5, 6, 7, 8 ]
-}
-		-->
 	<div class="margin" layout-align-gt-xs="center start" layout-fill="" layout-gt-xs="row">
 		<md-card flex="70">
 			<md-card-title>
@@ -79,7 +67,7 @@
 								<error-checker [fieldName]="$L.get('field.email')" [formField]="form.controls.email"></error-checker>
 							</div>
 						</div>
-							<div layout="row" layout-fill layout-padding >
+						<div layout="row" layout-fill layout-padding >
 							<td-chips flex [mdTooltip]="$L.get('Organizations that user can access')" [placeholder]="$L.get('Select organizations')" 
 									[items]="orgNames" [(ngModel)]="user_orgs" name="user_orgs" requireMatch>
 							</td-chips>
@@ -92,6 +80,11 @@
 								</md-checkbox>
 							</div>
 						</div>
+						<div layout="row" layout-fill layout-padding >
+							<td-chips flex [mdTooltip]="$L.get('Applications that user can access')" [placeholder]="$L.get('Select applications')" 
+									[items]="appNames" [(ngModel)]="user_apps" name="user_apps" requireMatch>
+							</td-chips>
+						</div>
 						<div layout="row" layout-fill layout-padding *ngIf="!isNew">
 							<field-readonly [value]="data.lastLogin || '' | timeAgo" label="field.lastLogin" flex></field-readonly>
 							<field-readonly [value]="data.creation_timestamp | date: 'medium'" label="field.creation_timestamp" flex></field-readonly>
diff --git a/securis/src/main/webapp/src/app/listing/user.list.component.html b/securis/src/main/webapp/src/app/listing/user.list.component.html
index 44cc658..766da66 100644
--- a/securis/src/main/webapp/src/app/listing/user.list.component.html
+++ b/securis/src/main/webapp/src/app/listing/user.list.component.html
@@ -6,7 +6,7 @@
     <span class="push-left-sm" *ngIf="filteredItems < data.length">
         <span class="md-body-1">{{filteredItems}} of {{data.length}} applications filtered</span>
     </span>
-    <td-search-box #searchBox class="push-right-sm" placeholder="Search here" (searchDebounce)="search($event)" flex>
+    <td-search-box #searchBox class="push-right-sm" [alwaysVisible]="false" [placeholder]="$L.get('Search here')" (searchDebounce)="search($event)" flex>
     </td-search-box>
     <button md-mini-fab color="accent" (click)="create()" [mdTooltip]="$L.get('Create a new application')">
         <md-icon>add</md-icon>
diff --git a/securis/src/main/webapp/systemjs.config.js b/securis/src/main/webapp/systemjs.config.js
index 476858d..4cb7dfc 100644
--- a/securis/src/main/webapp/systemjs.config.js
+++ b/securis/src/main/webapp/systemjs.config.js
@@ -4,7 +4,7 @@
  */
 (function (global) {
 
-  var ANGULAR_LIBS = ['core', 'http', 'common', 'compiler', 'material', 'flex-layout', //
+  var ANGULAR_LIBS = ['core', 'http', 'common', 'compiler', 'material', 'flex-layout', 'animations', //
                       'router', 'forms', 'platform-browser', 'platform-browser-dynamic', 'common'];
   var COVALENT_LIBS = ['core', 'http', 'dynamic-forms'];
   var mapping = {
@@ -12,6 +12,8 @@
      main: 'src/main.js',
     'app': 'src/app',
     'environments': 'src/environments',
+    '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
+    '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',   
 
     // other libraries
     'rxjs': 'npm:rxjs',
@@ -22,7 +24,7 @@
   }
 
   ANGULAR_LIBS.forEach(function (libName) {
-    mapping['@angular/' + libName] = 'npm:@angular/' + libName + '/bundles/' + libName + '.umd.js';
+      mapping['@angular/' + libName] = 'npm:@angular/' + libName + '/bundles/' + libName + '.umd.js';
   });
 
   COVALENT_LIBS.forEach(function (libName) {

--
Gitblit v1.3.2