/** * <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; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.AuthHelper; import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.MessageWindowController; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.dispatcher.DispatcherModule; import org.olat.core.gui.UserRequest; import org.olat.core.gui.UserRequestImpl; import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.RedirectMediaResource; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.ThreadLocalUserActivityLoggerInstaller; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.login.oauth.model.OAuthRegistration; import org.olat.login.oauth.model.OAuthUser; import org.olat.login.oauth.spi.OpenIDVerifier; import org.olat.login.oauth.ui.JSRedirectWindowController; import org.olat.login.oauth.ui.OAuthAuthenticationController; import org.olat.user.UserManager; import org.scribe.model.Token; import org.scribe.model.Verifier; import org.scribe.oauth.OAuthService; import org.springframework.beans.factory.annotation.Autowired; /** * Callback for OAuth 2 * * * Initial date: 03.11.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class OAuthDispatcher implements Dispatcher { private static final OLog log = Tracing.createLoggerFor(OAuthDispatcher.class); @Autowired private UserManager userManager; @Autowired private BaseSecurityManager securityManager; @Override public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uri = request.getRequestURI(); try { uri = URLDecoder.decode(uri, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertException("UTF-8 encoding not supported!!!!"); } String uriPrefix = DispatcherModule.getLegacyUriPrefix(request); uri = uri.substring(uriPrefix.length()); UserRequest ureq = null; try{ //upon creation URL is checked for ureq = new UserRequestImpl(uriPrefix, request, response); } catch(NumberFormatException nfe) { if(log.isDebug()){ log.debug("Bad Request "+request.getPathInfo()); } DispatcherModule.sendBadRequest(request.getPathInfo(), response); return; } String error = request.getParameter("error"); if(null != error) { error(ureq, translateOauthError(ureq, error)); return; } String problem = request.getParameter("oauth_problem"); if(problem != null && "token_rejected".equals(problem.trim())) { error(ureq, translateOauthError(ureq, error)); return; } try { HttpSession sess = request.getSession(); //OAuth 2.0 hasn't any request token Token requestToken = (Token)sess.getAttribute(OAuthConstants.REQUEST_TOKEN); OAuthService service = (OAuthService)sess.getAttribute(OAuthConstants.OAUTH_SERVICE); OAuthSPI provider = (OAuthSPI)sess.getAttribute(OAuthConstants.OAUTH_SPI); Token accessToken; if(provider.isImplicitWorkflow()) { String idToken = ureq.getParameter("id_token"); if(idToken == null) { redirectImplicitWorkflow(ureq); return; } else { Verifier verifier = OpenIDVerifier.create(ureq, sess); accessToken = service.getAccessToken(requestToken, verifier); } } else { String requestVerifier = request.getParameter("oauth_verifier"); if(requestVerifier == null) {//OAuth 2.0 as a code requestVerifier = request.getParameter("code"); } accessToken = service.getAccessToken(requestToken, new Verifier(requestVerifier)); } OAuthUser infos = provider.getUser(service, accessToken); if(infos == null || !StringHelper.containsNonWhitespace(infos.getId())) { error(ureq, translate(ureq, "error.no.id")); log.error("OAuth Login failed, no infos extracted from access token "); return; } OAuthRegistration registration = new OAuthRegistration(provider.getProviderName(), infos); login(infos, registration); if(registration.getIdentity() == null) { if(CoreSpringFactory.getImpl(OAuthLoginModule.class).isAllowUserCreation()) { register(request, response, registration); } else { error(ureq, translate(ureq, "error.account.creation")); log.error("OAuth Login ok but the user has not an account on OpenOLAT"); } } else { if(ureq.getUserSession() != null) { //re-init the activity logger ThreadLocalUserActivityLoggerInstaller.initUserActivityLogger(request); } Identity identity = registration.getIdentity(); int loginStatus = AuthHelper.doLogin(identity, provider.getProviderName(), ureq); if (loginStatus != AuthHelper.LOGIN_OK) { if (loginStatus == AuthHelper.LOGIN_NOTAVAILABLE) { DispatcherModule.redirectToServiceNotAvailable(response); } else { // error, redirect to login screen DispatcherModule.redirectToDefaultDispatcher(response); } } else { //update last login date and register active user UserDeletionManager.getInstance().setIdentityAsActiv(identity); MediaResource mr = ureq.getDispatchResult().getResultingMediaResource(); if (mr instanceof RedirectMediaResource) { RedirectMediaResource rmr = (RedirectMediaResource)mr; rmr.prepare(response); } else { DispatcherModule.redirectToDefaultDispatcher(response); // error, redirect to login screen } } } } catch (Exception e) { log.error("", e); error(ureq, translate(ureq, "error.generic")); } } private void redirectImplicitWorkflow(UserRequest ureq) { ChiefController msgcc = new JSRedirectWindowController(ureq); msgcc.getWindow().dispatchRequest(ureq, true); } private void login(OAuthUser infos, OAuthRegistration registration) { String id = infos.getId(); //has an identifier Authentication auth = null; if(StringHelper.containsNonWhitespace(id)) { auth = securityManager.findAuthenticationByAuthusername(id, registration.getAuthProvider()); if(auth == null) { String email = infos.getEmail(); if(StringHelper.containsNonWhitespace(email)) { Identity identity = null; try { identity = userManager.findIdentityByEmail(email); } catch(AssertException e) { // username was not an valid mail address. That is // totally ok here, continue with search by identity // name. } if(identity == null) { identity = securityManager.findIdentityByName(id); } if(identity != null) { auth = securityManager.createAndPersistAuthentication(identity, registration.getAuthProvider(), id, null, null); registration.setIdentity(identity); } else { log.error("OAuth Login failed, user with user name " + email + " not found."); } } } else { registration.setIdentity(auth.getIdentity()); } } } private String translate(UserRequest ureq, String i18nKey) { Translator trans = Util.createPackageTranslator(OAuthAuthenticationController.class, ureq.getLocale()); return trans.translate(i18nKey); } private String translateOauthError(UserRequest ureq, String error) { error = error == null ? null : error.trim(); String message; if("access_denied".equals(error)) { message = translate(ureq, "error.access.denied"); } else if("token_rejected".equals(error)) { message = translate(ureq, "error.token.rejected"); } else if("invalid_grant".equals(error)) { message = translate(ureq, "error.invalid.grant"); } else { message = translate(ureq, "error.generic"); } return message; } private void error(UserRequest ureq, String message) { StringBuilder sb = new StringBuilder(); sb.append("<h4><i class='o_icon o_icon-fw o_icon_error'> </i>"); sb.append(translate(ureq, "error.title")); sb.append("</h4><p>"); sb.append(message); sb.append("</p>"); ChiefController msgcc = new MessageWindowController(ureq, sb.toString()); msgcc.getWindow().dispatchRequest(ureq, true); } private void register(HttpServletRequest request, HttpServletResponse response, OAuthRegistration registration) { try { request.getSession().setAttribute("oauthRegistration", registration); response.sendRedirect(WebappHelper.getServletContextPath() + DispatcherModule.getPathDefault() + OAuthConstants.OAUTH_REGISTER_PATH + "/"); } catch (IOException e) { log.error("Redirect failed: url=" + WebappHelper.getServletContextPath() + DispatcherModule.getPathDefault(),e); } } }