001/* 002 * (C) Copyright 2006-2013 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 * Nelson Silva 016 * André Justo 017 */ 018package org.nuxeo.ecm.platform.oauth2.tokens; 019 020import java.io.IOException; 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 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.directory.DirectoryException; 035import org.nuxeo.ecm.directory.Session; 036import org.nuxeo.ecm.directory.api.DirectoryService; 037import org.nuxeo.runtime.api.Framework; 038 039import com.google.api.client.auth.oauth2.StoredCredential; 040import com.google.api.client.util.store.DataStore; 041import com.google.api.client.util.store.DataStoreFactory; 042 043/** 044 * {@link DataStore} backed by a Nuxeo Directory 045 * 046 * @since 7.3 047 */ 048public class OAuth2TokenStore implements DataStore<StoredCredential> { 049 050 protected static final Log log = LogFactory.getLog(OAuth2TokenStore.class); 051 052 public static final String DIRECTORY_NAME = "oauth2Tokens"; 053 054 public static final String ENTRY_ID = "id"; 055 056 private String serviceName; 057 058 public OAuth2TokenStore(String serviceName) { 059 this.serviceName = serviceName; 060 } 061 062 @Override 063 public DataStore<StoredCredential> set(String key, StoredCredential credential) throws IOException { 064 Map<String, Serializable> filter = new HashMap<>(); 065 filter.put(ENTRY_ID, key); 066 DocumentModel entry = find(filter); 067 068 if (entry == null) { 069 store(key, new NuxeoOAuth2Token(credential)); 070 } else { 071 refresh(entry, new NuxeoOAuth2Token(credential)); 072 } 073 return this; 074 } 075 076 @Override 077 public DataStore<StoredCredential> delete(String key) throws IOException { 078 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 079 try (Session session = ds.open(DIRECTORY_NAME)) { 080 Map<String, Serializable> filter = new HashMap<>(); 081 filter.put("serviceName", serviceName); 082 filter.put(ENTRY_ID, key); 083 084 DocumentModelList entries = session.query(filter); 085 for (DocumentModel entry : entries) { 086 session.deleteEntry(entry); 087 } 088 } 089 return this; 090 } 091 092 @Override 093 public StoredCredential get(String key) throws IOException { 094 Map<String, Serializable> filter = new HashMap<>(); 095 filter.put(ENTRY_ID, key); 096 DocumentModel entry = find(filter); 097 return entry != null ? NuxeoOAuth2Token.asCredential(entry) : null; 098 } 099 100 @Override 101 public DataStoreFactory getDataStoreFactory() { 102 return null; 103 } 104 105 public final String getId() { 106 return this.serviceName; 107 } 108 109 @Override 110 public boolean containsKey(String key) throws IOException { 111 return this.get(key) != null; 112 } 113 114 @Override 115 public boolean containsValue(StoredCredential value) throws IOException { 116 return this.values().contains(value); 117 } 118 119 @Override 120 public boolean isEmpty() throws IOException { 121 return this.size() == 0; 122 } 123 124 @Override 125 public int size() throws IOException { 126 return this.keySet().size(); 127 } 128 129 @Override 130 public Set<String> keySet() throws IOException { 131 Set<String> keys = new HashSet<>(); 132 DocumentModelList entries = query(); 133 for (DocumentModel entry : entries) { 134 keys.add((String) entry.getProperty(NuxeoOAuth2Token.SCHEMA, ENTRY_ID)); 135 } 136 return keys; 137 } 138 139 @Override 140 public Collection<StoredCredential> values() throws IOException { 141 List<StoredCredential> results = new ArrayList<>(); 142 DocumentModelList entries = query(); 143 for (DocumentModel entry : entries) { 144 results.add(NuxeoOAuth2Token.asCredential(entry)); 145 } 146 return results; 147 } 148 149 @Override 150 public DataStore<StoredCredential> clear() throws IOException { 151 return null; 152 } 153 154 /* 155 * Methods used by Nuxeo when acting as OAuth2 provider 156 */ 157 public void store(String userId, NuxeoOAuth2Token token) { 158 token.setServiceName(serviceName); 159 token.setNuxeoLogin(userId); 160 try { 161 storeTokenAsDirectoryEntry(token); 162 } catch (DirectoryException e) { 163 log.error("Error during token storage", e); 164 } 165 } 166 167 public NuxeoOAuth2Token refresh(String refreshToken, String clientId) { 168 Map<String, Serializable> filter = new HashMap<>(); 169 filter.put("clientId", clientId); 170 filter.put("refreshToken", refreshToken); 171 filter.put("serviceName", serviceName); 172 173 DocumentModel entry = find(filter); 174 if (entry != null) { 175 NuxeoOAuth2Token token = getTokenFromDirectoryEntry(entry); 176 delete(token.getAccessToken(), clientId); 177 token.refresh(); 178 return storeTokenAsDirectoryEntry(token); 179 } 180 return null; 181 } 182 183 public NuxeoOAuth2Token refresh(DocumentModel entry, NuxeoOAuth2Token token) { 184 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 185 try (Session session = ds.open(DIRECTORY_NAME)) { 186 entry.setProperty("oauth2Token", "accessToken", token.getAccessToken()); 187 entry.setProperty("oauth2Token", "refreshToken", token.getRefreshToken()); 188 entry.setProperty("oauth2Token", "creationDate", token.getCreationDate()); 189 entry.setProperty("oauth2Token", "expirationTimeMilliseconds", token.getExpirationTimeMilliseconds()); 190 session.updateEntry(entry); 191 return getTokenFromDirectoryEntry(entry); 192 } 193 } 194 195 public void delete(String token, String clientId) { 196 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 197 try (Session session = ds.open(DIRECTORY_NAME)) { 198 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 199 filter.put("serviceName", serviceName); 200 filter.put("clientId", clientId); 201 filter.put("accessToken", token); 202 203 DocumentModelList entries = session.query(filter); 204 for (DocumentModel entry : entries) { 205 session.deleteEntry(entry); 206 } 207 } 208 } 209 210 /** 211 * Retrieve an entry by it's accessToken 212 */ 213 public NuxeoOAuth2Token getToken(String token) { 214 Map<String, Serializable> filter = new HashMap<>(); 215 filter.put("accessToken", token); 216 217 DocumentModelList entries = query(filter); 218 if (entries.size() == 0) { 219 return null; 220 } 221 if (entries.size() > 1) { 222 log.error("Found several tokens"); 223 } 224 return getTokenFromDirectoryEntry(entries.get(0)); 225 } 226 227 public DocumentModelList query() { 228 return query(new HashMap<>()); 229 } 230 231 public DocumentModelList query(Map<String, Serializable> filter) { 232 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 233 try (Session session = ds.open(DIRECTORY_NAME)) { 234 filter.put("serviceName", serviceName); 235 return session.query(filter); 236 } 237 } 238 239 protected NuxeoOAuth2Token getTokenFromDirectoryEntry(DocumentModel entry) { 240 return new NuxeoOAuth2Token(entry); 241 } 242 243 protected NuxeoOAuth2Token storeTokenAsDirectoryEntry(NuxeoOAuth2Token aToken) { 244 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 245 try (Session session = ds.open(DIRECTORY_NAME)) { 246 DocumentModel entry = session.createEntry(aToken.toMap()); 247 session.updateEntry(entry); 248 return getTokenFromDirectoryEntry(entry); 249 } 250 } 251 252 protected DocumentModel find(Map<String, Serializable> filter) { 253 DocumentModelList entries = query(filter); 254 if (entries.size() == 0) { 255 return null; 256 } 257 if (entries.size() > 1) { 258 log.error("Found several tokens"); 259 } 260 return entries.get(0); 261 } 262}