Java tutorial
/******************************************************************************* * Copyright (c) 2013 Hypersocket Limited. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html ******************************************************************************/ package com.hypersocket.permissions; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.hypersocket.auth.AuthenticatedServiceImpl; import com.hypersocket.auth.AuthenticationPermission; import com.hypersocket.events.EventService; import com.hypersocket.events.SystemEvent; import com.hypersocket.i18n.I18N; import com.hypersocket.properties.PropertyCategory; import com.hypersocket.realm.PasswordPermission; import com.hypersocket.realm.Principal; import com.hypersocket.realm.ProfilePermission; import com.hypersocket.realm.Realm; import com.hypersocket.realm.RealmAdapter; import com.hypersocket.realm.RealmService; import com.hypersocket.realm.RolePermission; import com.hypersocket.realm.events.GroupEvent; import com.hypersocket.realm.events.UserEvent; import com.hypersocket.resource.ResourceChangeException; import com.hypersocket.resource.ResourceCreationException; import com.hypersocket.resource.ResourceNotFoundException; import com.hypersocket.role.events.RoleCreatedEvent; import com.hypersocket.role.events.RoleDeletedEvent; import com.hypersocket.role.events.RoleEvent; import com.hypersocket.role.events.RoleUpdatedEvent; import com.hypersocket.tables.ColumnSort; @Service public class PermissionServiceImpl extends AuthenticatedServiceImpl implements PermissionService, ApplicationListener<SystemEvent> { static Logger log = LoggerFactory.getLogger(PermissionServiceImpl.class); @Autowired PermissionRepository repository; @Autowired RealmService realmService; @Autowired @Qualifier("transactionManager") protected PlatformTransactionManager txManager; @Autowired EventService eventService; Set<Long> registerPermissionIds = new HashSet<Long>(); Set<Long> nonSystemPermissionIds = new HashSet<Long>(); Map<String, PermissionType> registeredPermissions = new HashMap<String, PermissionType>(); CacheManager cacheManager; Cache permissionsCache; Cache roleCache; @PostConstruct private void postConstruct() { TransactionTemplate tmpl = new TransactionTemplate(txManager); tmpl.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { PermissionCategory cat = registerPermissionCategory(RESOURCE_BUNDLE, "category.permissions"); registerPermission(SystemPermission.SYSTEM_ADMINISTRATION, cat); registerPermission(SystemPermission.SYSTEM, cat); } }); cacheManager = CacheManager.newInstance(); permissionsCache = new Cache("permissionsCache", 5000, false, false, 60 * 60, 60 * 60); cacheManager.addCache(permissionsCache); roleCache = new Cache("roleCache", 5000, false, false, 60 * 60, 60 * 60); cacheManager.addCache(roleCache); realmService.registerRealmListener(new RealmAdapter() { @Override public boolean hasCreatedDefaultResources(Realm realm) { return repository.getRoleByName(ROLE_ADMINISTRATOR, realm) != null; } @Override public void onCreateRealm(Realm realm) { if (log.isInfoEnabled()) { log.info("Creating Administrator role for realm " + realm.getName()); } repository.createRole(ROLE_ADMINISTRATOR, realm, false, false, true, true); if (log.isInfoEnabled()) { log.info("Creating Everyone role for realm " + realm.getName()); } Set<Permission> perms = new HashSet<Permission>(); perms.add(getPermission(AuthenticationPermission.LOGON.getResourceKey())); perms.add(getPermission(ProfilePermission.READ.getResourceKey())); perms.add(getPermission(ProfilePermission.UPDATE.getResourceKey())); perms.add(getPermission(PasswordPermission.CHANGE.getResourceKey())); repository.createRole(ROLE_EVERYONE, realm, false, true, false, true, perms); } }); eventService.registerEvent(RoleEvent.class, RESOURCE_BUNDLE); eventService.registerEvent(RoleCreatedEvent.class, RESOURCE_BUNDLE); eventService.registerEvent(RoleUpdatedEvent.class, RESOURCE_BUNDLE); eventService.registerEvent(RoleDeletedEvent.class, RESOURCE_BUNDLE); } @Override public PermissionCategory registerPermissionCategory(String resourceBundle, String resourceKey) { PermissionCategory result = repository.getCategoryByKey(resourceBundle, resourceKey); if (result == null) { result = repository.createCategory(resourceBundle, resourceKey); } return result; } @Override public Permission registerPermission(PermissionType type, PermissionCategory category) { registeredPermissions.put(type.getResourceKey(), type); return registerPermission(type.getResourceKey(), type.isSystem(), category, type.isHidden()); } protected Permission registerPermission(String resourceKey, boolean system, PermissionCategory category, boolean hidden) { Permission result = repository.getPermissionByResourceKey(resourceKey); if (result == null) { repository.createPermission(resourceKey, system, category, hidden); result = repository.getPermissionByResourceKey(resourceKey); } registerPermissionIds.add(result.getId()); if (!system) { nonSystemPermissionIds.add(result.getId()); } return result; } @Override public Role createRole(String name, Realm realm) throws AccessDeniedException, ResourceCreationException { assertPermission(RolePermission.CREATE); try { getRole(name, realm); ResourceCreationException ex = new ResourceCreationException(RESOURCE_BUNDLE, "error.role.alreadyExists", name); throw ex; } catch (ResourceNotFoundException re) { return repository.createRole(name, realm, false, false, false, false); } } @Override public Role createRole(String name, Realm realm, List<Principal> principals, List<Permission> permissions) throws AccessDeniedException, ResourceCreationException { assertPermission(RolePermission.CREATE); try { getRole(name, realm); ResourceCreationException ex = new ResourceCreationException(RESOURCE_BUNDLE, "error.role.alreadyExists", name); throw ex; } catch (ResourceNotFoundException re) { try { Role role = new Role(); role.setName(name); role.setRealm(realm); repository.saveRole(role, realm, principals.toArray(new Principal[0]), permissions); for (Principal p : principals) { permissionsCache.remove(p); } eventService.publishEvent(new RoleCreatedEvent(this, getCurrentSession(), realm, role)); return role; } catch (Throwable te) { eventService.publishEvent(new RoleCreatedEvent(this, name, te, getCurrentSession(), realm)); throw new ResourceCreationException(RESOURCE_BUNDLE, "error.resourceCreateError", te.getMessage()); } } } @Override public Permission getPermission(String resourceKey) { return repository.getPermissionByResourceKey(resourceKey); } @Override public void assignRole(Role role, Principal principal) throws AccessDeniedException { assertAnyPermission(PermissionStrategy.INCLUDE_IMPLIED, RolePermission.CREATE, RolePermission.UPDATE); try { repository.assignRole(role, principal); permissionsCache.remove(principal); eventService.publishEvent(new RoleUpdatedEvent(this, getCurrentSession(), role.getRealm(), role)); } catch (Throwable e) { eventService.publishEvent( new RoleUpdatedEvent(this, role.getName(), e, getCurrentSession(), role.getRealm())); } } @SuppressWarnings("unchecked") @Override public Set<Permission> getPrincipalPermissions(Principal principal) throws AccessDeniedException { if (!permissionsCache.isElementInMemory(principal) || (permissionsCache.get(principal) == null || permissionsCache.isExpired(permissionsCache.get(principal)))) { List<Principal> principals = realmService.getAssociatedPrincipals(principal); Set<Permission> principalPermissions = repository.getPrincipalPermissions(principals); Set<Role> roles = repository.getAllUserRoles(principal.getRealm()); for (Role r : roles) { principalPermissions.addAll(r.getPermissions()); } roles = repository.getRolesForPrincipal(principals); for (Role r : roles) { if (r.isAllPermissions()) { principalPermissions.addAll(repository.getAllPermissions(registerPermissionIds, false)); } } permissionsCache.put(new Element(principal, principalPermissions)); } return (Set<Permission>) permissionsCache.get(principal).getObjectValue(); } @SuppressWarnings("unchecked") @Override public Set<Role> getPrincipalRoles(Principal principal) throws AccessDeniedException { if (!roleCache.isElementInMemory(principal) || (roleCache.get(principal) == null || roleCache.isExpired(roleCache.get(principal)))) { roleCache.put(new Element(principal, repository.getRolesForPrincipal(realmService.getAssociatedPrincipals(principal)))); } return (Set<Role>) roleCache.get(principal).getObjectValue(); } private void recurseImpliedPermissions(PermissionType t, Set<PermissionType> derivedPermissions) { if (t != null && !derivedPermissions.contains(t)) { derivedPermissions.add(t); if (t.impliesPermissions() != null) { for (PermissionType t2 : t.impliesPermissions()) { recurseImpliedPermissions(t2, derivedPermissions); } } } } protected void verifyPermission(Principal principal, PermissionStrategy strategy, Set<Permission> principalPermissions, PermissionType... permissions) throws AccessDeniedException { if (principal == null) { throw new AccessDeniedException(); } if (!hasSystemPermission(principal)) { Set<PermissionType> derivedPrincipalPermissions = new HashSet<PermissionType>(); for (Permission t : principalPermissions) { if (!registeredPermissions.containsKey(t.getResourceKey())) { continue; } switch (strategy) { case INCLUDE_IMPLIED: recurseImpliedPermissions(registeredPermissions.get(t.getResourceKey()), derivedPrincipalPermissions); break; case EXCLUDE_IMPLIED: derivedPrincipalPermissions.add(registeredPermissions.get(t.getResourceKey())); break; } } for (PermissionType t : permissions) { for (PermissionType p : derivedPrincipalPermissions) { if (t.getResourceKey().equals(p.getResourceKey())) { return; } } } throw new AccessDeniedException( I18N.getResource(getCurrentLocale(), PermissionService.RESOURCE_BUNDLE, "error.accessDenied")); } } @Override public void verifyPermission(Principal principal, PermissionStrategy strategy, PermissionType... permissions) throws AccessDeniedException { if (principal == null) { if (log.isInfoEnabled()) { log.info("Denying permission because principal is null"); } throw new AccessDeniedException(); } if (!hasSystemPermission(principal)) { Set<Permission> principalPermissions = getPrincipalPermissions(principal); verifyPermission(principal, strategy, principalPermissions, permissions); } } @Override public boolean hasSystemPermission(Principal principal) { try { return hasSystemPrincipal(getPrincipalPermissions(principal)); } catch (AccessDeniedException e) { return false; } } protected boolean hasSystemPrincipal(Set<Permission> principalPermissions) { for (Permission p : principalPermissions) { if (p.getResourceKey().equals(SystemPermission.SYSTEM.getResourceKey()) || p.getResourceKey().equals(SystemPermission.SYSTEM_ADMINISTRATION.getResourceKey())) { return true; } } return false; } @Override public Set<Principal> getUsersWithPermissions(PermissionType permissions) { return repository.getPrincipalsWithPermissions(permissions); } @Override public Role getRole(String name, Realm realm) throws ResourceNotFoundException, AccessDeniedException { assertAnyPermission(RolePermission.READ); Role role = repository.getRoleByName(name, realm); if (role == null) { throw new ResourceNotFoundException(RESOURCE_BUNDLE, "error.realmNotFound", name); } return role; } @Override public void deleteRole(Role role) throws AccessDeniedException, ResourceChangeException { assertPermission(RolePermission.DELETE); try { repository.deleteRole(role); permissionsCache.removeAll(); eventService.publishEvent(new RoleDeletedEvent(this, getCurrentSession(), role.getRealm(), role)); } catch (Throwable te) { eventService.publishEvent( new RoleDeletedEvent(this, role.getName(), te, getCurrentSession(), role.getRealm())); throw new ResourceChangeException(RESOURCE_BUNDLE, "error.resourceDeleteError", te.getMessage()); } } @Override public List<Role> allRoles(Realm realm) throws AccessDeniedException { assertAnyPermission(RolePermission.READ); return repository.getRolesForRealm(realm); } @Override public List<Permission> allPermissions() { return repository.getAllPermissions(registerPermissionIds, getCurrentRealm().isSystem()); } private <T> Set<T> getEntitiesNotIn(Collection<T> source, Collection<T> from, EntityMatch<T> validation) { Set<T> result = new HashSet<T>(); for (T t : from) { if (!source.contains(t)) { if (validation == null || validation.validate(t)) { result.add(t); } } } return result; } @Override public void grantPermission(Role role, Permission permission) throws AccessDeniedException, ResourceChangeException { assertPermission(RolePermission.UPDATE); try { repository.grantPermission(role, permission); eventService.publishEvent(new RoleUpdatedEvent(this, getCurrentSession(), role.getRealm(), role)); } catch (Throwable e) { eventService.publishEvent( new RoleUpdatedEvent(this, role.getName(), e, getCurrentSession(), role.getRealm())); throw new ResourceChangeException(RESOURCE_BUNDLE, "error.resourceUpdateError", e.getMessage()); } } @Override public Role updateRole(Role role, String name, List<Principal> principals, List<Permission> permissions) throws AccessDeniedException, ResourceChangeException { assertPermission(RolePermission.UPDATE); try { Role anotherRole = getRole(name, role.getRealm()); if (!anotherRole.getId().equals(role.getId())) { throw new ResourceChangeException(RESOURCE_BUNDLE, "error.role.alreadyExists", name); } } catch (ResourceNotFoundException ne) { role.setName(name); } try { Set<Principal> unassignPrincipals = getEntitiesNotIn(principals, role.getPrincipals(), new EntityMatch<Principal>() { @Override public boolean validate(Principal t) { return getCurrentRealm().equals(t.getRealm()); } }); Set<Principal> assignPrincipals = getEntitiesNotIn(role.getPrincipals(), principals, new EntityMatch<Principal>() { @Override public boolean validate(Principal t) { return getCurrentRealm().equals(t.getRealm()); } }); Set<Permission> revokePermissions = getEntitiesNotIn(permissions, role.getPermissions(), null); Set<Permission> grantPermissions = getEntitiesNotIn(role.getPermissions(), permissions, null); repository.updateRole(role, unassignPrincipals, assignPrincipals, revokePermissions, grantPermissions); permissionsCache.removeAll(); eventService.publishEvent(new RoleUpdatedEvent(this, getCurrentSession(), role.getRealm(), role)); return role; } catch (Throwable te) { eventService.publishEvent( new RoleUpdatedEvent(this, role.getName(), te, getCurrentSession(), role.getRealm())); throw new ResourceChangeException(RESOURCE_BUNDLE, "error.resourceUpdateError", te.getMessage()); } } @Override public Role getRoleById(Long id, Realm realm) throws ResourceNotFoundException, AccessDeniedException { assertPermission(RolePermission.READ); Role role = repository.getRoleById(id); if (role.getRealm() != null && !role.getRealm().equals(realm)) { throw new ResourceNotFoundException(RESOURCE_BUNDLE, "error.invalidRole", id); } return role; } @Override public Permission getPermissionById(Long id) { return repository.getPermissionById(id); } private interface EntityMatch<T> { boolean validate(T t); } @Override public Long getRoleCount(String searchPattern) throws AccessDeniedException { assertPermission(RolePermission.READ); return repository.countRoles(getCurrentRealm(), searchPattern); } @Override public List<?> getRoles(String searchPattern, int start, int length, ColumnSort[] sorting) throws AccessDeniedException { assertPermission(RolePermission.READ); return repository.searchRoles(getCurrentRealm(), searchPattern, start, length, sorting); } @Override public Role getPersonalRole(Principal principal) { return repository.getPersonalRole(principal); } @Override public List<PropertyCategory> getRoleTemplates() throws AccessDeniedException { assertPermission(RolePermission.READ); return new ArrayList<PropertyCategory>(); } @Override public void onApplicationEvent(SystemEvent event) { if (event instanceof GroupEvent || event instanceof UserEvent) { permissionsCache.removeAll(); } } }