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