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 try (Session session = ds.open(DIRECTORY_NAME)) { 188 entry.setProperty("oauth2Token", "accessToken", token.getAccessToken()); 189 entry.setProperty("oauth2Token", "refreshToken", token.getRefreshToken()); 190 entry.setProperty("oauth2Token", "creationDate", token.getCreationDate()); 191 entry.setProperty("oauth2Token", "expirationTimeMilliseconds", token.getExpirationTimeMilliseconds()); 192 session.updateEntry(entry); 193 return getTokenFromDirectoryEntry(entry); 194 } 195 } 196 197 public void delete(String token, String clientId) { 198 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 199 try (Session session = ds.open(DIRECTORY_NAME)) { 200 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 201 filter.put("serviceName", serviceName); 202 filter.put("clientId", clientId); 203 filter.put("accessToken", token); 204 205 DocumentModelList entries = session.query(filter); 206 for (DocumentModel entry : entries) { 207 session.deleteEntry(entry); 208 } 209 } 210 } 211 212 /** 213 * Retrieve an entry by it's accessToken 214 */ 215 public NuxeoOAuth2Token getToken(String token) { 216 Map<String, Serializable> filter = new HashMap<>(); 217 filter.put("accessToken", token); 218 219 DocumentModelList entries = query(filter); 220 if (entries.size() == 0) { 221 return null; 222 } 223 if (entries.size() > 1) { 224 log.error("Found several tokens"); 225 } 226 return getTokenFromDirectoryEntry(entries.get(0)); 227 } 228 229 public DocumentModelList query() { 230 return query(new HashMap<>()); 231 } 232 233 public DocumentModelList query(Map<String, Serializable> filter) { 234 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 235 try (Session session = ds.open(DIRECTORY_NAME)) { 236 filter.put("serviceName", serviceName); 237 return session.query(filter); 238 } 239 } 240 241 protected NuxeoOAuth2Token getTokenFromDirectoryEntry(DocumentModel entry) { 242 return new NuxeoOAuth2Token(entry); 243 } 244 245 protected NuxeoOAuth2Token storeTokenAsDirectoryEntry(NuxeoOAuth2Token aToken) { 246 DirectoryService ds = Framework.getLocalService(DirectoryService.class); 247 try (Session session = ds.open(DIRECTORY_NAME)) { 248 DocumentModel entry = session.createEntry(aToken.toMap()); 249 session.updateEntry(entry); 250 return getTokenFromDirectoryEntry(entry); 251 } 252 } 253 254 protected DocumentModel find(Map<String, Serializable> filter) { 255 DocumentModelList entries = query(filter); 256 if (entries.size() == 0) { 257 return null; 258 } 259 if (entries.size() > 1) { 260 log.error("Found several tokens"); 261 } 262 return entries.get(0); 263 } 264}