/*
documentr - Edit, maintain, and present software documentation on the web.
Copyright (C) 2012-2013 Maik Schreiber
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.blizzy.documentr.access;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.google.common.collect.Sets;
import de.blizzy.documentr.access.GrantedAuthorityTarget.Type;
import de.blizzy.documentr.page.IPageStore;
import de.blizzy.documentr.page.PageNotFoundException;
import de.blizzy.documentr.repository.IGlobalRepositoryManager;
/**
* <p>documentr's {@link PermissionEvaluator}.</p>
*
* <p>Permissions are handled recursively. For example, if asked if a user has a permission on a specific page,
* and they don't, lookup continues on the page's branch. If the user does not have the permission on the branch,
* lookup continues on the branch's project. If they don't have the permission on the project, lookup continues
* on the "application object."</p>
*
* <p>Granting the {@link Permission#ADMIN ADMIN} permission on an object implies granting all other permissions
* on the same object.</p>
*
* <p>It is not possible to grant permissions on a higher-level object, then deny those permissions on any
* of their children. For example, having granted the {@link Permission#VIEW VIEW} permission on a project
* allows view access to all branches and pages of that project. In that case it is not possible to deny
* viewing a particular branch of the project.</p>
*/
@Component
public class DocumentrPermissionEvaluator implements PermissionEvaluator {
@Autowired
private IPageStore pageStore;
@Autowired
private UserStore userStore;
@Autowired
private IGlobalRepositoryManager repoManager;
@Autowired
private LoginNameUserDetailsService userDetailsService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
Assert.notNull(authentication);
Assert.notNull(targetDomainObject);
Assert.notNull(permission);
// not used
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
Assert.notNull(authentication);
Assert.notNull(targetId);
Assert.hasLength(targetType);
Assert.notNull(permission);
// not used
return false;
}
public boolean hasApplicationPermission(Authentication authentication, Permission permission) {
return hasApplicationPermission(authentication.getAuthorities(), permission);
}
private boolean hasApplicationPermission(Collection<? extends GrantedAuthority> authorities,
Permission permission) {
for (GrantedAuthority authority : authorities) {
if (authority instanceof PermissionGrantedAuthority) {
PermissionGrantedAuthority pga = (PermissionGrantedAuthority) authority;
GrantedAuthorityTarget target = pga.getTarget();
Type type = target.getType();
if ((type == Type.APPLICATION) &&
hasPermission(pga, permission)) {
return true;
}
}
}
return false;
}
public boolean hasProjectPermission(Authentication authentication, String projectName, Permission permission) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority instanceof PermissionGrantedAuthority) {
PermissionGrantedAuthority pga = (PermissionGrantedAuthority) authority;
GrantedAuthorityTarget target = pga.getTarget();
Type type = target.getType();
String id = target.getTargetId();
if ((type == Type.PROJECT) &&
id.equals(projectName) &&
hasPermission(pga, permission)) {
return true;
}
}
}
return hasApplicationPermission(authentication, permission);
}
public boolean hasAnyProjectPermission(Authentication authentication, Permission permission) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority instanceof PermissionGrantedAuthority) {
PermissionGrantedAuthority pga = (PermissionGrantedAuthority) authority;
GrantedAuthorityTarget target = pga.getTarget();
Type type = target.getType();
if ((type == Type.PROJECT) &&
hasPermission(pga, permission)) {
return true;
}
}
}
return hasApplicationPermission(authentication, permission);
}
public boolean hasBranchPermission(Authentication authentication, String projectName, String branchName,
Permission permission) {
String targetId = projectName + "/" + branchName; //$NON-NLS-1$
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority instanceof PermissionGrantedAuthority) {
PermissionGrantedAuthority pga = (PermissionGrantedAuthority) authority;
GrantedAuthorityTarget target = pga.getTarget();
Type type = target.getType();
String id = target.getTargetId();
if ((type == Type.BRANCH) &&
id.equals(targetId) &&
hasPermission(pga, permission)) {
return true;
}
}
}
return hasProjectPermission(authentication, projectName, permission);
}
public boolean hasAnyBranchPermission(Authentication authentication, String projectName, Permission permission) {
String targetIdPrefix = projectName + "/"; //$NON-NLS-1$
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (authority instanceof PermissionGrantedAuthority) {
PermissionGrantedAuthority pga = (PermissionGrantedAuthority) authority;
GrantedAuthorityTarget target = pga.getTarget();
Type type = target.getType();
String id = target.getTargetId();
if ((type == Type.BRANCH) &&
id.startsWith(targetIdPrefix) &&
hasPermission(pga, permission)) {
return true;
}
}
}
return hasProjectPermission(authentication, projectName, permission);
}
public boolean hasPagePermission(Authentication authentication, String projectName, String branchName,
String path, Permission permission) {
if (hasBranchPermission(authentication, projectName, branchName, Permission.ADMIN)) {
return true;
} else if (hasBranchPermission(authentication, projectName, branchName, permission)) {
try {
String viewRestrictionRole = pageStore.getViewRestrictionRole(projectName, branchName, path);
return StringUtils.isBlank(viewRestrictionRole) ||
hasRoleOnBranch(authentication, projectName, branchName, viewRestrictionRole);
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
} catch (PageNotFoundException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
}
return false;
}
public boolean hasPagePermissionInOtherBranches(Authentication authentication, String projectName,
String branchName, String path, Permission permission) {
try {
List<String> branches = repoManager.listProjectBranches(projectName);
for (String branch : branches) {
if (!branch.equals(branchName)) {
boolean result = hasPagePermission(authentication, projectName, branch, path, permission);
if (result) {
return true;
}
}
}
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
return false;
}
public Set<String> getBranchesForPermission(Authentication authentication, Permission permission) {
try {
Set<String> branches = Sets.newHashSet();
for (String project : repoManager.listProjects()) {
for (String branch : repoManager.listProjectBranches(project)) {
if (hasBranchPermission(authentication, project, branch, permission)) {
branches.add(project + "/" + branch); //$NON-NLS-1$
}
}
}
return branches;
} catch (IOException e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
}
public boolean isAdmin(String loginName) {
try {
UserDetails user = userDetailsService.loadUserByUsername(loginName);
return hasApplicationPermission(user.getAuthorities(), Permission.ADMIN);
} catch (UsernameNotFoundException e) {
// okay
}
return false;
}
public boolean isLastAdminRole(String roleName) {
try {
if (userStore.getRole(roleName).getPermissions().contains(Permission.ADMIN)) {
Set<String> roles = Sets.newHashSet(userStore.listRoles());
roles.remove(roleName);
// find all roles containing the ADMIN permission
Set<String> adminRoles = Sets.newHashSet();
for (String role : roles) {
Role r = userStore.getRole(role);
if (r.getPermissions().contains(Permission.ADMIN)) {
adminRoles.add(role);
}
}
// check whether any of the admin roles is granted to any user on the "application" object
if (!adminRoles.isEmpty()) {
List<String> users = userStore.listUsers();
for (String user : users) {
List<RoleGrantedAuthority> authorities = userStore.getUserAuthorities(user);
for (RoleGrantedAuthority rga : authorities) {
for (String role : adminRoles) {
if (rga.getRoleName().equals(role) &&
rga.getTarget().equals(GrantedAuthorityTarget.APPLICATION)) {
return false;
}
}
}
}
}
return true;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
private boolean hasRoleOnBranch(Authentication authentication, String projectName, String branchName,
String roleName) throws IOException {
if (authentication.isAuthenticated()) {
List<RoleGrantedAuthority> authorities = userStore.getUserAuthorities(authentication.getName());
for (RoleGrantedAuthority rga : authorities) {
if (rga.getRoleName().equals(roleName)) {
GrantedAuthorityTarget target = rga.getTarget();
switch (target.getType()) {
case APPLICATION:
return true;
case PROJECT:
if (target.getTargetId().equals(projectName)) {
return true;
}
break;
case BRANCH:
if (target.getTargetId().equals(projectName + "/" + branchName)) { //$NON-NLS-1$
return true;
}
break;
}
}
}
}
return false;
}
private boolean hasPermission(PermissionGrantedAuthority authority, Permission permission) {
Permission p = authority.getPermission();
return (p == Permission.ADMIN) || (p == permission);
}
}