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