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