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