001/* 002 * (C) Copyright 2006-20012 Nuxeo SA (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 * Antoine Taillefer 016 */ 017package org.nuxeo.ecm.tokenauth.service; 018 019import java.io.Serializable; 020import java.util.Calendar; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.UUID; 025 026import javax.security.auth.login.LoginContext; 027import javax.security.auth.login.LoginException; 028 029import org.apache.commons.lang.StringUtils; 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.ecm.core.api.DocumentModel; 033import org.nuxeo.ecm.core.api.DocumentModelList; 034import org.nuxeo.ecm.core.api.NuxeoException; 035import org.nuxeo.ecm.directory.BaseSession; 036import org.nuxeo.ecm.directory.Session; 037import org.nuxeo.ecm.directory.api.DirectoryService; 038import org.nuxeo.ecm.tokenauth.TokenAuthenticationException; 039import org.nuxeo.runtime.api.Framework; 040 041/** 042 * Default implementation of the {@link TokenAuthenticationService}. 043 * <p> 044 * The token is generated by the {@link UUID#randomUUID()} method which guarantees its uniqueness. The storage back-end 045 * is a SQL Directory. 046 * 047 * @author Antoine Taillefer (ataillefer@nuxeo.com) 048 * @since 5.7 049 */ 050public class TokenAuthenticationServiceImpl implements TokenAuthenticationService { 051 052 private static final long serialVersionUID = 35041039370298705L; 053 054 private static final Log log = LogFactory.getLog(TokenAuthenticationServiceImpl.class); 055 056 protected static final String DIRECTORY_NAME = "authTokens"; 057 058 protected static final String DIRECTORY_SCHEMA = "authtoken"; 059 060 protected static final String USERNAME_FIELD = "userName"; 061 062 protected static final String TOKEN_FIELD = "token"; 063 064 protected static final String APPLICATION_NAME_FIELD = "applicationName"; 065 066 protected static final String DEVICE_ID_FIELD = "deviceId"; 067 068 protected static final String DEVICE_DESCRIPTION_FIELD = "deviceDescription"; 069 070 protected static final String PERMISSION_FIELD = "permission"; 071 072 protected static final String CREATION_DATE_FIELD = "creationDate"; 073 074 @Override 075 public String acquireToken(String userName, String applicationName, String deviceId, String deviceDescription, 076 String permission) throws TokenAuthenticationException { 077 078 // Look for a token bound to the (userName, 079 // applicationName, deviceId) triplet, if it exists return it, 080 // else generate a unique one 081 String token = getToken(userName, applicationName, deviceId); 082 if (token != null) { 083 return token; 084 } 085 086 // Check required parameters (userName, applicationName and deviceId are 087 // already checked in #getToken) 088 if (StringUtils.isEmpty(permission)) { 089 throw new TokenAuthenticationException( 090 "The permission parameter is mandatory to acquire an authentication token."); 091 } 092 093 // Log in as system user 094 LoginContext lc; 095 try { 096 lc = Framework.login(); 097 } catch (LoginException e) { 098 throw new NuxeoException("Cannot log in as system user", e); 099 } 100 try { 101 // Open directory session 102 try (Session session = Framework.getService(DirectoryService.class).open(DIRECTORY_NAME)) { 103 // Generate random token, store the binding and return the token 104 UUID uuid = UUID.randomUUID(); 105 token = uuid.toString(); 106 107 final DocumentModel entry = getBareAuthTokenModel(Framework.getService(DirectoryService.class)); 108 entry.setProperty(DIRECTORY_SCHEMA, TOKEN_FIELD, token); 109 entry.setProperty(DIRECTORY_SCHEMA, USERNAME_FIELD, userName); 110 entry.setProperty(DIRECTORY_SCHEMA, APPLICATION_NAME_FIELD, applicationName); 111 entry.setProperty(DIRECTORY_SCHEMA, DEVICE_ID_FIELD, deviceId); 112 if (!StringUtils.isEmpty(deviceDescription)) { 113 entry.setProperty(DIRECTORY_SCHEMA, DEVICE_DESCRIPTION_FIELD, deviceDescription); 114 } 115 entry.setProperty(DIRECTORY_SCHEMA, PERMISSION_FIELD, permission); 116 Calendar creationDate = Calendar.getInstance(); 117 creationDate.setTimeInMillis(System.currentTimeMillis()); 118 entry.setProperty(DIRECTORY_SCHEMA, CREATION_DATE_FIELD, creationDate); 119 session.createEntry(entry); 120 121 log.debug(String.format( 122 "Generated unique token for the (userName, applicationName, deviceId) triplet: ('%s', '%s', '%s'), returning it.", 123 userName, applicationName, deviceId)); 124 return token; 125 126 } 127 } finally { 128 try { 129 // Login context may be null in tests 130 if (lc != null) { 131 lc.logout(); 132 } 133 } catch (LoginException e) { 134 throw new NuxeoException("Cannot log out system user", e); 135 } 136 } 137 } 138 139 @Override 140 public String getToken(String userName, String applicationName, String deviceId) 141 throws TokenAuthenticationException { 142 143 if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(applicationName) || StringUtils.isEmpty(deviceId)) { 144 throw new TokenAuthenticationException( 145 "The following parameters are mandatory to get an authentication token: userName, applicationName, deviceId."); 146 } 147 148 // Log in as system user 149 LoginContext lc; 150 try { 151 lc = Framework.login(); 152 } catch (LoginException e) { 153 throw new NuxeoException("Cannot log in as system user", e); 154 } 155 try { 156 // Open directory session 157 try (Session session = Framework.getService(DirectoryService.class).open(DIRECTORY_NAME)) { 158 // Look for a token bound to the (userName, 159 // applicationName, deviceId) triplet, if it exists return it, 160 // else return null 161 final Map<String, Serializable> filter = new HashMap<String, Serializable>(); 162 filter.put(USERNAME_FIELD, userName); 163 filter.put(APPLICATION_NAME_FIELD, applicationName); 164 filter.put(DEVICE_ID_FIELD, deviceId); 165 DocumentModelList tokens = session.query(filter); 166 if (!tokens.isEmpty()) { 167 // Multiple tokens found for the same triplet, this is 168 // inconsistent 169 if (tokens.size() > 1) { 170 throw new NuxeoException(String.format( 171 "Found multiple tokens for the (userName, applicationName, deviceId) triplet: ('%s', '%s', '%s'), this is inconsistent.", 172 userName, applicationName, deviceId)); 173 } 174 // Return token 175 log.debug(String.format( 176 "Found token for the (userName, applicationName, deviceId) triplet: ('%s', '%s', '%s'), returning it.", 177 userName, applicationName, deviceId)); 178 DocumentModel tokenModel = tokens.get(0); 179 return tokenModel.getId(); 180 } 181 182 log.debug(String.format( 183 "No token found for the (userName, applicationName, deviceId) triplet: ('%s', '%s', '%s'), returning null.", 184 userName, applicationName, deviceId)); 185 return null; 186 } 187 } finally { 188 try { 189 // Login context may be null in tests 190 if (lc != null) { 191 lc.logout(); 192 } 193 } catch (LoginException e) { 194 throw new NuxeoException("Cannot log out system user", e); 195 } 196 } 197 } 198 199 @Override 200 public String getUserName(final String token) { 201 202 // Log in as system user 203 LoginContext lc; 204 try { 205 lc = Framework.login(); 206 } catch (LoginException e) { 207 throw new NuxeoException("Cannot log in as system user", e); 208 } 209 try { 210 try (Session session = Framework.getService(DirectoryService.class).open(DIRECTORY_NAME)) { 211 DocumentModel entry = session.getEntry(token); 212 if (entry == null) { 213 log.debug(String.format("Found no user name bound to the token: '%s', returning null.", token)); 214 return null; 215 } 216 log.debug(String.format("Found a user name bound to the token: '%s', returning it.", token)); 217 return (String) entry.getProperty(DIRECTORY_SCHEMA, USERNAME_FIELD); 218 219 } 220 } finally { 221 try { 222 // Login context may be null in tests 223 if (lc != null) { 224 lc.logout(); 225 } 226 } catch (LoginException e) { 227 throw new NuxeoException("Cannot log out system user", e); 228 } 229 } 230 } 231 232 @Override 233 public void revokeToken(final String token) { 234 235 // Log in as system user 236 LoginContext lc; 237 try { 238 lc = Framework.login(); 239 } catch (LoginException e) { 240 throw new NuxeoException("Cannot log in as system user", e); 241 } 242 try { 243 try (Session session = Framework.getService(DirectoryService.class).open(DIRECTORY_NAME)) { 244 session.deleteEntry(token); 245 log.info(String.format("Deleted token: '%s' from the back-end.", token)); 246 } 247 } finally { 248 try { 249 // Login context may be null in tests 250 if (lc != null) { 251 lc.logout(); 252 } 253 } catch (LoginException e) { 254 throw new NuxeoException("Cannot log out system user", e); 255 } 256 } 257 } 258 259 @Override 260 public DocumentModelList getTokenBindings(String userName) { 261 262 // Log in as system user 263 LoginContext lc; 264 try { 265 lc = Framework.login(); 266 } catch (LoginException e) { 267 throw new NuxeoException("Cannot log in as system user", e); 268 } 269 try { 270 try (Session session = Framework.getService(DirectoryService.class).open(DIRECTORY_NAME)) { 271 final Map<String, Serializable> filter = new HashMap<String, Serializable>(); 272 filter.put(USERNAME_FIELD, userName); 273 final Map<String, String> orderBy = new HashMap<String, String>(); 274 orderBy.put(CREATION_DATE_FIELD, "desc"); 275 return session.query(filter, Collections.<String> emptySet(), orderBy); 276 } 277 } finally { 278 try { 279 // Login context may be null in tests 280 if (lc != null) { 281 lc.logout(); 282 } 283 } catch (LoginException e) { 284 throw new NuxeoException("Cannot log out system user", e); 285 } 286 } 287 } 288 289 protected DocumentModel getBareAuthTokenModel(DirectoryService directoryService) { 290 291 String directorySchema = directoryService.getDirectorySchema(DIRECTORY_NAME); 292 return BaseSession.createEntryModel(null, directorySchema, null, null); 293 } 294 295}