001/*
002 * (C) Copyright 2010 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 *     Nuxeo - initial API and implementation
018 */
019
020package org.nuxeo.ecm.platform.oauth.providers;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Random;
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.core.api.PropertyException;
037import org.nuxeo.ecm.directory.DirectoryException;
038import org.nuxeo.ecm.directory.Session;
039import org.nuxeo.ecm.directory.api.DirectoryService;
040import org.nuxeo.runtime.api.Framework;
041import org.nuxeo.runtime.model.DefaultComponent;
042
043/**
044 * Implementation of the {@link OAuthServiceProviderRegistry}. The main storage backend is a SQL Directory. Readonly
045 * providers (contributed directly at OpenSocialService level) are managed in memory.
046 *
047 * @author tiry
048 */
049public class OAuthServiceProviderRegistryImpl extends DefaultComponent implements OAuthServiceProviderRegistry {
050
051    protected static final Log log = LogFactory.getLog(OAuthServiceProviderRegistryImpl.class);
052
053    public static final String DIRECTORY_NAME = "oauthServiceProviders";
054
055    protected Map<String, NuxeoOAuthServiceProvider> inMemoryProviders = new HashMap<String, NuxeoOAuthServiceProvider>();
056
057    @Override
058    public NuxeoOAuthServiceProvider getProvider(String gadgetUri, String serviceName) {
059        try {
060            NuxeoOAuthServiceProvider provider = getEntry(gadgetUri, serviceName, null);
061            return provider;
062        } catch (DirectoryException e) {
063            log.error("Unable to read provider from Directory backend", e);
064            return null;
065        }
066    }
067
068    protected String getBareGadgetUri(String gadgetUri) {
069        if (gadgetUri == null) {
070            return null;
071        }
072        String pattern = "http(s)?://(localhost|127.0.0.1)";
073        return gadgetUri.replaceFirst(pattern, "");
074    }
075
076    protected String preProcessServiceName(String serviceName) {
077        if (serviceName != null && serviceName.trim().isEmpty()) {
078            return null;
079        }
080        return serviceName;
081    }
082
083    protected DocumentModel getBestEntry(DocumentModelList entries, String gadgetUri, String serviceName)
084            throws PropertyException {
085        if (entries.size() > 1) {
086            log.warn("Found several entries for gadgetUri=" + gadgetUri + " and serviceName=" + serviceName);
087        }
088        if (serviceName == null || serviceName.trim().isEmpty()) {
089            for (DocumentModel entry : entries) {
090                if (entry.getPropertyValue("serviceName") == null
091                        || ((String) entry.getPropertyValue("serviceName")).trim().isEmpty()) {
092                    return entry;
093                }
094            }
095            return null;
096        } else if (gadgetUri == null || gadgetUri.trim().isEmpty()) {
097            for (DocumentModel entry : entries) {
098                if (entry.getPropertyValue("gadgetUrl") == null
099                        || ((String) entry.getPropertyValue("gadgetUrl")).trim().isEmpty()) {
100                    return entry;
101                }
102            }
103            return null;
104        }
105
106        // XXX do better than that !
107        return entries.get(0);
108    }
109
110    protected NuxeoOAuthServiceProvider getEntry(String gadgetUri, String serviceName, Set<String> ftFilter)
111            {
112
113        String id = mkStringIdx(gadgetUri, serviceName);
114        if (inMemoryProviders.containsKey(id)) {
115            return inMemoryProviders.get(id);
116        }
117
118        // normalize "enmpty" service name
119        serviceName = preProcessServiceName(serviceName);
120
121        if (gadgetUri == null && serviceName == null) {
122            log.warn("Can not find provider with null gadgetUri and null serviceName !");
123            return null;
124        }
125
126        DirectoryService ds = Framework.getService(DirectoryService.class);
127        NuxeoOAuthServiceProvider provider = null;
128        try (Session session = ds.open(DIRECTORY_NAME)) {
129            Map<String, Serializable> filter = new HashMap<String, Serializable>();
130            if (gadgetUri != null) {
131                filter.put("gadgetUrl", gadgetUri);
132            }
133            if (serviceName != null) {
134                filter.put("serviceName", serviceName);
135            }
136            DocumentModelList entries = session.query(filter, ftFilter);
137            if (entries == null || entries.size() == 0) {
138                String bareGadgetUrl = getBareGadgetUri(gadgetUri);
139                if (bareGadgetUrl != null && !bareGadgetUrl.equals(gadgetUri)) {
140                    Set<String> urlfilter = new HashSet<String>();
141                    urlfilter.add("gadgetUrl");
142                    return getEntry(bareGadgetUrl, serviceName, urlfilter);
143                }
144                if (serviceName != null) {
145                    if (bareGadgetUrl != null) {
146                        provider = getEntry(bareGadgetUrl, null, ftFilter);
147                        if (provider != null) {
148                            return provider;
149                        }
150                    }
151                    if (gadgetUri != null) {
152                        return getEntry(null, serviceName, ftFilter);
153                    }
154                }
155                return null;
156            }
157            DocumentModel entry = getBestEntry(entries, gadgetUri, serviceName);
158            if (entry == null) {
159                return null;
160            }
161            provider = NuxeoOAuthServiceProvider.createFromDirectoryEntry(entry);
162            return provider;
163        }
164    }
165
166    protected String mkStringIdx(String gadgetUri, String serviceName) {
167        return "k-" + gadgetUri + "-" + serviceName;
168    }
169
170    @Override
171    public NuxeoOAuthServiceProvider addReadOnlyProvider(String gadgetUri, String serviceName, String consumerKey,
172            String consumerSecret, String publicKey) {
173        String id = mkStringIdx(gadgetUri, serviceName);
174        Long dummyId = new Random().nextLong();
175        NuxeoOAuthServiceProvider sp = new NuxeoOAuthServiceProvider(dummyId, gadgetUri, serviceName, consumerKey,
176                consumerSecret, publicKey);
177        inMemoryProviders.put(id, sp);
178        return sp;
179    }
180
181    @Override
182    public void deleteProvider(String gadgetUri, String serviceName) {
183
184        NuxeoOAuthServiceProvider provider = getProvider(gadgetUri, serviceName);
185        if (provider != null) {
186            deleteProvider(provider.id.toString());
187        }
188
189    }
190
191    @Override
192    public void deleteProvider(String providerId) {
193        try {
194            DirectoryService ds = Framework.getService(DirectoryService.class);
195            try (Session session = ds.open(DIRECTORY_NAME)) {
196                session.deleteEntry(providerId);
197            }
198        } catch (DirectoryException e) {
199            log.error("Unable to delete provider " + providerId, e);
200        }
201    }
202
203    @Override
204    public List<NuxeoOAuthServiceProvider> listProviders() {
205
206        List<NuxeoOAuthServiceProvider> result = new ArrayList<NuxeoOAuthServiceProvider>();
207        for (NuxeoOAuthServiceProvider provider : inMemoryProviders.values()) {
208            result.add(provider);
209        }
210        DirectoryService ds = Framework.getService(DirectoryService.class);
211        Framework.doPrivileged(() -> {
212            try (Session session = ds.open(DIRECTORY_NAME)) {
213                DocumentModelList entries = session.query(Collections.emptyMap());
214                for (DocumentModel entry : entries) {
215                    result.add(NuxeoOAuthServiceProvider.createFromDirectoryEntry(entry));
216                }
217            } catch (DirectoryException e) {
218                log.error("Error while fetching provider directory", e);
219            }
220        });
221        return result;
222    }
223}