001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.webapp.security; 021 022import static org.jboss.seam.ScopeType.CONVERSATION; 023 024import java.io.Serializable; 025import java.security.Principal; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031 032import javax.faces.context.FacesContext; 033import javax.faces.model.SelectItem; 034import javax.servlet.http.HttpServletRequest; 035 036import org.apache.commons.lang.StringUtils; 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.jboss.seam.ScopeType; 040import org.jboss.seam.annotations.Factory; 041import org.jboss.seam.annotations.In; 042import org.jboss.seam.annotations.Name; 043import org.jboss.seam.annotations.Observer; 044import org.jboss.seam.annotations.Scope; 045import org.jboss.seam.annotations.intercept.BypassInterceptors; 046import org.jboss.seam.core.Events; 047import org.jboss.seam.faces.FacesMessages; 048import org.jboss.seam.international.StatusMessage; 049import org.nuxeo.common.utils.UserAgentMatcher; 050import org.nuxeo.common.utils.i18n.Labeler; 051import org.nuxeo.ecm.core.api.CoreSession; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.api.NuxeoPrincipal; 054import org.nuxeo.ecm.core.api.security.ACP; 055import org.nuxeo.ecm.core.api.security.PermissionProvider; 056import org.nuxeo.ecm.core.api.security.SecurityConstants; 057import org.nuxeo.ecm.core.api.security.UserEntry; 058import org.nuxeo.ecm.core.api.security.UserVisiblePermission; 059import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 060import org.nuxeo.ecm.platform.query.api.PageSelection; 061import org.nuxeo.ecm.platform.query.api.PageSelections; 062import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 063import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 064import org.nuxeo.ecm.platform.usermanager.UserManager; 065import org.nuxeo.ecm.webapp.base.InputController; 066import org.nuxeo.ecm.webapp.helpers.EventNames; 067import org.nuxeo.runtime.api.Framework; 068 069/** 070 * Provides security related methods. 071 * 072 * @author Razvan Caraghin 073 */ 074@Name("securityActions") 075@Scope(CONVERSATION) 076public class SecurityActionsBean extends InputController implements SecurityActions, Serializable { 077 078 private static final long serialVersionUID = -7190826911734958662L; 079 080 private static final Log log = LogFactory.getLog(SecurityActionsBean.class); 081 082 @In(create = true) 083 protected transient NavigationContext navigationContext; 084 085 @In(create = true, required = false) 086 protected transient CoreSession documentManager; 087 088 @In(create = true) 089 protected PermissionActionListManager permissionActionListManager; 090 091 @In(create = true) 092 protected PermissionListManager permissionListManager; 093 094 @In(create = true) 095 protected PrincipalListManager principalListManager; 096 097 @In(create = true) 098 protected transient UserManager userManager; 099 100 @In(create = true) 101 protected NuxeoPrincipal currentUser; 102 103 protected static final String[] SEED_PERMISSIONS_TO_CHECK = { SecurityConstants.WRITE_SECURITY, 104 SecurityConstants.READ_SECURITY }; 105 106 private static final Labeler labeler = new Labeler("label.security.permission"); 107 108 protected String[] CACHED_PERMISSION_TO_CHECK; 109 110 protected SecurityData securityData; 111 112 protected boolean obsoleteSecurityData = true; 113 114 protected PageSelections<String> entries; 115 116 protected transient List<String> cachedValidatedUserAndGroups; 117 118 protected transient List<String> cachedDeletedUserAndGroups; 119 120 private Boolean blockRightInheritance; 121 122 protected String selectedEntry; 123 124 protected List<String> selectedEntries; 125 126 @Override 127 @Observer(value = EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED, create = false) 128 @BypassInterceptors 129 public void resetSecurityData() { 130 obsoleteSecurityData = true; 131 blockRightInheritance = null; 132 } 133 134 @Override 135 public void rebuildSecurityData() { 136 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 137 if (currentDocument != null) { 138 if (securityData == null) { 139 securityData = new SecurityData(); 140 securityData.setDocumentType(currentDocument.getType()); 141 } 142 ACP acp = documentManager.getACP(currentDocument.getRef()); 143 144 if (acp != null) { 145 SecurityDataConverter.convertToSecurityData(acp, securityData); 146 } else { 147 securityData.clear(); 148 } 149 150 reconstructTableModel(); 151 152 // Check if the inherited rights are activated 153 List<String> deniedPerms = securityData.getCurrentDocDeny().get(SecurityConstants.EVERYONE); 154 if (deniedPerms != null && deniedPerms.contains(SecurityConstants.EVERYTHING)) { 155 blockRightInheritance = Boolean.TRUE; 156 } 157 158 if (blockRightInheritance == null) { 159 blockRightInheritance = Boolean.FALSE; 160 } 161 obsoleteSecurityData = false; 162 } 163 } 164 165 /** 166 * Update the dataTableModel from the current {@link SecurityData} this method is automatically called by 167 * rebuildSecurityData method 168 */ 169 protected void reconstructTableModel() { 170 List<String> items = getCurrentDocumentUsers(); 171 entries = new PageSelections<String>(); 172 if (items != null) { 173 for (String item : items) { 174 if (SecurityConstants.EVERYONE.equals(item)) { 175 final List<String> grantedPerms = securityData.getCurrentDocGrant().get(item); 176 final List<String> deniedPerms = securityData.getCurrentDocDeny().get(item); 177 if (deniedPerms != null && deniedPerms.contains(SecurityConstants.EVERYTHING) 178 && grantedPerms == null && deniedPerms.size() == 1) { 179 // the only perm is deny everything, there is no need to display the row 180 continue; 181 } 182 } 183 entries.add(new PageSelection<String>(item, false)); 184 } 185 } 186 } 187 188 @Override 189 public PageSelections<String> getDataTableModel() { 190 if (obsoleteSecurityData) { 191 // lazy initialization at first time access 192 rebuildSecurityData(); 193 } 194 195 return entries; 196 } 197 198 @Override 199 public SecurityData getSecurityData() { 200 if (obsoleteSecurityData) { 201 // lazy initialization at first time access 202 rebuildSecurityData(); 203 } 204 return securityData; 205 } 206 207 @Override 208 public String updateSecurityOnDocument() { 209 List<UserEntry> modifiableEntries = SecurityDataConverter.convertToUserEntries(securityData); 210 ACP acp = currentDocument.getACP(); 211 212 if (acp == null) { 213 acp = new ACPImpl(); 214 } 215 216 acp.setRules(modifiableEntries.toArray(new UserEntry[0])); 217 218 currentDocument.setACP(acp, true); 219 documentManager.save(); 220 Events.instance().raiseEvent(EventNames.DOCUMENT_SECURITY_CHANGED); 221 222 // Reread data from the backend to be sure the current bean 223 // state is uptodate w.r.t. the real backend state 224 rebuildSecurityData(); 225 226 // Type currentType = typeManager.getType(currentDocument.getType()); 227 // return applicationController.getPageOnEditedDocumentType(currentType); 228 229 // Forward to default view, that's not what we want 230 // return navigationContext.getActionResult(currentDocument, UserAction.AFTER_EDIT); 231 232 // Temporary fix, to avoid forward to default_view. 233 // The same page is reloaded after submit. 234 // May use UserAction, with new kind of action (AFTER_EDIT_RIGHTS)? 235 return null; 236 } 237 238 @Override 239 public String addPermission(String principalName, String permissionName, boolean grant) { 240 if (securityData == null) { 241 securityData = getSecurityData(); 242 } 243 244 String grantPerm = permissionName; 245 String denyPerm = permissionName; 246 List<UserVisiblePermission> uvps = getVisibleUserPermissions(securityData.getDocumentType()); 247 if (uvps != null) { 248 for (UserVisiblePermission uvp : uvps) { 249 if (uvp.getId().equals(permissionName)) { 250 grantPerm = uvp.getPermission(); 251 denyPerm = uvp.getDenyPermission(); 252 break; 253 } 254 } 255 } else { 256 log.debug("no entry for documentType in visibleUserPermissions this should never happend, using default mapping ..."); 257 } 258 259 if (grant) { 260 // remove the opposite rule if any 261 boolean removed = securityData.removeModifiablePrivilege(principalName, denyPerm, !grant); 262 if (!removed) { 263 removed = securityData.removeModifiablePrivilege(principalName, grantPerm, !grant); 264 } 265 // add rule only if none was removed 266 if (!removed) { 267 securityData.addModifiablePrivilege(principalName, grantPerm, grant); 268 } 269 } else { 270 // remove the opposite rule if any 271 boolean removed = securityData.removeModifiablePrivilege(principalName, grantPerm, !grant); 272 if (!removed) { 273 removed = securityData.removeModifiablePrivilege(principalName, denyPerm, !grant); 274 } 275 // add rule only if none was removed 276 if (!removed) { 277 securityData.addModifiablePrivilege(principalName, denyPerm, grant); 278 } 279 } 280 reconstructTableModel(); 281 return null; 282 } 283 284 @Override 285 public String addPermission() { 286 String permissionName = permissionListManager.getSelectedPermission(); 287 boolean grant = permissionActionListManager.getSelectedGrant().equals("Grant"); 288 return addPermission(selectedEntry, permissionName, grant); 289 } 290 291 @Override 292 public String addPermissions() { 293 if (selectedEntries == null || selectedEntries.isEmpty()) { 294 String message = ComponentUtils.translate(FacesContext.getCurrentInstance(), 295 "error.rightsManager.noUsersSelected"); 296 FacesMessages.instance().add(message); 297 return null; 298 } 299 String permissionName = permissionListManager.getSelectedPermission(); 300 boolean grant = permissionActionListManager.getSelectedGrant().equals("Grant"); 301 302 for (String principalName : selectedEntries) { 303 addPermission(principalName, permissionName, grant); 304 } 305 return null; 306 } 307 308 @Override 309 public String addPermissionAndUpdate() { 310 addPermission(); 311 updateSecurityOnDocument(); 312 return null; 313 } 314 315 @Override 316 public String addPermissionsAndUpdate() { 317 addPermissions(); 318 updateSecurityOnDocument(); 319 selectedEntries = null; 320 facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); 321 return null; 322 } 323 324 @Override 325 public String saveSecurityUpdates() { 326 updateSecurityOnDocument(); 327 selectedEntries = null; 328 facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); 329 return null; 330 } 331 332 @Override 333 public String removePermission() { 334 securityData.removeModifiablePrivilege(selectedEntry, permissionListManager.getSelectedPermission(), 335 permissionActionListManager.getSelectedGrant().equals("Grant")); 336 reconstructTableModel(); 337 return null; 338 } 339 340 @Override 341 public String removePermissionAndUpdate() { 342 removePermission(); 343 344 if (!checkPermissions()) { 345 facesMessages.add(StatusMessage.Severity.ERROR, 346 resourcesAccessor.getMessages().get("message.updated.rights")); 347 return null; 348 } 349 350 updateSecurityOnDocument(); 351 // do not redirect to the default folder view 352 return null; 353 } 354 355 @Override 356 public String removePermissions() { 357 for (PageSelection<String> user : getSelectedRows()) { 358 securityData.removeModifiablePrivilege(user.getData()); 359 if (!checkPermissions()) { 360 facesMessages.add(StatusMessage.Severity.ERROR, 361 resourcesAccessor.getMessages().get("message.error.removeRight")); 362 return null; 363 } 364 } 365 reconstructTableModel(); 366 return null; 367 } 368 369 @Override 370 public String removePermissionsAndUpdate() { 371 for (PageSelection<String> user : getDataTableModel().getEntries()) { 372 securityData.removeModifiablePrivilege(user.getData()); 373 if (!checkPermissions()) { 374 facesMessages.add(StatusMessage.Severity.ERROR, 375 resourcesAccessor.getMessages().get("message.error.removeRight")); 376 return null; 377 } 378 } 379 updateSecurityOnDocument(); 380 facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get("message.updated.rights")); 381 // do not redirect to the default folder view 382 return null; 383 } 384 385 @Override 386 public boolean getCanAddSecurityRules() { 387 return documentManager.hasPermission(currentDocument.getRef(), "WriteSecurity"); 388 } 389 390 @Override 391 public boolean getCanRemoveSecurityRules() { 392 return documentManager.hasPermission(currentDocument.getRef(), "WriteSecurity") && !getSelectedRows().isEmpty(); 393 } 394 395 /** 396 * @return The list of selected rows in the local rights table. 397 * @since 6.0 398 */ 399 private List<PageSelection<String>> getSelectedRows() { 400 List<PageSelection<String>> selectedRows = new ArrayList<PageSelection<String>>(); 401 402 if (!getDataTableModel().isEmpty()) { 403 for (PageSelection<String> entry : getDataTableModel().getEntries()) { 404 if (entry.isSelected()) { 405 selectedRows.add(entry); 406 } 407 } 408 } 409 return selectedRows; 410 } 411 412 public List<UserVisiblePermission> getVisibleUserPermissions(String documentType) { 413 return Framework.getService(PermissionProvider.class).getUserVisiblePermissionDescriptors(documentType); 414 } 415 416 @Override 417 public List<SelectItem> getSettablePermissions() { 418 String documentType = navigationContext.getCurrentDocument().getType(); 419 420 // BBB: use the platform service if it defines permissions (deprecated) 421 UIPermissionService service = (UIPermissionService) Framework.getRuntime().getComponent( 422 UIPermissionService.NAME); 423 String[] settablePermissions = service.getUIPermissions(documentType); 424 425 if (settablePermissions == null || settablePermissions.length == 0) { 426 // new centralized permission provider at the core level 427 428 List<UserVisiblePermission> visiblePerms = getVisibleUserPermissions(documentType); 429 settablePermissions = new String[visiblePerms.size()]; 430 int idx = 0; 431 for (UserVisiblePermission uvp : visiblePerms) { 432 settablePermissions[idx] = uvp.getId(); 433 idx++; 434 } 435 } 436 437 return asSelectItems(settablePermissions); 438 } 439 440 protected List<SelectItem> asSelectItems(String... permissions) { 441 List<SelectItem> items = new ArrayList<SelectItem>(); 442 for (String perm : permissions) { 443 String label = labeler.makeLabel(perm); 444 SelectItem it = new SelectItem(perm, resourcesAccessor.getMessages().get(label)); 445 items.add(it); 446 } 447 return items; 448 } 449 450 /** 451 * @since 7.4 452 */ 453 public List<SelectItem> getUserVisiblePermissionSelectItems(String documentType) { 454 List<UserVisiblePermission> userVisiblePermissions = getVisibleUserPermissions(documentType); 455 List<String> permissions = new ArrayList<>(); 456 for (UserVisiblePermission userVisiblePermission : userVisiblePermissions) { 457 permissions.add(userVisiblePermission.getId()); 458 } 459 return asSelectItems(permissions.toArray(new String[permissions.size()])); 460 } 461 462 @Override 463 public Map<String, String> getIconAltMap() { 464 return principalListManager.iconAlt; 465 } 466 467 @Override 468 public Map<String, String> getIconPathMap() { 469 return principalListManager.iconPath; 470 } 471 472 @Override 473 public Boolean getBlockRightInheritance() { 474 return blockRightInheritance; 475 } 476 477 @Override 478 public void setBlockRightInheritance(Boolean blockRightInheritance) { 479 this.blockRightInheritance = blockRightInheritance; 480 } 481 482 public String blockRightInheritance() { 483 Boolean needBlockRightInheritance = blockRightInheritance; 484 485 if (needBlockRightInheritance) { 486 // Block 487 securityData.addModifiablePrivilege(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false); 488 // add user to avoid lock up 489 Principal currentUser = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal(); 490 if (securityData.getCurrentDocumentUsers() != null 491 && !securityData.getCurrentDocumentUsers().contains(currentUser.getName())) { 492 securityData.addModifiablePrivilege(currentUser.getName(), SecurityConstants.EVERYTHING, true); 493 // add administrators to avoid LockUp 494 List<String> adminGroups = userManager.getAdministratorsGroups(); 495 for (String adminGroup : adminGroups) { 496 securityData.addModifiablePrivilege(adminGroup, SecurityConstants.EVERYTHING, true); 497 } 498 } 499 } else { 500 securityData.removeModifiablePrivilege(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false); 501 } 502 updateSecurityOnDocument(); 503 selectedEntries = null; 504 return null; 505 } 506 507 @Override 508 public Boolean displayInheritedPermissions() { 509 return getDisplayInheritedPermissions(); 510 } 511 512 @Override 513 public boolean getDisplayInheritedPermissions() { 514 if (blockRightInheritance == null) { 515 rebuildSecurityData(); 516 } 517 if (blockRightInheritance) { 518 return false; 519 } 520 return !securityData.getParentDocumentsUsers().isEmpty(); 521 } 522 523 @Override 524 public List<String> getCurrentDocumentUsers() { 525 List<String> currentUsers = securityData.getCurrentDocumentUsers(); 526 return validateUserGroupList(currentUsers); 527 } 528 529 @Override 530 public List<String> getParentDocumentsUsers() { 531 List<String> parentUsers = securityData.getParentDocumentsUsers(); 532 return validateUserGroupList(parentUsers); 533 } 534 535 /** 536 * Validates user/group against userManager in order to remove obsolete entries (ie: deleted groups/users). 537 */ 538 private List<String> validateUserGroupList(List<String> usersGroups2Validate) { 539 // TODO : 540 // 1 -should add a clean cache system to avoid 541 // calling the directory : this can be problematic for big ldaps 542 // 2 - this filtering should at some point be applied to acp and saved 543 // back in a batch? 544 545 List<String> returnList = new ArrayList<String>(); 546 for (String entry : usersGroups2Validate) { 547 if (entry.equals(SecurityConstants.EVERYONE)) { 548 returnList.add(entry); 549 continue; 550 } 551 if (isUserGroupInCache(entry)) { 552 returnList.add(entry); 553 continue; 554 } 555 if (isUserGroupInDeletedCache(entry)) { 556 continue; 557 } 558 559 if (userManager.getPrincipal(entry) != null) { 560 returnList.add(entry); 561 addUserGroupInCache(entry); 562 continue; 563 } else if (userManager.getGroup(entry) != null) { 564 returnList.add(entry); 565 addUserGroupInCache(entry); 566 continue; 567 } else { 568 addUserGroupInDeletedCache(entry); 569 } 570 } 571 return returnList; 572 } 573 574 private Boolean isUserGroupInCache(String entry) { 575 if (cachedValidatedUserAndGroups == null) { 576 return false; 577 } 578 return cachedValidatedUserAndGroups.contains(entry); 579 } 580 581 private void addUserGroupInCache(String entry) { 582 if (cachedValidatedUserAndGroups == null) { 583 cachedValidatedUserAndGroups = new ArrayList<String>(); 584 } 585 cachedValidatedUserAndGroups.add(entry); 586 } 587 588 private Boolean isUserGroupInDeletedCache(String entry) { 589 if (cachedDeletedUserAndGroups == null) { 590 return false; 591 } 592 return cachedDeletedUserAndGroups.contains(entry); 593 } 594 595 private void addUserGroupInDeletedCache(String entry) { 596 if (cachedDeletedUserAndGroups == null) { 597 cachedDeletedUserAndGroups = new ArrayList<String>(); 598 } 599 600 cachedDeletedUserAndGroups.add(entry); 601 } 602 603 /** 604 * Checks if the current user can still read and write access rights. If he can't, then the security data are 605 * rebuilt. 606 */ 607 private boolean checkPermissions() { 608 if (currentUser.isAdministrator()) { 609 return true; 610 } else { 611 List<String> principals = new ArrayList<String>(); 612 principals.add(currentUser.getName()); 613 principals.addAll(currentUser.getAllGroups()); 614 615 ACP acp = currentDocument.getACP(); 616 new SecurityDataConverter(); 617 List<UserEntry> modifiableEntries = SecurityDataConverter.convertToUserEntries(securityData); 618 619 if (null == acp) { 620 acp = new ACPImpl(); 621 } 622 acp.setRules(modifiableEntries.toArray(new UserEntry[0])); 623 624 final boolean access = acp.getAccess(principals.toArray(new String[0]), getPermissionsToCheck()) 625 .toBoolean(); 626 if (!access) { 627 rebuildSecurityData(); 628 } 629 return access; 630 } 631 } 632 633 protected String[] getPermissionsToCheck() { 634 if (CACHED_PERMISSION_TO_CHECK == null) { 635 PermissionProvider pprovider = Framework.getService(PermissionProvider.class); 636 List<String> aggregatedPerms = new LinkedList<String>(); 637 for (String seedPerm : SEED_PERMISSIONS_TO_CHECK) { 638 aggregatedPerms.add(seedPerm); 639 String[] compoundPerms = pprovider.getPermissionGroups(seedPerm); 640 if (compoundPerms != null) { 641 aggregatedPerms.addAll(Arrays.asList(compoundPerms)); 642 } 643 } 644 CACHED_PERMISSION_TO_CHECK = aggregatedPerms.toArray(new String[aggregatedPerms.size()]); 645 } 646 return CACHED_PERMISSION_TO_CHECK; 647 } 648 649 @Override 650 public String getSelectedEntry() { 651 return selectedEntry; 652 } 653 654 @Override 655 public void setSelectedEntry(String selectedEntry) { 656 this.selectedEntry = selectedEntry; 657 } 658 659 @Override 660 public List<String> getSelectedEntries() { 661 return selectedEntries; 662 } 663 664 @Override 665 public void setSelectedEntries(List<String> selectedEntries) { 666 this.selectedEntries = selectedEntries; 667 } 668 669 @Factory(value = "isMSIEorEdge", scope = ScopeType.SESSION) 670 public boolean isMSIEorEdge() { 671 FacesContext context = FacesContext.getCurrentInstance(); 672 if (context != null) { 673 HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); 674 String ua = request.getHeader("User-Agent"); 675 return UserAgentMatcher.isMSIE6or7(ua) || UserAgentMatcher.isMSIE10OrMore(ua) 676 || UserAgentMatcher.isMSEdge(ua); 677 } else { 678 return false; 679 } 680 } 681 682 public String getLabel(String permission) { 683 return StringUtils.isNotBlank(permission) ? labeler.makeLabel(permission) : permission; 684 } 685 686}