diff --git a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java index 7e1ec8bb8e4d5219ea381006cb7537c5f0f7e798..1c7d64c6986115cd3564139dc24a1f28c7a459c7 100644 --- a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java +++ b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java @@ -22,6 +22,7 @@ package org.olat.core.configuration; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.olat.core.gui.control.Event; @@ -128,6 +129,10 @@ public abstract class AbstractSpringModule implements GenericEventListener, Init protected Properties createPropertiesFromPersistedProperties() { return moduleConfigProperties.createPropertiesFromPersistedProperties(); } + + protected Set<Object> getPropertyKeys() { + return moduleConfigProperties.getPropertyKeys(); + } /** * Return a string value for certain propertyName-parameter. @@ -197,6 +202,12 @@ public abstract class AbstractSpringModule implements GenericEventListener, Init moduleConfigProperties.setIntProperty(propertyName, value, saveConfiguration); log.audit("change system property: " + propertyName, Integer.toString(value)); } + + protected void removeProperty(String propertyName, boolean saveConfiguration) { + moduleConfigProperties.removeProperty(propertyName, saveConfiguration); + log.audit("remove system property: " + propertyName, null); + } + /** * Return a boolean value for certain propertyName * diff --git a/src/main/java/org/olat/core/configuration/PersistedProperties.java b/src/main/java/org/olat/core/configuration/PersistedProperties.java index f0126d779e3947847a335c48c867175dd152db4a..3164b8c69339200311d74479d50ecd595c31789b 100644 --- a/src/main/java/org/olat/core/configuration/PersistedProperties.java +++ b/src/main/java/org/olat/core/configuration/PersistedProperties.java @@ -35,7 +35,9 @@ import java.io.OutputStream; import java.nio.file.Paths; import java.security.SecureRandom; import java.security.Security; +import java.util.HashSet; import java.util.Properties; +import java.util.Set; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -257,12 +259,24 @@ public class PersistedProperties extends LogDelegator implements Initializable, * Call this method when the PersitedProperties is not used anymore. Will * remove the event listener for change events on this class */ + @Override public final void destroy() { if (propertiesChangedEventListener != null) { coordinatorManager.getCoordinator().getEventBus().deregisterFor(propertiesChangedEventListener, PROPERTIES_CHANGED_EVENT_CHANNEL); propertiesChangedEventListener = null; } } + + public Set<Object> getPropertyKeys() { + Set<Object> keys = new HashSet<>(); + if(configuredProperties != null) { + keys.addAll(configuredProperties.keySet()); + } + if(defaultProperties != null) { + keys.addAll(defaultProperties.keySet()); + } + return keys; + } /** * Return an int value for a certain propertyName @@ -426,6 +440,15 @@ public class PersistedProperties extends LogDelegator implements Initializable, public void setBooleanPropertyDefault(String propertyName, boolean value) { defaultProperties.setProperty(propertyName, Boolean.toString(value)); } + + public void removeProperty(String propertyName, boolean saveConfiguration) { + synchronized (configuredProperties) { // make read/write save in VM + configuredProperties.remove(propertyName); + if (saveConfiguration) { + savePropertiesAndFireChangedEvent(); + } + } + } /** * Save the properties configuration to disk and notify other nodes about @@ -512,20 +535,19 @@ public class PersistedProperties extends LogDelegator implements Initializable, private static final String salt = "A long, but constant phrase that will be used each time as the salt."; - private static final int iterations = 2000; - private static final int keyLength = 128; - private static final SecureRandom random = new SecureRandom(); + private static final int iterations = 2000; + private static final int keyLength = 128; + private static final SecureRandom random = new SecureRandom(); private static SecretKey generateKey(String passphrase) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC"); - return keyFactory.generateSecret(keySpec); + PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC"); + return keyFactory.generateSecret(keySpec); } private static IvParameterSpec generateIV(Cipher cipher) throws Exception { - byte [] ivBytes = new byte[cipher.getBlockSize()]; - random.nextBytes(ivBytes); - return new IvParameterSpec(ivBytes); - } - + byte [] ivBytes = new byte[cipher.getBlockSize()]; + random.nextBytes(ivBytes); + return new IvParameterSpec(ivBytes); + } } diff --git a/src/main/java/org/olat/dispatcher/DMZDispatcher.java b/src/main/java/org/olat/dispatcher/DMZDispatcher.java index 51e0b38c0a05ee537066110d52a5d9e7b15485f2..deebbe566225d403c9c49e89b7d879c9f4240c1e 100644 --- a/src/main/java/org/olat/dispatcher/DMZDispatcher.java +++ b/src/main/java/org/olat/dispatcher/DMZDispatcher.java @@ -235,12 +235,14 @@ public class DMZDispatcher implements Dispatcher { I18nManager.updateLocaleInfoToThread(usess);//update locale infos OAuthLoginModule oauthModule = CoreSpringFactory.getImpl(OAuthLoginModule.class); - if(canRedirectOAuth(request, oauthModule)) { + if(canRedirectConfigurableOAuth(request, response, oauthModule)) { + return; + } else if(canRedirectOAuth(request, oauthModule)) { OAuthSPI oauthSpi = oauthModule.getRootProvider(); HttpSession session = request.getSession(); OAuthResource.redirect(oauthSpi, response, session); return; - } + } // request new windows since it is a new usersession, the old one was purged ws = Windows.getWindows(usess); @@ -301,6 +303,19 @@ public class DMZDispatcher implements Dispatcher { } return canRedirect; } + + private boolean canRedirectConfigurableOAuth(HttpServletRequest request, HttpServletResponse response, OAuthLoginModule oauthModule) { + String provider = request.getParameter("provider"); + if(StringHelper.containsNonWhitespace(provider)) { + OAuthSPI spi = oauthModule.getProvider(provider); + if(spi != null) { + HttpSession session = request.getSession(); + OAuthResource.redirect(spi, response, session); + return true; + } + } + return false; + } /** * called by spring only diff --git a/src/main/java/org/olat/login/LoginModule.java b/src/main/java/org/olat/login/LoginModule.java index b4b08b735850cb14a20cac6cd97ecebe3d06d89a..1826c84eab03284d87182f3e0e8a97cae2d8efa9 100644 --- a/src/main/java/org/olat/login/LoginModule.java +++ b/src/main/java/org/olat/login/LoginModule.java @@ -192,6 +192,8 @@ public class LoginModule extends AbstractSpringModule { for(AuthenticationProvider authProvider:authenticationProviders) { if(authProvider.getName().equalsIgnoreCase(provider)) { authenticationProvider = authProvider; + } else if(authProvider.accept(provider)) { + authenticationProvider = authProvider; } } return authenticationProvider; diff --git a/src/main/java/org/olat/login/auth/AuthenticationProvider.java b/src/main/java/org/olat/login/auth/AuthenticationProvider.java index 44df8b621f8ba212706164ea3e8b544cb695fb89..6f40cefafa84693d6391c218b3be5dff1d7b1912 100644 --- a/src/main/java/org/olat/login/auth/AuthenticationProvider.java +++ b/src/main/java/org/olat/login/auth/AuthenticationProvider.java @@ -85,6 +85,10 @@ public class AuthenticationProvider implements ControllerCreator{ return isDefault; } + public boolean accept(@SuppressWarnings("unused") String subProviderName) { + return false; + } + /** * @return Name used to identify this authprovider. */ diff --git a/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java b/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java index c704a18d605cf41422b64d4b16176989838dd525..7b16dbce02c89170125e3d0357f384a66e8c2e9a 100644 --- a/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java +++ b/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java @@ -36,6 +36,18 @@ public class OAuthAuthenticationProvider extends AuthenticationProvider { super(name, clazz, true, isDefault, iconCssClass); } + @Override + public boolean accept(String subProviderName) { + OAuthLoginModule oauthLoginModule = CoreSpringFactory.getImpl(OAuthLoginModule.class); + List<OAuthSPI> spies = oauthLoginModule.getEnableSPIs(); + for(OAuthSPI spi:spies) { + if(spi.getProviderName().equals(subProviderName)) { + return true; + } + } + return false; + } + @Override public boolean isEnabled() { OAuthLoginModule oauthLoginModule = CoreSpringFactory.getImpl(OAuthLoginModule.class); diff --git a/src/main/java/org/olat/login/oauth/OAuthDisplayName.java b/src/main/java/org/olat/login/oauth/OAuthDisplayName.java new file mode 100644 index 0000000000000000000000000000000000000000..18aeb65150ea93a6d78710841d08e4be1a47241e --- /dev/null +++ b/src/main/java/org/olat/login/oauth/OAuthDisplayName.java @@ -0,0 +1,13 @@ +package org.olat.login.oauth; + +/** + * + * Initial date: 6 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface OAuthDisplayName { + + public String getDisplayName(); + +} diff --git a/src/main/java/org/olat/login/oauth/OAuthLoginModule.java b/src/main/java/org/olat/login/oauth/OAuthLoginModule.java index d4dbff02d79e22144065485adc2b64093b8a1953..6b1f508b1651ae1d8ddc0a88c5b3e998ec4b59f1 100644 --- a/src/main/java/org/olat/login/oauth/OAuthLoginModule.java +++ b/src/main/java/org/olat/login/oauth/OAuthLoginModule.java @@ -21,9 +21,11 @@ package org.olat.login.oauth; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.olat.core.configuration.AbstractSpringModule; import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.login.oauth.spi.OpenIdConnectFullConfigurableProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,6 +38,9 @@ import org.springframework.stereotype.Service; @Service public class OAuthLoginModule extends AbstractSpringModule { + private static final String OPEN_ID_IF_START_MARKER = "openIdConnectIF."; + private static final String OPEN_ID_IF_END_MARKER = ".Enabled"; + private boolean allowUserCreation; private boolean linkedInEnabled; @@ -71,6 +76,8 @@ public class OAuthLoginModule extends AbstractSpringModule { @Autowired private List<OAuthSPI> oauthSPIs; + private List<OAuthSPI> configurableOauthSPIs; + @Autowired public OAuthLoginModule(CoordinatorManager coordinatorManager) { super(coordinatorManager, true); @@ -131,10 +138,60 @@ public class OAuthLoginModule extends AbstractSpringModule { openIdConnectIFApiSecret = getStringPropertyValue("openIdConnectIFApiSecret", false); openIdConnectIFIssuer = getStringPropertyValue("openIdConnectIFIssuer", false); openIdConnectIFAuthorizationEndPoint = getStringPropertyValue("openIdConnectIFAuthorizationEndPoint", false); + + Set<Object> allPropertyKeys = getPropertyKeys(); + List<OAuthSPI> otherOAuthSPies = new ArrayList<>(); + for(Object propertyKey:allPropertyKeys) { + if(propertyKey instanceof String) { + String key = (String)propertyKey; + if(key.startsWith(OPEN_ID_IF_START_MARKER) && key.endsWith(OPEN_ID_IF_END_MARKER)) { + OAuthSPI spi = getAdditionalOpenIDConnectIF(key); + if(spi != null) { + otherOAuthSPies.add(spi); + } + } + } + } + configurableOauthSPIs = otherOAuthSPies; + } + + private OAuthSPI getAdditionalOpenIDConnectIF(String enableKey) { + String providerName = enableKey.substring(OPEN_ID_IF_START_MARKER.length(), enableKey.length() - OPEN_ID_IF_END_MARKER.length()); + + String rootEnabledObj = getStringPropertyValue("openIdConnectIF." + providerName + ".RootEnabled", true); + boolean rootEnabled = "true".equals(rootEnabledObj); + String apiKey = getStringPropertyValue("openIdConnectIF." + providerName + ".ApiKey", true); + String apiSecret = getStringPropertyValue("openIdConnectIF." + providerName + ".ApiSecret", true); + String issuer = getStringPropertyValue("openIdConnectIF." + providerName + ".Issuer", true); + String endPoint = getStringPropertyValue("openIdConnectIF." + providerName + ".AuthorizationEndPoint", true); + String displayName = getStringPropertyValue("openIdConnectIF." + providerName + ".DisplayName", true); + + OpenIdConnectFullConfigurableProvider provider = new OpenIdConnectFullConfigurableProvider(); + provider.setRootEnabled(rootEnabled); + provider.setName(providerName); + provider.setDisplayName(displayName); + provider.setProviderName(providerName); + provider.setAppKey(apiKey); + provider.setAppSecret(apiSecret); + provider.setIssuer(issuer); + provider.setEndPoint(endPoint); + return provider; } public List<OAuthSPI> getAllSPIs() { - return new ArrayList<>(oauthSPIs); + List<OAuthSPI> spies = new ArrayList<>(oauthSPIs); + if(configurableOauthSPIs != null) { + spies.addAll(configurableOauthSPIs); + } + return spies; + } + + public List<OAuthSPI> getAllConfigurableSPIs() { + List<OAuthSPI> spies = new ArrayList<>(); + if(configurableOauthSPIs != null) { + spies.addAll(configurableOauthSPIs); + } + return spies; } public List<OAuthSPI> getEnableSPIs() { @@ -146,6 +203,13 @@ public class OAuthLoginModule extends AbstractSpringModule { } } } + if(configurableOauthSPIs != null) { + for(OAuthSPI spi:configurableOauthSPIs) { + if(spi.isEnabled()) { + enabledSpis.add(spi); + } + } + } return enabledSpis; } @@ -162,8 +226,34 @@ public class OAuthLoginModule extends AbstractSpringModule { } } } + if(rootSpi == null && configurableOauthSPIs != null) { + for(OAuthSPI spi:configurableOauthSPIs) { + if(spi.isEnabled() && spi.isRootEnabled()) { + rootSpi = spi; + } + } + } return rootSpi; } + + public OAuthSPI getProvider(String providerName) { + OAuthSPI spi = null; + if(oauthSPIs != null) { + for(OAuthSPI oauthSpi:oauthSPIs) { + if(providerName.equals(oauthSpi.getProviderName())) { + spi = oauthSpi; + } + } + } + if(spi == null && configurableOauthSPIs != null) { + for(OAuthSPI oauthSpi:configurableOauthSPIs) { + if(providerName.equals(oauthSpi.getProviderName())) { + spi = oauthSpi; + } + } + } + return spi; + } public boolean isAllowUserCreation() { return allowUserCreation; @@ -381,4 +471,24 @@ public class OAuthLoginModule extends AbstractSpringModule { setStringProperty("openIdConnectIFAuthorizationEndPoint", openIdConnectIFAuthorizationEndPoint, true); } + public void setAdditionalOpenIDConnectIF(String providerName, String displayName, boolean rootEnabled, String issuer, String endPoint, String apiKey, String apiSecret) { + setStringProperty("openIdConnectIF." + providerName + ".Enabled", "true", false); + setStringProperty("openIdConnectIF." + providerName + ".RootEnabled", rootEnabled ? "true" : "false", false); + setStringProperty("openIdConnectIF." + providerName + ".ApiKey", apiKey, false); + setStringProperty("openIdConnectIF." + providerName + ".ApiSecret", apiSecret, false); + setStringProperty("openIdConnectIF." + providerName + ".Issuer", issuer, false); + setStringProperty("openIdConnectIF." + providerName + ".DisplayName", displayName, false); + setStringProperty("openIdConnectIF." + providerName + ".AuthorizationEndPoint", endPoint, true); + updateProperties(); + } + + public void removeAdditionalOpenIDConnectIF(String providerName) { + removeProperty("openIdConnectIF." + providerName + ".Enabled", false); + removeProperty("openIdConnectIF." + providerName + ".ApiKey", false); + removeProperty("openIdConnectIF." + providerName + ".ApiSecret", false); + removeProperty("openIdConnectIF." + providerName + ".Issuer", false); + removeProperty("openIdConnectIF." + providerName + ".DisplayName", false); + removeProperty("openIdConnectIF." + providerName + ".AuthorizationEndPoint", true); + updateProperties(); + } } diff --git a/src/main/java/org/olat/login/oauth/OAuthSPI.java b/src/main/java/org/olat/login/oauth/OAuthSPI.java index 2ab05a60317a88b55cb2058b6f4e7c433bdbafd9..f8490e8a8dac723ff3d5cdf8728e90a91dcfcdf8 100644 --- a/src/main/java/org/olat/login/oauth/OAuthSPI.java +++ b/src/main/java/org/olat/login/oauth/OAuthSPI.java @@ -33,7 +33,7 @@ import org.scribe.oauth.OAuthService; */ public interface OAuthSPI extends ConfigOnOff { - public Class<? extends Api> getScribeProvider(); + public Api getScribeProvider(); public String getName(); diff --git a/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java b/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java index 5cd5b7d33fc2bca47bff4e16928bb983436bbe1c..79dcb1ab7b4ba25403dc7ad4c20120e7364fd69a 100644 --- a/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java +++ b/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java @@ -63,8 +63,8 @@ public class ADFSProvider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return ADFSApi.class; + public Api getScribeProvider() { + return new ADFSApi(); } @Override diff --git a/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java b/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java index 1a7df90a56d8d33096dc59f718f67845ae887911..3f77881cab5d0360d7865c1b67c0f0aafcea1cb3 100644 --- a/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java +++ b/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java @@ -67,8 +67,8 @@ public class FacebookProvider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return FacebookApi.class; + public Api getScribeProvider() { + return new FacebookApi(); } @Override diff --git a/src/main/java/org/olat/login/oauth/spi/Google2Provider.java b/src/main/java/org/olat/login/oauth/spi/Google2Provider.java index 817314cb682c71b986877ceb1646b591a33bc475..e3cad3f4a76b36fc9cc3934ef77d987b8db3afa1 100644 --- a/src/main/java/org/olat/login/oauth/spi/Google2Provider.java +++ b/src/main/java/org/olat/login/oauth/spi/Google2Provider.java @@ -66,8 +66,8 @@ public class Google2Provider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return Google2Api.class; + public Api getScribeProvider() { + return new Google2Api(); } @Override diff --git a/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java b/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java index 60cacd456402853a3ad2ec5128502567952680cf..7999287b877d3e699ba4c443943bd7cc6948b704 100644 --- a/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java +++ b/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java @@ -93,8 +93,8 @@ public class LinkedInProvider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return LinkedInApi.class; + public Api getScribeProvider() { + return new LinkedInApi(); } @Override diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java new file mode 100644 index 0000000000000000000000000000000000000000..6aff4fe904646a2915a75f5fb8a8f15c1e802541 --- /dev/null +++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java @@ -0,0 +1,129 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.login.oauth.spi; + +import java.util.UUID; + +import org.json.JSONException; +import org.json.JSONObject; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.scribe.builder.api.DefaultApi20; +import org.scribe.extractors.AccessTokenExtractor; +import org.scribe.model.OAuthConfig; +import org.scribe.model.Token; +import org.scribe.model.Verb; +import org.scribe.model.Verifier; +import org.scribe.oauth.OAuth20ServiceImpl; +import org.scribe.oauth.OAuthService; +import org.scribe.utils.OAuthEncoder; + +/** + * + * Initial date: 6 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OpenIdConnectFullConfigurableApi extends DefaultApi20 { + + private static final OLog log = Tracing.createLoggerFor(OpenIdConnectFullConfigurableApi.class); + + private final OpenIdConnectFullConfigurableProvider provider; + + public OpenIdConnectFullConfigurableApi(OpenIdConnectFullConfigurableProvider provider) { + this.provider = provider; + } + + @Override + public String getAccessTokenEndpoint() { + return null; + } + + @Override + public AccessTokenExtractor getAccessTokenExtractor() { + return null; + } + + @Override + public String getAuthorizationUrl(OAuthConfig config) { + String url = provider.getEndPoint(); + StringBuilder authorizeUrl = new StringBuilder(); + authorizeUrl + .append(url).append("?") + .append("response_type=").append(OAuthEncoder.encode("id_token token")) + .append("&client_id=").append(config.getApiKey()) + .append("&redirect_uri=").append(OAuthEncoder.encode(config.getCallback())) + .append("&scope=").append(OAuthEncoder.encode("openid email")) + .append("&state=").append(UUID.randomUUID().toString()) + .append("&nonce=").append(UUID.randomUUID().toString()); + return authorizeUrl.toString(); + } + + @Override + public Verb getAccessTokenVerb() { + return Verb.POST; + } + + @Override + public OAuthService createService(OAuthConfig config) { + return new OpenIdConnectFullConfigurableService(this, config); + } + + public class OpenIdConnectFullConfigurableService extends OAuth20ServiceImpl { + + public OpenIdConnectFullConfigurableService(DefaultApi20 api, OAuthConfig config) { + super(api, config); + } + + @Override + public Token getAccessToken(Token requestToken, Verifier verifier) { + try { + OpenIDVerifier oVerifier = (OpenIDVerifier)verifier; + String idToken = oVerifier.getIdToken(); + JSONObject idJson = JSONWebToken.parse(idToken).getJsonPayload(); + JSONObject accessJson = JSONWebToken.parse(oVerifier.getAccessToken()).getJsonPayload(); + + boolean allOk = true; + if(!provider.getIssuer().equals(idJson.get("iss"))) { + allOk &= false; + } + if(!provider.getIssuer().equals(accessJson.get("iss"))) { + allOk &= false; + } + + if(!provider.getAppKey().equals(idJson.get("aud"))) { + allOk &= false; + } + if(!oVerifier.getState().equals(oVerifier.getSessionState())) { + allOk &= false; + } + + if(!oVerifier.getSessionNonce().equals(idJson.get("nonce"))) { + allOk &= false; + } + + return allOk ? new Token(idToken, oVerifier.getState()) : null; + } catch (JSONException e) { + log.error("", e); + return null; + } + } + } +} diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..72c1098ffa4245c5b24bf98ba6de1e46bfe96937 --- /dev/null +++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java @@ -0,0 +1,179 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.login.oauth.spi; + +import org.json.JSONException; +import org.json.JSONObject; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.login.oauth.OAuthDisplayName; +import org.olat.login.oauth.OAuthSPI; +import org.olat.login.oauth.model.OAuthUser; +import org.scribe.builder.api.Api; +import org.scribe.model.Token; +import org.scribe.oauth.OAuthService; + +/** + * + * Initial date: 6 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OpenIdConnectFullConfigurableProvider implements OAuthSPI, OAuthDisplayName { + + private static final OLog log = Tracing.createLoggerFor(Google2Provider.class); + + private String name; + private String displayName; + private String providerName; + private String appKey; + private String appSecret; + private String issuer; + private String endPoint; + + private boolean rootEnabled; + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isRootEnabled() { + return rootEnabled; + } + + public void setRootEnabled(boolean rootEnabled) { + this.rootEnabled = rootEnabled; + } + + @Override + public boolean isImplicitWorkflow() { + return true; + } + + @Override + public Api getScribeProvider() { + return new OpenIdConnectFullConfigurableApi(this); + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + @Override + public String getIconCSS() { + return "o_icon o_icon_provider_" + name; + } + + @Override + public String getAppKey() { + return appKey; + } + + public void setAppKey(String appKey) { + this.appKey = appKey; + } + + @Override + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getEndPoint() { + return endPoint; + } + + public void setEndPoint(String endPoint) { + this.endPoint = endPoint; + } + + @Override + public String[] getScopes() { + return new String[] { "openid", "email" }; + } + + @Override + public OAuthUser getUser(OAuthService service, Token accessToken) { + try { + String idToken = accessToken.getToken(); + JSONWebToken token = JSONWebToken.parse(idToken); + return parseInfos(token.getPayload()); + } catch (JSONException e) { + log.error("", e); + return null; + } + } + + public OAuthUser parseInfos(String body) { + OAuthUser user = new OAuthUser(); + + try { + JSONObject obj = new JSONObject(body); + user.setId(getValue(obj, "sub")); + user.setEmail(getValue(obj, "sub")); + } catch (JSONException e) { + log.error("", e); + } + + return user; + } + + private String getValue(JSONObject obj, String property) { + String value = obj.optString(property); + return StringHelper.containsNonWhitespace(value) ? value : null; + } +} diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java index 5fa47dd1bb113d4cfabfcb7898d1ea5e331ebc43..97c4340bad64f723c32693cf06de5da400a628fc 100644 --- a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java +++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java @@ -63,8 +63,8 @@ public class OpenIdConnectProvider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return OpenIdConnectApi.class; + public Api getScribeProvider() { + return new OpenIdConnectApi(); } @Override diff --git a/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java b/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java index 81a0dc30e8813bcdc4ca89de6ff383a832e0a181..0c03269eb3c6f3c5612ebfaede13ca47c6606280 100644 --- a/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java +++ b/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java @@ -67,8 +67,8 @@ public class TwitterProvider implements OAuthSPI { } @Override - public Class<? extends Api> getScribeProvider() { - return TwitterApi.SSL.class; + public Api getScribeProvider() { + return new TwitterApi.SSL(); } @Override diff --git a/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java b/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java new file mode 100644 index 0000000000000000000000000000000000000000..9359aec042fdb411e2306436b3acf988dbe37497 --- /dev/null +++ b/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java @@ -0,0 +1,124 @@ +package org.olat.login.oauth.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.login.oauth.OAuthLoginModule; +import org.olat.login.oauth.OAuthSPI; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 6 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AddOpenIDConnectIFFullConfigurableController extends FormBasicController { + + private static final String[] keys = new String[]{ "on" }; + private static final String[] values = new String[] { "" }; + + private TextElement openIdConnectIFName; + private TextElement openIdConnectIFDisplayName; + private TextElement openIdConnectIFApiKeyEl; + private TextElement openIdConnectIFApiSecretEl; + private TextElement openIdConnectIFIssuerEl; + private TextElement openIdConnectIFAuthorizationEndPointEl; + + private MultipleSelectionElement openIdConnectIFDefaultEl; + + @Autowired + private OAuthLoginModule oauthModule; + + public AddOpenIDConnectIFFullConfigurableController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + openIdConnectIFDefaultEl = uifactory.addCheckboxesHorizontal("openidconnectif.default.enabled", formLayout, keys, values); + openIdConnectIFDefaultEl.addActionListener(FormEvent.ONCHANGE); + + openIdConnectIFName = uifactory.addTextElement("openidconnectif.name", "openidconnectif.name", 256, "", formLayout); + openIdConnectIFDisplayName = uifactory.addTextElement("openidconnectif.displayname", "openidconnectif.displayname", 256, "", formLayout); + + openIdConnectIFApiKeyEl = uifactory.addTextElement("openidconnectif.id", "openidconnectif.api.id", 256, "", formLayout); + openIdConnectIFApiSecretEl = uifactory.addTextElement("openidconnectif.secret", "openidconnectif.api.secret", 256, "", formLayout); + + openIdConnectIFIssuerEl = uifactory.addTextElement("openidconnectif.issuer", "openidconnectif.issuer", 256, "", formLayout); + openIdConnectIFIssuerEl.setExampleKey("openidconnectif.issuer.example", null); + + openIdConnectIFAuthorizationEndPointEl = uifactory.addTextElement("openidconnectif.authorization.endpoint", "openidconnectif.authorization.endpoint", 256, "", formLayout); + openIdConnectIFAuthorizationEndPointEl.setExampleKey("openidconnectif.authorization.endpoint.example", null); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); + formLayout.add(buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("save", buttonLayout); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + allOk &= validate(openIdConnectIFName); + allOk &= validate(openIdConnectIFDisplayName); + allOk &= validate(openIdConnectIFApiKeyEl); + allOk &= validate(openIdConnectIFApiSecretEl); + allOk &= validate(openIdConnectIFIssuerEl); + allOk &= validate(openIdConnectIFAuthorizationEndPointEl); + + String providerName = openIdConnectIFName.getValue(); + if(StringHelper.containsNonWhitespace(providerName)) { + OAuthSPI existingSpi = oauthModule.getProvider(providerName); + if(existingSpi != null) { + openIdConnectIFName.setErrorKey("error.duplicate.provider", null); + allOk &= false; + } + } + + return allOk & super.validateFormLogic(ureq); + } + + private boolean validate(TextElement el) { + boolean allOk = true; + el.clearError(); + if(!StringHelper.containsNonWhitespace(el.getValue())) { + el.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + return allOk; + } + + @Override + protected void formOK(UserRequest ureq) { + String providerName = openIdConnectIFName.getValue(); + String displayName = openIdConnectIFDisplayName.getValue(); + String issuer = openIdConnectIFIssuerEl.getValue(); + String endPoint = openIdConnectIFAuthorizationEndPointEl.getValue(); + String apiKey = openIdConnectIFApiKeyEl.getValue(); + String apiSecret = openIdConnectIFApiSecretEl.getValue(); + boolean rootEnabled = openIdConnectIFDefaultEl.isAtLeastSelected(1); + oauthModule.setAdditionalOpenIDConnectIF(providerName, displayName, rootEnabled, issuer, endPoint, apiKey, apiSecret); + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java b/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java index 46b66e0e9b1cb57944abfb55199767acb0681376..1a2cee4a8df1580afaacbcca8465bf54e0f68d4c 100644 --- a/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java +++ b/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java @@ -19,18 +19,30 @@ */ package org.olat.login.oauth.ui; +import java.util.ArrayList; +import java.util.List; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.util.CodeHelper; import org.olat.core.util.StringHelper; import org.olat.login.oauth.OAuthLoginModule; +import org.olat.login.oauth.OAuthSPI; +import org.olat.login.oauth.spi.OpenIdConnectFullConfigurableProvider; import org.springframework.beans.factory.annotation.Autowired; /** @@ -44,6 +56,7 @@ public class OAuthAdminController extends FormBasicController { private static final String[] keys = new String[]{ "on" }; private static final String[] values = new String[] { "" }; + private FormLink addProviderLink; private MultipleSelectionElement userCreationEl; @@ -75,6 +88,14 @@ public class OAuthAdminController extends FormBasicController { private TextElement openIdConnectIFIssuerEl; private TextElement openIdConnectIFAuthorizationEndPointEl; + private FormLayoutContainer customProvidersCont; + + private CloseableModalController cmc; + private DialogBoxController confirmDeleteCtrl; + private AddOpenIDConnectIFFullConfigurableController addConfigCtrl; + + private List<ConfigurableProviderWrapper> providerWrappers = new ArrayList<>(); + @Autowired private OAuthLoginModule oauthModule; @@ -250,6 +271,13 @@ public class OAuthAdminController extends FormBasicController { openIdConnectIFDefaultEl.select(keys[0], true); } + customProvidersCont = FormLayoutContainer.createBareBoneFormLayout("custom.providers", getTranslator()); + customProvidersCont.setRootForm(mainForm); + formLayout.add(customProvidersCont); + + //highly configurable providers + initCustomProviders(); + //buttons FormLayoutContainer buttonBonesCont = FormLayoutContainer.createDefaultFormLayout("button_bones", getTranslator()); buttonBonesCont.setRootForm(mainForm); @@ -258,14 +286,39 @@ public class OAuthAdminController extends FormBasicController { FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); buttonBonesCont.add(buttonLayout); uifactory.addFormSubmitButton("save", buttonLayout); + addProviderLink = uifactory.addFormLink("add.openidconnectif.custom", buttonLayout, Link.BUTTON); + } + + private void initCustomProviders() { + // remove old ones + for(ConfigurableProviderWrapper providerWrapper:providerWrappers) { + FormItemContainer layoutCont = providerWrapper.getLayoutCont(); + customProvidersCont.remove(layoutCont); + } + + providerWrappers.clear(); + List<OAuthSPI> configurableSpies = oauthModule.getAllConfigurableSPIs(); + for(OAuthSPI configurableSpi:configurableSpies) { + if(configurableSpi instanceof OpenIdConnectFullConfigurableProvider) { + ConfigurableProviderWrapper wrapper = + initOpenIDConnectIFFullConfigurableProviders(customProvidersCont, (OpenIdConnectFullConfigurableProvider)configurableSpi); + if(wrapper != null) { + providerWrappers.add(wrapper); + } + } + } + } + + private ConfigurableProviderWrapper initOpenIDConnectIFFullConfigurableProviders(FormItemContainer formLayout, OpenIdConnectFullConfigurableProvider provider) { + ConfigurableProviderWrapper wrapper = new ConfigurableProviderWrapper(provider); + wrapper.initForm(formLayout); + return wrapper; } @Override protected void doDispose() { // } - - @Override protected boolean validateFormLogic(UserRequest ureq) { @@ -282,6 +335,10 @@ public class OAuthAdminController extends FormBasicController { allOk &= mandatory(adfsEl, adfsApiKeyEl, adfsOAuth2EndpointEl); //open id connect allOk &= mandatory(openIdConnectIFEl, openIdConnectIFAuthorizationEndPointEl, openIdConnectIFApiKeyEl, openIdConnectIFApiSecretEl); + + for(ConfigurableProviderWrapper wrapper:providerWrappers) { + allOk &= wrapper.validateFormLogic(); + } return allOk & super.validateFormLogic(ureq); } @@ -306,6 +363,25 @@ public class OAuthAdminController extends FormBasicController { return allOk; } + + private boolean mandatory(TextElement... textEls) { + boolean allOk = true; + + if(textEls != null) { + for(int i=textEls.length; i-->0; ) { + TextElement textEl = textEls[i]; + if(textEl != null) { + textEl.clearError(); + if(!StringHelper.containsNonWhitespace(textEl.getValue())) { + textEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + } + } + } + + return allOk; + } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { @@ -331,6 +407,14 @@ public class OAuthAdminController extends FormBasicController { openIdConnectIFDefaultEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1)); openIdConnectIFApiSecretEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1)); openIdConnectIFAuthorizationEndPointEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1)); + } else if(addProviderLink == source) { + doAddOpenIDConnectIFCustom(ureq); + } else if(source instanceof FormLink) { + FormLink link = (FormLink)source; + if("delete".equals(link.getCmd())) { + ConfigurableProviderWrapper providerWrapper = (ConfigurableProviderWrapper)link.getUserObject(); + doConfirmDelete(ureq, providerWrapper); + } } super.formInnerEvent(ureq, source, event); } @@ -406,5 +490,140 @@ public class OAuthAdminController extends FormBasicController { oauthModule.setOpenIdConnectIFIssuer(""); oauthModule.setOpenIdConnectIFAuthorizationEndPoint(""); } + + for(ConfigurableProviderWrapper wrapper:providerWrappers) { + wrapper.commit(); + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(addConfigCtrl == source) { + if(event == Event.DONE_EVENT) { + initCustomProviders(); + } + cmc.deactivate(); + cleanUp(); + } else if(confirmDeleteCtrl == source) { + if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) { + ConfigurableProviderWrapper providerWrapper = (ConfigurableProviderWrapper)confirmDeleteCtrl.getUserObject(); + doDelete(providerWrapper); + } + } else if(cmc == source) { + cleanUp(); + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(addConfigCtrl); + removeAsListenerAndDispose(cmc); + addConfigCtrl = null; + cmc = null; + } + + private void doAddOpenIDConnectIFCustom(UserRequest ureq) { + addConfigCtrl = new AddOpenIDConnectIFFullConfigurableController(ureq, getWindowControl()); + listenTo(addConfigCtrl); + + String title = translate("add.openidconnectif.custom"); + cmc = new CloseableModalController(getWindowControl(), null, addConfigCtrl.getInitialComponent(), true, title, true); + listenTo(cmc); + cmc.activate(); + } + + private void doConfirmDelete(UserRequest ureq, ConfigurableProviderWrapper providerWrapper) { + OAuthSPI spi = providerWrapper.getSpi(); + String title = translate("confirm.delete.provider.title", new String[]{ spi.getProviderName() }); + String text = translate("confirm.delete.provider.text", new String[]{ spi.getProviderName() }); + confirmDeleteCtrl = activateOkCancelDialog(ureq, title, text, confirmDeleteCtrl); + confirmDeleteCtrl.setUserObject(providerWrapper); + } + + private void doDelete(ConfigurableProviderWrapper providerWrapper) { + OAuthSPI spi = providerWrapper.getSpi(); + if(spi instanceof OpenIdConnectFullConfigurableProvider) { + oauthModule.removeAdditionalOpenIDConnectIF(spi.getProviderName()); + } + initCustomProviders(); + } + + public class ConfigurableProviderWrapper { + + private FormLayoutContainer openIdConnectIFCont; + + private FormLink deleteButton; + private TextElement openIdConnectIFConfName; + private TextElement openIdConnectIFConfDisplayName; + private TextElement openIdConnectIFConfApiKeyEl; + private TextElement openIdConnectIFConfApiSecretEl; + private TextElement openIdConnectIFConfIssuerEl; + private TextElement openIdConnectIFConfAuthorizationEndPointEl; + + private final OpenIdConnectFullConfigurableProvider spi; + + public ConfigurableProviderWrapper(OpenIdConnectFullConfigurableProvider spi) { + this.spi = spi; + } + + public OpenIdConnectFullConfigurableProvider getSpi() { + return spi; + } + + public FormItemContainer getLayoutCont() { + return openIdConnectIFCont; + } + + public void initForm(FormItemContainer container) { + String counter = Long.toString(CodeHelper.getRAMUniqueID()); + openIdConnectIFCont = FormLayoutContainer.createDefaultFormLayout("openidconnectif." + counter, getTranslator()); + openIdConnectIFCont.setFormTitle(translate("openidconnectif.admin.custom.title", new String[]{ spi.getProviderName() })); + openIdConnectIFCont.setFormTitleIconCss("o_icon o_icon_provider_openid"); + openIdConnectIFCont.setRootForm(mainForm); + container.add(openIdConnectIFCont); + openIdConnectIFDefaultEl = uifactory.addCheckboxesHorizontal("openidconnectif." + counter + ".default.enabled", "openidconnectif.default.enabled", openIdConnectIFCont, keys, values); + if(spi.isRootEnabled()) { + openIdConnectIFDefaultEl.select(keys[0], true); + } + + String providerName = spi.getProviderName(); + openIdConnectIFConfName = uifactory.addTextElement("openidconnectif." + counter + ".name", "openidconnectif.name", 256, providerName, openIdConnectIFCont); + openIdConnectIFConfName.setEnabled(false); + + String displayName = spi.getDisplayName(); + openIdConnectIFConfDisplayName = uifactory.addTextElement("openidconnectif." + counter + ".displayname", "openidconnectif.displayname", 256, displayName, openIdConnectIFCont); + String apiKey = spi.getAppKey(); + openIdConnectIFConfApiKeyEl = uifactory.addTextElement("openidconnectif." + counter + ".id", "openidconnectif.api.id", 256, apiKey, openIdConnectIFCont); + String apiSecret = spi.getAppSecret(); + openIdConnectIFConfApiSecretEl = uifactory.addTextElement("openidconnectif." + counter + ".secret", "openidconnectif.api.secret", 256, apiSecret, openIdConnectIFCont); + String issuer = spi.getIssuer(); + openIdConnectIFConfIssuerEl = uifactory.addTextElement("openidconnectif." + counter + ".issuer", "openidconnectif.issuer", 256, issuer, openIdConnectIFCont); + openIdConnectIFConfIssuerEl.setExampleKey("openidconnectif.issuer.example", null); + String endPoint = spi.getEndPoint(); + openIdConnectIFConfAuthorizationEndPointEl = uifactory.addTextElement("openidconnectif." + counter + ".authorization.endpoint", "openidconnectif.authorization.endpoint", 256, endPoint, openIdConnectIFCont); + openIdConnectIFConfAuthorizationEndPointEl.setExampleKey("openidconnectif.authorization.endpoint.example", null); + + deleteButton = uifactory.addFormLink("delete.".concat(counter), "delete", "delete", null, openIdConnectIFCont, Link.BUTTON); + deleteButton.setUserObject(this); + } + + protected boolean validateFormLogic() { + boolean allOk = true; + + allOk &= mandatory(openIdConnectIFConfName, openIdConnectIFConfDisplayName, openIdConnectIFConfApiKeyEl, openIdConnectIFConfApiSecretEl, + openIdConnectIFConfIssuerEl, openIdConnectIFConfAuthorizationEndPointEl); + + return allOk; + } + + protected void commit() { + String displayName = openIdConnectIFConfDisplayName.getValue(); + String issuer = openIdConnectIFConfIssuerEl.getValue(); + String endPoint = openIdConnectIFConfAuthorizationEndPointEl.getValue(); + String apiKey = openIdConnectIFConfApiKeyEl.getValue(); + String apiSecret = openIdConnectIFConfApiSecretEl.getValue(); + boolean rootEnabled = openIdConnectIFDefaultEl.isAtLeastSelected(1); + oauthModule.setAdditionalOpenIDConnectIF(spi.getProviderName(), displayName, rootEnabled, issuer, endPoint, apiKey, apiSecret); + } } } diff --git a/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java b/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java index 2ba5fdd9ce8ff29c5e417533c3c81bb31f91801e..e0ff4190af60ff62f5cdfdbeecbbc1161b437256 100644 --- a/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java +++ b/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java @@ -19,6 +19,8 @@ */ package org.olat.login.oauth.ui; +import java.util.List; + import javax.servlet.http.HttpSession; import org.olat.core.gui.UserRequest; @@ -30,7 +32,12 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.media.MediaResource; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.core.util.StringHelper; +import org.olat.login.oauth.OAuthDisplayName; import org.olat.login.oauth.OAuthLoginModule; import org.olat.login.oauth.OAuthResource; import org.olat.login.oauth.OAuthSPI; @@ -42,7 +49,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class OAuthAuthenticationController extends FormBasicController { +public class OAuthAuthenticationController extends FormBasicController implements Activateable2 { @Autowired private OAuthLoginModule oauthModule; @@ -50,13 +57,30 @@ public class OAuthAuthenticationController extends FormBasicController { public OAuthAuthenticationController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl, "login"); initForm(ureq); + + String provider = ureq.getParameter("provider"); + if(StringHelper.containsNonWhitespace(provider)) { + OAuthSPI spi = oauthModule.getProvider(provider); + if(spi != null) { + redirect(ureq, spi); + } + } } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { for(OAuthSPI spi: oauthModule.getEnableSPIs()) { - String spiName = spi.getName(); - FormLink button = uifactory.addFormLink(spiName, "login", "login." + spiName, null, formLayout, Link.BUTTON); + String spiName; + int presentation; + if(spi instanceof OAuthDisplayName) { + spiName = ((OAuthDisplayName)spi).getDisplayName(); + presentation = Link.BUTTON | Link.NONTRANSLATED; + } else { + spiName = "login.".concat(spi.getName()); + presentation = Link.BUTTON; + } + + FormLink button = uifactory.addFormLink(spiName, "login", spiName, null, formLayout, presentation); button.setIconLeftCSS(spi.getIconCSS()); button.setElementCssClass("o_sel_auth_" + spiName); button.setUserObject(spi); @@ -68,6 +92,11 @@ public class OAuthAuthenticationController extends FormBasicController { // } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + System.out.println(""); + } + @Override protected void formOK(UserRequest ureq) { // @@ -79,11 +108,15 @@ public class OAuthAuthenticationController extends FormBasicController { FormLink button = (FormLink)source; if("login".equals(button.getCmd())) { OAuthSPI provider = (OAuthSPI)source.getUserObject(); - HttpSession session = ureq.getHttpReq().getSession(); - MediaResource redirectResource = new OAuthResource(provider, session); - ureq.getDispatchResult().setResultingMediaResource(redirectResource); + redirect(ureq, provider); } } super.formInnerEvent(ureq, source, event); } + + private void redirect(UserRequest ureq, OAuthSPI provider) { + HttpSession session = ureq.getHttpReq().getSession(); + MediaResource redirectResource = new OAuthResource(provider, session); + ureq.getDispatchResult().setResultingMediaResource(redirectResource); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties index 52bff0035b5c6eb133c672643e998cca23f50b9e..dcce2b06681dd5b7a8bcafb5990e87663c18942c 100644 --- a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties @@ -1,4 +1,5 @@ #Thu Jul 21 17:55:01 CEST 2016 +add.openidconnectif.custom=Custom Open ID Connect erstellen adfs.admin.title=Active directory federation system adfs.admin.title.alt=Active directory federation system adfs.api.id=Client ID @@ -10,9 +11,12 @@ admin.menu.title=Soziale Netzwerke admin.menu.title.alt=OAuth und Soziale Netzwerke authentication.provider.description=Melden Sie sich \u00FCber Ihr Konto auf einem Ihrer sozialen Netzwerk an authentication.provider.linkText=Soziale Netzwerke +confirm.delete.provider.title=Provider "{0}" l\u00F6schen +confirm.delete.provider.text=Wollen Sie wirklichen den Provider "{0}" l\u00F6schen? disclaimer.title=Disclaimer error.access.denied=Sie sind nicht berechtigt auf den OpenOLAT Dienst zuzugreifen (access_denied). Bitte kontaktieren Sie den Systemadministrator. error.account.creation=Ihr Konto wurde auf dem OpenOLAT Dienst noch nicht angelegt, dies ist notwendig um sich anmelden zu k\u00F6nnen. Bitte kontaktieren Sie den Systemadministrator. +error.duplicate.provider=Dieser Provider existiert schon. error.generic=Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es sp\u00E4ter noch einmal. error.invalid.grant=Sie sind nicht berechtigt auf den OpenOLAT Dienst zuzugreifen (invalid_grant). Bitte kontaktieren Sie den Systemadministrator. error.no.id=Sie konnten nicht identifiziert werden. Bitte kontaktieren Sie den Systemadministrator. @@ -39,6 +43,7 @@ login.linkedin=LinkedIn login.panther=My Hamilton login.twitter=Twitter oauth.admin.title=OAuth Konfiguration +openidconnectif.admin.custom.title="{0}" OpenID Connect Implicit Flow openidconnectif.admin.title=OpenID Connect Implicit Flow openidconnectif.admin.title.alt=OpenID Connect with Implicit Flow openidconnectif.api.id=Client Id @@ -49,6 +54,8 @@ openidconnectif.default.enabled=$\:adfs.default.enabled openidconnectif.enabled=Open ID Connect openidconnectif.issuer=Issuer openidconnectif.issuer.example=https\://frentix.com +openidconnectif.name=Provider name +openidconnectif.displayname=Display name openidconnectif.wait.message=Sie werden in wenigen Augenblicken zum OpenOLAT Dienst weitergeleitet. twitter.admin.title=Twitter Konfiguration twitter.api.id=Consumer key (App key) diff --git a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties index 04c86c714f61d8b8ab651b45e7fda35b2baaa54b..67ad49999aa1d7eb9d1dbdae2e410702d9de472f 100644 --- a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties @@ -1,4 +1,5 @@ #Thu Jul 21 17:54:47 CEST 2016 +add.openidconnectif.custom=Add custom Open ID Connect adfs.admin.title=Active directory federation system adfs.admin.title.alt=Active directory federation system adfs.api.id=Client ID @@ -10,9 +11,12 @@ admin.menu.title=Social providers admin.menu.title.alt=OAuth and social providers authentication.provider.description=Login with your favorite social media authentication.provider.linkText=Social logins +confirm.delete.provider.title=Delete provider "{0}" +confirm.delete.provider.text=Do you really want to delete the following provider "{0}"? disclaimer.title=Disclaimer error.access.denied=You are not authorized to use the OpenOLAT service (access_denied). Please contact the system administrator. error.account.creation=Your account is not yet created on the OpenOLAT service. This is required for beeing able to log in. Please contact the system administrator. +error.duplicate.provider=This provider already exists. error.generic=An unexpected error occurred. Please try it again later. error.invalid.grant=You are not allowed to access the OpenOLAT service (invalid_grant). Please contact the system administrator. error.no.id=We were not able to identify you. Please contact the system administrator. @@ -49,6 +53,8 @@ openidconnectif.default.enabled=$\:adfs.default.enabled openidconnectif.enabled=Open ID Connect openidconnectif.issuer=Issuer openidconnectif.issuer.example=https\://frentix.com +openidconnectif.name=Provider name +openidconnectif.displayname=Display name openidconnectif.wait.message=You will be redirected to OpenOLAT momentarily. twitter.admin.title=Twitter configuration twitter.api.id=Consumer key (App key)