001/* 002 * (C) Copyright 2015 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 * Thomas Roger 018 */ 019 020package org.nuxeo.ecm.permissions; 021 022import org.nuxeo.ecm.core.api.CoreInstance; 023import org.nuxeo.ecm.core.api.DocumentModel; 024import org.nuxeo.ecm.core.api.NuxeoPrincipal; 025import org.nuxeo.ecm.core.api.security.ACE; 026import org.nuxeo.ecm.core.api.security.ACL; 027import org.nuxeo.ecm.core.api.security.ACP; 028import org.nuxeo.ecm.core.query.sql.NXQL; 029import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService; 030import org.nuxeo.runtime.api.Framework; 031import org.nuxeo.runtime.services.config.ConfigurationService; 032 033/** 034 * @since 8.1 035 */ 036public class TransientUserPermissionHelper { 037 038 /** 039 * @since 10.3 040 */ 041 // status = 0 is PENDING, status = 1 or status = NULL is EFFECTIVE, excludes ARCHIVED ACLs 042 public static final String OTHER_DOCUMENT_WITH_PENDING_OR_EFFECTIVE_ACL_QUERY = "SELECT ecm:uuid FROM Document, Relation" 043 + " WHERE (ecm:acl/*1/status is NULL OR ecm:acl/*1/status = 0 OR ecm:acl/*1/status = 1)" 044 + " AND ecm:acl/*1/principal = %s AND ecm:uuid <> %s"; 045 046 /** 047 * @since 10.3 048 */ 049 public static final String TRANSIENT_APP_NAME = "transient/appName"; 050 051 /** 052 * @since 10.3 053 */ 054 public static final String TRANSIENT_DEVICE_ID = "transient/deviceId"; 055 056 /** 057 * @since 10.3 058 */ 059 public static final String TRANSIENT_PERMISSION = "transient/permission"; 060 061 private TransientUserPermissionHelper() { 062 // helper class 063 } 064 065 /** 066 * @deprecated since 10.3. Use {@link #addToken(String)} instead. 067 */ 068 @Deprecated 069 public static String acquireToken(String username, DocumentModel doc, String permission) { 070 addToken(username); 071 // return value was never used anyway 072 return null; 073 } 074 075 /** 076 * Adds a token for the given {@code username}. 077 * <p> 078 * Does nothing if {@code username} is not a transient username or if a token already exists. 079 * 080 * @since 10.3 081 */ 082 public static void addToken(String username) { 083 if (NuxeoPrincipal.isTransientUsername(username)) { 084 TokenAuthenticationService tokenAuthenticationService = Framework.getService( 085 TokenAuthenticationService.class); 086 tokenAuthenticationService.acquireToken(username, TRANSIENT_APP_NAME, TRANSIENT_DEVICE_ID, null, 087 TRANSIENT_PERMISSION); 088 } 089 } 090 091 /** 092 * Returns the token linked to the given transient {@code username}, or {@code null} if no token can be found. 093 * 094 * @since 10.3 095 */ 096 public static String getToken(String username) { 097 return Framework.getService(TokenAuthenticationService.class) 098 .getToken(username, TRANSIENT_APP_NAME, TRANSIENT_DEVICE_ID); 099 } 100 101 public static void revokeToken(String username, DocumentModel doc) { 102 if (NuxeoPrincipal.isTransientUsername(username)) { 103 if (hasOtherPermission(username, doc)) { 104 // do not remove the token as username has a permission on another document 105 return; 106 } 107 108 // check if the transient user has other ACE on the document 109 ACP acp = doc.getACP(); 110 for (ACL acl : acp.getACLs()) { 111 if (ACL.INHERITED_ACL.equals(acl.getName())) { 112 continue; 113 } 114 115 for (ACE ace : acl) { 116 if (username.equals(ace.getUsername()) && !ace.isArchived()) { 117 // skip token removal 118 return; 119 } 120 } 121 } 122 123 TokenAuthenticationService tokenAuthenticationService = Framework.getService( 124 TokenAuthenticationService.class); 125 String token = tokenAuthenticationService.getToken(username, TRANSIENT_APP_NAME, TRANSIENT_DEVICE_ID); 126 if (token != null) { 127 tokenAuthenticationService.revokeToken(token); 128 } 129 130 // for compatibility, remove also token that may be stored based on the document 131 token = tokenAuthenticationService.getToken(username, doc.getRepositoryName(), doc.getId()); 132 if (token != null) { 133 tokenAuthenticationService.revokeToken(token); 134 } 135 } 136 } 137 138 /** 139 * Returns {@code true} if the given {@code username} has a non-archived ACE on another document than {@code doc}, 140 * {@code false} otherwise. 141 * <p> 142 * Always returns {@code false} if the configuration property {@link NuxeoPrincipal#TRANSIENT_USERNAME_UNIQUE_PROP} 143 * is {@code true}. 144 * 145 * @since 10.3 146 */ 147 protected static boolean hasOtherPermission(String username, DocumentModel doc) { 148 if (Framework.getService(ConfigurationService.class) 149 .isBooleanTrue(NuxeoPrincipal.TRANSIENT_USERNAME_UNIQUE_PROP)) { 150 // as the transient username is unique, assume there is no other document with a permission 151 // for username. 152 return false; 153 } 154 155 String query = String.format(OTHER_DOCUMENT_WITH_PENDING_OR_EFFECTIVE_ACL_QUERY, NXQL.escapeString(username), 156 NXQL.escapeString(doc.getId())); 157 return CoreInstance.doPrivileged(doc.getRepositoryName(), 158 session -> !session.queryProjection(query, 1, 0).isEmpty()); 159 } 160}