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 */ 017package org.nuxeo.ecm.platform.oauth2.providers; 018 019import java.io.Serializable; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.ecm.core.api.DocumentModel; 031import org.nuxeo.ecm.directory.BaseSession; 032import org.nuxeo.ecm.directory.DirectoryException; 033import org.nuxeo.ecm.directory.Session; 034import org.nuxeo.ecm.directory.api.DirectoryService; 035import org.nuxeo.runtime.api.Framework; 036import org.nuxeo.runtime.model.ComponentContext; 037import org.nuxeo.runtime.model.ComponentInstance; 038import org.nuxeo.runtime.model.DefaultComponent; 039 040/** 041 * Implementation of the {@link OAuth2ServiceProviderRegistry}. The storage backend is a SQL Directory. 042 */ 043public class OAuth2ServiceProviderRegistryImpl extends DefaultComponent implements OAuth2ServiceProviderRegistry { 044 045 protected static final Log log = LogFactory.getLog(OAuth2ServiceProviderRegistryImpl.class); 046 047 public static final String PROVIDER_EP = "providers"; 048 049 public static final String DIRECTORY_NAME = "oauth2ServiceProviders"; 050 051 public static final String SCHEMA = "oauth2ServiceProvider"; 052 053 /** 054 * Registry of contributed providers. These providers can extend and/or override the default provider class. 055 */ 056 protected OAuth2ServiceProviderContributionRegistry registry = new OAuth2ServiceProviderContributionRegistry(); 057 058 @Override 059 public OAuth2ServiceProvider getProvider(String serviceName) { 060 try { 061 if (StringUtils.isBlank(serviceName)) { 062 log.warn("Can not find provider without a serviceName!"); 063 return null; 064 } 065 066 Map<String, Serializable> filter = new HashMap<>(); 067 filter.put("serviceName", serviceName); 068 069 List<DocumentModel> providers = queryProviders(filter, 1); 070 return providers.isEmpty() ? null : buildProvider(providers.get(0)); 071 } catch (DirectoryException e) { 072 log.error("Unable to read provider from Directory backend", e); 073 return null; 074 } 075 } 076 077 @Override 078 public OAuth2ServiceProvider addProvider(String serviceName, String description, String tokenServerURL, 079 String authorizationServerURL, String clientId, String clientSecret, List<String> scopes) { 080 081 DirectoryService ds = Framework.getService(DirectoryService.class); 082 try (Session session = ds.open(DIRECTORY_NAME)) { 083 DocumentModel creationEntry = BaseSession.createEntryModel(null, SCHEMA, null, null); 084 DocumentModel entry = session.createEntry(creationEntry); 085 entry.setProperty(SCHEMA, "serviceName", serviceName); 086 entry.setProperty(SCHEMA, "description", description); 087 entry.setProperty(SCHEMA, "authorizationServerURL", authorizationServerURL); 088 entry.setProperty(SCHEMA, "tokenServerURL", tokenServerURL); 089 entry.setProperty(SCHEMA, "clientId", clientId); 090 entry.setProperty(SCHEMA, "clientSecret", clientSecret); 091 entry.setProperty(SCHEMA, "scopes", StringUtils.join(scopes, ",")); 092 boolean enabled = (clientId != null && clientSecret != null); 093 entry.setProperty(SCHEMA, "enabled", enabled); 094 if (!enabled) { 095 log.info("OAuth2 provider for " + serviceName 096 + " is disabled because clientId and/or clientSecret are empty"); 097 } 098 session.updateEntry(entry); 099 return getProvider(serviceName); 100 } 101 } 102 103 protected List<DocumentModel> queryProviders(Map<String, Serializable> filter, int limit) { 104 DirectoryService ds = Framework.getService(DirectoryService.class); 105 try (Session session = ds.open(DIRECTORY_NAME)) { 106 Set<String> fulltext = Collections.emptySet(); 107 Map<String, String> orderBy = Collections.emptyMap(); 108 return session.query(filter, fulltext, orderBy, true, limit, 0); 109 } catch (DirectoryException e) { 110 log.error("Error while fetching provider directory", e); 111 } 112 return Collections.emptyList(); 113 } 114 115 /** 116 * Instantiates the provider merging the contribution and the directory entry 117 */ 118 protected OAuth2ServiceProvider buildProvider(DocumentModel entry) { 119 String serviceName = (String) entry.getProperty(SCHEMA, "serviceName"); 120 OAuth2ServiceProvider provider = registry.getProvider(serviceName); 121 if (provider == null) { 122 provider = new NuxeoOAuth2ServiceProvider(); 123 provider.setServiceName(serviceName); 124 } 125 provider.setId((Long) entry.getProperty(SCHEMA, "id")); 126 provider.setAuthorizationServerURL((String) entry.getProperty(SCHEMA, "authorizationServerURL")); 127 provider.setTokenServerURL((String) entry.getProperty(SCHEMA, "tokenServerURL")); 128 provider.setClientId((String) entry.getProperty(SCHEMA, "clientId")); 129 provider.setClientSecret((String) entry.getProperty(SCHEMA, "clientSecret")); 130 String scopes = (String) entry.getProperty(SCHEMA, "scopes"); 131 provider.setScopes(StringUtils.split(scopes, ",")); 132 provider.setEnabled((Boolean) entry.getProperty(SCHEMA, "enabled")); 133 return provider; 134 } 135 136 @Override 137 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 138 if (PROVIDER_EP.equals(extensionPoint)) { 139 OAuth2ServiceProviderDescriptor provider = (OAuth2ServiceProviderDescriptor) contribution; 140 log.info("OAuth2 provider for " + provider.getName() + " will be registered at application startup"); 141 // delay registration because data sources may not be available 142 // at this point 143 registry.addContribution(provider); 144 } 145 } 146 147 @Override 148 public void applicationStarted(ComponentContext context) { 149 super.applicationStarted(context); 150 registerCustomProviders(); 151 } 152 153 protected void registerCustomProviders() { 154 for (OAuth2ServiceProviderDescriptor provider : registry.getContribs()) { 155 if (getProvider(provider.getName()) == null) { 156 addProvider(provider.getName(), provider.getDescription(), provider.getTokenServerURL(), 157 provider.getAuthorizationServerURL(), provider.getClientId(), provider.getClientSecret(), 158 Arrays.asList(provider.getScopes())); 159 } else { 160 log.info("Provider " + provider.getName() 161 + " is already in the Database, XML contribution won't overwrite it"); 162 } 163 } 164 } 165}