/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <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>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.basesecurity;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import org.olat.basesecurity.manager.GroupDAO;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.fullWebApp.BaseFullWebappController;
import org.olat.core.commons.fullWebApp.BaseFullWebappControllerParts;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.dispatcher.DispatcherModule;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.Windows;
import org.olat.core.gui.components.Window;
import org.olat.core.gui.control.ChiefController;
import org.olat.core.gui.media.RedirectMediaResource;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.gui.render.URLBuilder;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.logging.activity.OlatLoggingAction;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.SessionInfo;
import org.olat.core.util.StringHelper;
import org.olat.core.util.UserSession;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.i18n.I18nManager;
import org.olat.core.util.i18n.I18nModule;
import org.olat.core.util.session.UserSessionManager;
import org.olat.course.assessment.AssessmentMode;
import org.olat.course.assessment.AssessmentModeManager;
import org.olat.course.assessment.AssessmentModule;
import org.olat.course.assessment.model.TransientAssessmentMode;
import org.olat.login.AuthBFWCParts;
import org.olat.login.GuestBFWCParts;
import org.olat.portfolio.manager.InvitationDAO;
import org.olat.user.UserManager;
import org.olat.util.logging.activity.LoggingResourceable;
/**
* Description: <br>
*
* @author Felix Jost
*/
public class AuthHelper {
/**
* <code>LOGOUT_PAGE</code>
*/
public static final int LOGIN_OK = 0;
private static final int LOGIN_FAILED = 1;
private static final int LOGIN_DENIED = 2;
public static final int LOGIN_NOTAVAILABLE = 3;
private static final int MAX_SESSION_NO_LIMIT = 0;
/** whether or not requests to dmz (except those coming via 'switch-to-node' cluster feature) are
* rejected hence resulting the browser to go to another node.
* Note: this is not configurable currently as it's more of a runtime choice to change this to true
*/
private static boolean rejectDMZRequests = false;
private static boolean loginBlocked = false;
private static int maxSessions = MAX_SESSION_NO_LIMIT;
private static OLog log = Tracing.createLoggerFor(AuthHelper.class);
/**
* Used by DMZDispatcher to do regular logins and by ShibbolethDispatcher
* which is somewhat special because logins are handled asynchronuous ->
* therefore a dedicated dispatcher is needed which also has to have access to
* the doLogin() method.
*
* @param identity
* @param authProvider
* @param ureq
* @return True if success, false otherwise.
*/
public static int doLogin(Identity identity, String authProvider, UserRequest ureq) {
int initializeStatus = initializeLogin(identity, authProvider, ureq, false);
if (initializeStatus != LOGIN_OK) {
return initializeStatus; // login not successfull
}
// do logging
ThreadLocalUserActivityLogger.log(OlatLoggingAction.OLAT_LOGIN, AuthHelper.class, LoggingResourceable.wrap(identity));
// brasato:: fix it
// successfull login, reregister window
ChiefController occ;
if(ureq.getUserSession().getRoles().isGuestOnly()){
occ = createGuestHome(ureq);
}else{
occ = createAuthHome(ureq);
}
Window currentWindow = occ.getWindow();
currentWindow.setUriPrefix(WebappHelper.getServletContextPath() + DispatcherModule.PATH_AUTHENTICATED);
Windows.getWindows(ureq).registerWindow(currentWindow);
RedirectMediaResource redirect;
String redirectTo = (String)ureq.getUserSession().getEntry("redirect-bc");
if(StringHelper.containsNonWhitespace(redirectTo)) {
String url = WebappHelper.getServletContextPath() + DispatcherModule.PATH_AUTHENTICATED + redirectTo;
redirect = new RedirectMediaResource(url);
} else {
// redirect to AuthenticatedDispatcher
// IMPORTANT: windowID has changed due to re-registering current window -> do not use ureq.getWindowID() to build new URLBuilder.
URLBuilder ubu = new URLBuilder(WebappHelper.getServletContextPath() + DispatcherModule.PATH_AUTHENTICATED, currentWindow.getInstanceId(), "1");
StringOutput sout = new StringOutput(30);
ubu.buildURI(sout, null, null);
redirect = new RedirectMediaResource(sout.toString());
}
ureq.getDispatchResult().setResultingMediaResource(redirect);
return LOGIN_OK;
}
/**
*
* @param identity
* @param authProvider
* @param ureq
* @param Is login via REST API?
* @return
*/
public static int doHeadlessLogin(Identity identity, String authProvider, UserRequest ureq, boolean rest) {
int initializeStatus = initializeLogin(identity, authProvider, ureq, rest);
if (initializeStatus != LOGIN_OK) {
return initializeStatus; // login not successful
}
// Set session info to reflect the REST headless login
UserSession usess = ureq.getUserSession();
usess.getSessionInfo().setREST(true);
usess.getIdentityEnvironment().getAttributes().put("isrest", "true");
//
ThreadLocalUserActivityLogger.log(OlatLoggingAction.OLAT_LOGIN, AuthHelper.class, LoggingResourceable.wrap(identity));
return LOGIN_OK;
}
/**
* Create a base chief controller for the current anonymous user request
* and initialize the first screen after login. Note, the user request
* must be authenticated, but as an anonymous user and not a known user.
*
* @param ureq The authenticated user request.
* @return The chief controller
*/
private static ChiefController createGuestHome(UserRequest ureq) {
if (!ureq.getUserSession().isAuthenticated()) throw new AssertException("not authenticated!");
BaseFullWebappControllerParts guestSitesAndNav = new GuestBFWCParts();
ChiefController cc = new BaseFullWebappController(ureq, guestSitesAndNav);
Windows.getWindows(ureq.getUserSession()).setChiefController(cc);
return cc;
}
/**
* Create a base chief controller for the current authenticated user request
* and initialize the first screen after login.
*
* @param ureq The authenticated user request.
* @return The chief controller
*/
public static ChiefController createAuthHome(UserRequest ureq) {
if (!ureq.getUserSession().isAuthenticated()) throw new AssertException("not authenticated!");
BaseFullWebappControllerParts authSitesAndNav = new AuthBFWCParts();
ChiefController cc = new BaseFullWebappController(ureq, authSitesAndNav);
Windows.getWindows(ureq.getUserSession()).setChiefController(cc);
return cc;
}
/**
* Logs in as anonymous user using the given language key. If the current
* installation does not support this language, the systems default language
* is used instead
*
* @param ureq The user request
* @param lang The language of the anonymous user or null if system default should be used
* @return true if login was successful, false otherwise
*/
public static int doAnonymousLogin(UserRequest ureq, Locale locale) {
Collection<String> supportedLanguages = I18nModule.getEnabledLanguageKeys();
if ( locale == null || ! supportedLanguages.contains(locale.toString()) ) {
locale = I18nModule.getDefaultLocale();
}
Identity guestIdent = BaseSecurityManager.getInstance().getAndUpdateAnonymousUserForLanguage(locale);
int loginStatus = doLogin(guestIdent, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq);
return loginStatus;
}
public static int doInvitationLogin(String invitationToken, UserRequest ureq, Locale locale) {
InvitationDAO invitationDao = CoreSpringFactory.getImpl(InvitationDAO.class);
boolean hasPolicies = invitationDao.hasInvitations(invitationToken, new Date());
if(!hasPolicies) {
return LOGIN_DENIED;
}
UserManager um = UserManager.getInstance();
BaseSecurity securityManager = BaseSecurityManager.getInstance();
GroupDAO groupDao = CoreSpringFactory.getImpl(GroupDAO.class);
Invitation invitation = invitationDao.findInvitation(invitationToken);
if(invitation == null) {
return LOGIN_DENIED;
}
//check if identity exists
Identity identity = um.findIdentityByEmail(invitation.getMail());
if(identity != null) {
SecurityGroup allUsers = securityManager.findSecurityGroupByName(Constants.GROUP_OLATUSERS);
if(securityManager.isIdentityInSecurityGroup(identity, allUsers)) {
//already a normal olat user, cannot be invited
return LOGIN_DENIED;
} else {
//fxdiff FXOLAT-151: add eventually the identity to the security group
if(!groupDao.hasRole(invitation.getBaseGroup(), identity, GroupRoles.invitee.name())) {
groupDao.addMembershipTwoWay(invitation.getBaseGroup(), identity, GroupRoles.invitee.name());
DBFactory.getInstance().commit();
}
int result = doLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq);
//fxdiff FXOLAT-151: double check: problem with the DB, invitee is not marked has such
if(ureq.getUserSession().getRoles().isInvitee()) {
return result;
}
return LOGIN_DENIED;
}
}
Collection<String> supportedLanguages = I18nModule.getEnabledLanguageKeys();
if ( locale == null || ! supportedLanguages.contains(locale.toString()) ) {
locale = I18nModule.getDefaultLocale();
}
//invitation ok -> create a temporary user
Identity invitee = invitationDao.createIdentityFrom(invitation, locale);
return doLogin(invitee, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq);
}
/**
* ONLY for authentication provider OLAT Authenticate Identity and do the
* necessary work. Returns true if successfull, false otherwise.
*
* @param identity
* @param authProvider
* @param ureq
* @return boolean
*/
private static int initializeLogin(Identity identity, String authProvider, UserRequest ureq, boolean rest) {
// continue only if user has login permission.
if (identity == null) return LOGIN_FAILED;
//test if a user may not logon, since he/she is in the PERMISSION_LOGON
if (!BaseSecurityManager.getInstance().isIdentityVisible(identity)) {
log.audit("was denied login");
return LOGIN_DENIED;
}
UserSessionManager sessionManager = CoreSpringFactory.getImpl(UserSessionManager.class);
// if the user sending the cookie did not log out and we are logging in
// again, then we need to make sure everything is cleaned up. we cleanup in all cases.
UserSession usess = ureq.getUserSession();
// prepare for a new user: clear all the instance vars of the userSession
// note: does not invalidate the session, since it is reused
sessionManager.signOffAndClear(usess);
// init the UserSession for the new User
// we can set the identity and finish the log in process
usess.setIdentity(identity);
setRolesFor(identity, usess);
// check if loginDenied or maxSession (only for non-admin)
if ( (loginBlocked && !usess.getRoles().isOLATAdmin())
|| ( ((maxSessions != MAX_SESSION_NO_LIMIT) && (sessionManager.getUserSessionsCnt() >= maxSessions)) && !usess.getRoles().isOLATAdmin() ) ) {
log.audit("Login was blocked for username=" + usess.getIdentity().getName() + ", loginBlocked=" + loginBlocked + " NbrOfSessions=" + sessionManager.getUserSessionsCnt());
sessionManager.signOffAndClear(usess);
return LOGIN_NOTAVAILABLE;
}
//need to block the all things for assessment?
if(usess.getRoles() != null && usess.getRoles().isOLATAdmin()) {
usess.setAssessmentModes(Collections.<TransientAssessmentMode>emptyList());
} else {
AssessmentModule assessmentModule = CoreSpringFactory.getImpl(AssessmentModule.class);
if(assessmentModule.isAssessmentModeEnabled()) {
AssessmentModeManager assessmentManager = CoreSpringFactory.getImpl(AssessmentModeManager.class);
List<AssessmentMode> modes = assessmentManager.getAssessmentModeFor(identity);
if(modes.isEmpty()) {
usess.setAssessmentModes(Collections.<TransientAssessmentMode>emptyList());
} else {
usess.setAssessmentModes(TransientAssessmentMode.create(modes));
}
}
}
//set the language
usess.setLocale( I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()) );
// update fontsize in users session globalsettings
Windows.getWindows(ureq).getWindowManager().setFontSize(Integer.parseInt(identity.getUser().getPreferences().getFontsize() ));
// calculate session info and attach it to the user session
setSessionInfoFor(identity, authProvider, ureq, rest);
//confirm signedOn
sessionManager.signOn(usess);
// set users web delivery mode
Windows.getWindows(ureq).getWindowManager().setAjaxWanted(ureq);
// update web delivery mode in session info
usess.getSessionInfo().setWebModeFromUreq(ureq);
return LOGIN_OK;
}
/**
* This is a convenience method to log out. IMPORTANT: This method initiates a
* redirect and RETURN. Make sure you return the call hierarchy gracefully.
* Most of all, don't touch HttpServletRequest or the Session after you call
* this method.
*
* @param ureq
*/
public static void doLogout(UserRequest ureq) {
if(ureq == null) return;
boolean wasGuest = false;
UserSession usess = ureq.getUserSession();
if(usess != null && usess.getRoles() != null) {
wasGuest = ureq.getUserSession().getRoles().isGuestOnly();
}
String lang = I18nManager.getInstance().getLocaleKey(ureq.getLocale());
HttpSession session = ureq.getHttpReq().getSession(false);
// next line fires a valueunbound event to UserSession, which does some
// stuff on logout
if (session != null) {
try{
session.invalidate();
deleteShibsessionCookie(ureq);
} catch(IllegalStateException ise) {
// thrown when session already invalidated. fine. ignore.
}
}
// redirect to logout page in dmz realm, set info that DMZ is shown because of logout
// if it was a guest user, do not set logout=true. The parameter must be evaluated
// by the implementation of the AuthenticationProvider.
String setWarning = wasGuest ? "" : "&logout=true";
ureq.getDispatchResult().setResultingMediaResource(
new RedirectMediaResource(WebappHelper.getServletContextPath() + "/dmz/?lang=" + lang + setWarning));
}
private static void deleteShibsessionCookie(UserRequest ureq) {
// try to delete the "shibsession" cookie for this ureq, if any found
Cookie[] cookies = ureq.getHttpReq().getCookies();
Cookie cookie = null;
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
/*if(log.isDebug()) {
log.info("found cookie with name: " + cookies[i].getName() + " and value: " + cookies[i].getValue());
}*/
if (cookies[i].getName().indexOf("shibsession")!=-1) { //contains "shibsession"
cookie = cookies[i];
break;
}
}
}
if(cookie!=null) {
//A zero value causes the cookie to be deleted.
cookie.setMaxAge(0);
cookie.setPath("/");
ureq.getHttpResp().addCookie(cookie);
if(log.isDebug()) {
log.info("AuthHelper - shibsession cookie deleted");
}
}
}
/**
* Build session info
* @param identity
* @param authProvider
* @param ureq
*/
public static void setSessionInfoFor(Identity identity, String authProvider, UserRequest ureq, boolean rest) {
HttpSession session = ureq.getHttpReq().getSession();
SessionInfo sinfo = new SessionInfo(identity.getKey(), identity.getName(), session);
sinfo.setFirstname(identity.getUser().getProperty(UserConstants.FIRSTNAME, ureq.getLocale()));
sinfo.setLastname(identity.getUser().getProperty(UserConstants.LASTNAME, ureq.getLocale()));
sinfo.setFromIP(ureq.getHttpReq().getRemoteAddr());
sinfo.setFromFQN(ureq.getHttpReq().getRemoteAddr());
try {
InetAddress[] iaddr = InetAddress.getAllByName(ureq.getHttpReq().getRemoteAddr());
if (iaddr.length > 0) sinfo.setFromFQN(iaddr[0].getHostName());
} catch (UnknownHostException e) {
// ok, already set IP as FQDN
}
sinfo.setAuthProvider(authProvider);
sinfo.setUserAgent(ureq.getHttpReq().getHeader("User-Agent"));
sinfo.setSecure(ureq.getHttpReq().isSecure());
sinfo.setLastClickTime();
sinfo.setREST(rest);
// set session info for this session
UserSession usess = ureq.getUserSession();
usess.setSessionInfo(sinfo);
// For Usertracking, let the User object know about some desired/specified infos from the sessioninfo
Map<String,String> sessionInfoForUsertracking = new HashMap<String, String>();
sessionInfoForUsertracking.put("language", usess.getLocale().toString());
sessionInfoForUsertracking.put("authprovider", authProvider);
sessionInfoForUsertracking.put("iswebdav", String.valueOf(sinfo.isWebDAV()));
sessionInfoForUsertracking.put("isrest", String.valueOf(sinfo.isREST()));
usess.getIdentityEnvironment().setAttributes(sessionInfoForUsertracking);
}
/**
* Set the roles (admin, author, guest)
* @param identity
* @param usess
*/
private static void setRolesFor(Identity identity, UserSession usess) {
Roles roles = BaseSecurityManager.getInstance().getRoles(identity);
usess.setRoles(roles);
}
public static void setLoginBlocked(boolean newLoginBlocked) {
loginBlocked = newLoginBlocked;
}
public static boolean isLoginBlocked() {
return loginBlocked;
}
public static void setRejectDMZRequests(boolean newRejectDMZRequests) {
rejectDMZRequests = newRejectDMZRequests;
}
public static boolean isRejectDMZRequests() {
return rejectDMZRequests;
}
public static void setMaxSessions(int maxSession) {
maxSessions = maxSession;
}
public static int getMaxSessions() {
return maxSessions;
}
}