/* * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nelson Silva * André Justo */ package org.nuxeo.ecm.platform.oauth2.tokens; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.directory.DirectoryException; import org.nuxeo.ecm.directory.Session; import org.nuxeo.ecm.directory.api.DirectoryService; import org.nuxeo.runtime.api.Framework; import com.google.api.client.auth.oauth2.StoredCredential; import com.google.api.client.util.store.DataStore; import com.google.api.client.util.store.DataStoreFactory; /** * {@link DataStore} backed by a Nuxeo Directory * * @since 7.3 */ public class OAuth2TokenStore implements DataStore<StoredCredential> { protected static final Log log = LogFactory.getLog(OAuth2TokenStore.class); public static final String DIRECTORY_NAME = "oauth2Tokens"; public static final String ENTRY_ID = "id"; private String serviceName; public OAuth2TokenStore(String serviceName) { this.serviceName = serviceName; } @Override public DataStore<StoredCredential> set(String key, StoredCredential credential) throws IOException { Map<String, Serializable> filter = new HashMap<>(); filter.put(ENTRY_ID, key); DocumentModel entry = find(filter); if (entry == null) { store(key, new NuxeoOAuth2Token(credential)); } else { refresh(entry, new NuxeoOAuth2Token(credential)); } return this; } @Override public DataStore<StoredCredential> delete(String key) throws IOException { DirectoryService ds = Framework.getLocalService(DirectoryService.class); try (Session session = ds.open(DIRECTORY_NAME)) { Map<String, Serializable> filter = new HashMap<>(); filter.put("serviceName", serviceName); filter.put(ENTRY_ID, key); DocumentModelList entries = session.query(filter); for (DocumentModel entry : entries) { session.deleteEntry(entry); } } return this; } @Override public StoredCredential get(String key) throws IOException { Map<String, Serializable> filter = new HashMap<>(); filter.put(ENTRY_ID, key); DocumentModel entry = find(filter); return entry != null ? NuxeoOAuth2Token.asCredential(entry) : null; } @Override public DataStoreFactory getDataStoreFactory() { return null; } public final String getId() { return this.serviceName; } @Override public boolean containsKey(String key) throws IOException { return this.get(key) != null; } @Override public boolean containsValue(StoredCredential value) throws IOException { return this.values().contains(value); } @Override public boolean isEmpty() throws IOException { return this.size() == 0; } @Override public int size() throws IOException { return this.keySet().size(); } @Override public Set<String> keySet() throws IOException { Set<String> keys = new HashSet<>(); DocumentModelList entries = query(); for (DocumentModel entry : entries) { keys.add((String) entry.getProperty(NuxeoOAuth2Token.SCHEMA, ENTRY_ID)); } return keys; } @Override public Collection<StoredCredential> values() throws IOException { List<StoredCredential> results = new ArrayList<>(); DocumentModelList entries = query(); for (DocumentModel entry : entries) { results.add(NuxeoOAuth2Token.asCredential(entry)); } return results; } @Override public DataStore<StoredCredential> clear() throws IOException { return null; } /* * Methods used by Nuxeo when acting as OAuth2 provider */ public void store(String userId, NuxeoOAuth2Token token) { token.setServiceName(serviceName); token.setNuxeoLogin(userId); try { storeTokenAsDirectoryEntry(token); } catch (DirectoryException e) { log.error("Error during token storage", e); } } public NuxeoOAuth2Token refresh(String refreshToken, String clientId) { Map<String, Serializable> filter = new HashMap<>(); filter.put("clientId", clientId); filter.put("refreshToken", refreshToken); filter.put("serviceName", serviceName); DocumentModel entry = find(filter); if (entry != null) { NuxeoOAuth2Token token = getTokenFromDirectoryEntry(entry); delete(token.getAccessToken(), clientId); token.refresh(); return storeTokenAsDirectoryEntry(token); } return null; } public NuxeoOAuth2Token refresh(DocumentModel entry, NuxeoOAuth2Token token) { DirectoryService ds = Framework.getLocalService(DirectoryService.class); return Framework.doPrivileged(() -> { try (Session session = ds.open(DIRECTORY_NAME)) { entry.setProperty("oauth2Token", "accessToken", token.getAccessToken()); entry.setProperty("oauth2Token", "refreshToken", token.getRefreshToken()); entry.setProperty("oauth2Token", "creationDate", token.getCreationDate()); entry.setProperty("oauth2Token", "expirationTimeMilliseconds", token.getExpirationTimeMilliseconds()); session.updateEntry(entry); return getTokenFromDirectoryEntry(entry); } }); } public void delete(String token, String clientId) { DirectoryService ds = Framework.getLocalService(DirectoryService.class); Framework.doPrivileged(() -> { try (Session session = ds.open(DIRECTORY_NAME)) { Map<String, Serializable> filter = new HashMap<String, Serializable>(); filter.put("serviceName", serviceName); filter.put("clientId", clientId); filter.put("accessToken", token); DocumentModelList entries = session.query(filter); for (DocumentModel entry : entries) { session.deleteEntry(entry); } } }); } /** * Retrieve an entry by it's accessToken */ public NuxeoOAuth2Token getToken(String token) { Map<String, Serializable> filter = new HashMap<>(); filter.put("accessToken", token); DocumentModelList entries = query(filter); if (entries.size() == 0) { return null; } if (entries.size() > 1) { log.error("Found several tokens"); } return getTokenFromDirectoryEntry(entries.get(0)); } public DocumentModelList query() { return query(new HashMap<>()); } public DocumentModelList query(Map<String, Serializable> filter) { DirectoryService ds = Framework.getLocalService(DirectoryService.class); return Framework.doPrivileged(() -> { try (Session session = ds.open(DIRECTORY_NAME)) { filter.put("serviceName", serviceName); return session.query(filter); } }); } protected NuxeoOAuth2Token getTokenFromDirectoryEntry(DocumentModel entry) { return new NuxeoOAuth2Token(entry); } protected NuxeoOAuth2Token storeTokenAsDirectoryEntry(NuxeoOAuth2Token aToken) { DirectoryService ds = Framework.getLocalService(DirectoryService.class); return Framework.doPrivileged(() -> { try (Session session = ds.open(DIRECTORY_NAME)) { DocumentModel entry = session.createEntry(aToken.toMap()); session.updateEntry(entry); return getTokenFromDirectoryEntry(entry); } }); } protected DocumentModel find(Map<String, Serializable> filter) { DocumentModelList entries = query(filter); if (entries.size() == 0) { return null; } if (entries.size() > 1) { log.error("Found several tokens"); } return entries.get(0); } }