All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prerna.util.linotp.LinOTPUtil Maven / Gradle / Ivy

The newest version!
package prerna.util.linotp;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.cookie.ClientCookie;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import prerna.auth.AccessToken;
import prerna.auth.AuthProvider;
import prerna.auth.User;
import prerna.auth.utils.AbstractSecurityUtils;
import prerna.auth.utils.SecurityAdminUtils;
import prerna.util.Constants;
import prerna.util.SocialPropertiesUtil;
import prerna.util.ldap.ILdapAuthenticator;
import prerna.util.ldap.LDAPPasswordChangeRequiredException;

public class LinOTPUtil {

	private static final Logger classLogger = LogManager.getLogger(LinOTPUtil.class);

	/**
	 * 
	 * @param request
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static LinOTPResponse login(HttpServletRequest request) throws ClientProtocolException, IOException {
		SocialPropertiesUtil socialData = SocialPropertiesUtil.getInstance();

		Map returnMap = new HashMap<>();
		LinOTPResponse linotpResponse = new LinOTPResponse();
		linotpResponse.setReturnMap(returnMap);

		final String LINOTP_USERNAME = "username";
		final String LINOTP_TRANSACTION = "transactionId";
		final String OTP = "OTP";
		final String AD_ACCESS_TOKEN = "ad_token";
		
		// https://YOUR_LINOTP_SERVER/validate/check?user=USERNAME&pass=PINOTP
		final String hostname = socialData.getProperty("linotp_hostname");
		final String realm = socialData.getProperty("linotp_realm");

		String controller = "validate";
		String action = "check";
		String requestURL = hostname + "/" + controller + "/" + action;
		String username = request.getParameter("username");
		String pin = request.getParameter("pin");
		String otp = request.getParameter("otp");

		if( (username == null || username.isEmpty())
				&& (otp == null || otp.isEmpty()) ) {
			returnMap.put(Constants.ERROR_MESSAGE, "The user name cannot be null or empty.");
			linotpResponse.setResponseCode(401);
			return linotpResponse;
		}

		if( (pin == null || pin.isEmpty()) && (otp == null || otp.isEmpty())) {
			returnMap.put(Constants.ERROR_MESSAGE, "Must be providing either a pin or otp");
			linotpResponse.setResponseCode(401);
			return linotpResponse;
		}

		AccessToken adAuthToken = null;
		if (otp==null) {
			boolean checkAD = Boolean.parseBoolean(socialData.getProperty("linotp_check_ad", "false"));
			if(checkAD) {
				ILdapAuthenticator authenticator = null;
				try {
					authenticator = socialData.getLdapAuthenticator();
					adAuthToken = authenticator.authenticate(username, pin);
					if(adAuthToken == null) {
						throw new IllegalArgumentException("Unable to parse any user attributes");
					}
					
					// store this in the session
					HttpSession session = request.getSession();
					session.setAttribute(AD_ACCESS_TOKEN, adAuthToken);
				} catch(LDAPPasswordChangeRequiredException e) {
					HttpSession session = request.getSession(false);
					if(session != null) {
						User user = (User) session.getAttribute(Constants.SESSION_USER);
						if(!AbstractSecurityUtils.anonymousUsersEnabled() && user != null && user.getLogins().isEmpty()) {
							session.invalidate();
						}
					}
					classLogger.error(Constants.STACKTRACE, e);
					returnMap.put(Constants.ERROR_MESSAGE, e.getMessage());
					returnMap.put(ILdapAuthenticator.LDAP_PASSWORD_CHANGE_RETURN_KEY, true);
					linotpResponse.setResponseCode(401);
					return linotpResponse;
				} catch (Exception e) {
					HttpSession session = request.getSession(false);
					if(session != null) {
						User user = (User) session.getAttribute(Constants.SESSION_USER);
						if(!AbstractSecurityUtils.anonymousUsersEnabled() && user != null && user.getLogins().isEmpty()) {
							session.invalidate();
						}
					}
					classLogger.error(Constants.STACKTRACE, e);
					if(e.getMessage() != null) {
						returnMap.put(Constants.ERROR_MESSAGE, "Failed to authenticate with active directory. Error message = " + e.getMessage());
					} else {
						returnMap.put(Constants.ERROR_MESSAGE, "Failed to authenticate with active directory.");
					}
					linotpResponse.setResponseCode(500);
					return linotpResponse;
				} finally {
					if(authenticator != null) {
						try {
							authenticator.close();
						} catch (IOException e) {
							classLogger.error(Constants.STACKTRACE, e);
						}
					}
				}
			}

			// first, request for challenge request using user pin
			// Create HTTP request via ssl port (https) and pass post parameters
			CloseableHttpClient httpclient = HttpClients
					.custom()
					.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
					.build();
			try {
				HttpEntity entity = null;
				HttpPost httpPost = new HttpPost(requestURL);
				List  nvps = new ArrayList ();
				nvps.add(new BasicNameValuePair("user", username));
				nvps.add(new BasicNameValuePair("pass", pin));
				nvps.add(new BasicNameValuePair("realm", realm));
				httpPost.setEntity(new UrlEncodedFormEntity(nvps));
				CloseableHttpResponse postResponse = httpclient.execute(httpPost);
				try {
					entity = postResponse.getEntity();
					String s_response = EntityUtils.toString(entity);
					JsonObject j_response = JsonParser.parseString(s_response).getAsJsonObject();
					//parse json response for result value
					JsonObject j_result = j_response.getAsJsonObject("result");
					boolean authenticated = false;
					if(j_result.has("value")) {
						authenticated = j_result.get("value").getAsBoolean();
					}
					if (authenticated) {
						// this means we have authenticated
						// and there is no policy requiring an otp
						// just the initial login is being performed where that
						// pin can be defined as the ldap password or the otp pin
						AccessToken token = new AccessToken();
						token.setProvider(AuthProvider.LINOTP);
						token.setId(username);
						token.setUsername(username);
						returnMap.put("success", "true");
						returnMap.put("username", username);
						
						// if we did an AD login might as well grab those properties
						HttpSession session = request.getSession();
						adAuthToken = (AccessToken) session.getAttribute(AD_ACCESS_TOKEN);
						if(adAuthToken != null) {
							token.setEmail(adAuthToken.getEmail());
							token.setLastPasswordReset(adAuthToken.getLastPasswordReset());
							session.removeAttribute(AD_ACCESS_TOKEN);
						}
						
						linotpResponse.setToken(token);
						linotpResponse.setResponseCode(200);
						
						// this is just an attempt to reset counter
						// still allow user to login
						try {
							resetCounter(new LinOTPResponse(), username);
						} catch(Exception e) {
							classLogger.error(Constants.STACKTRACE, e);
						}
						
						return linotpResponse;
					} else {
						// challenge request flow
						if (j_response.has("detail")) {
							JsonObject j_detail = j_response.getAsJsonObject("detail");
							String transactionId = j_detail.get("transactionid").getAsString();
							HttpSession session = request.getSession();
							session.setAttribute(LINOTP_USERNAME, username);
							session.setAttribute(LINOTP_TRANSACTION, transactionId);
							returnMap.put(OTP, "Please get OTP code.");
							linotpResponse.setResponseCode(200);
							return linotpResponse;
						} else {
							returnMap.put(Constants.ERROR_MESSAGE, "The user name or pin/password are invalid.");
							linotpResponse.setResponseCode(401);
							return linotpResponse;
						}
					}
					
				} finally {
					// consume will release the entity
					if(entity != null) {
						EntityUtils.consume(entity);
					}
					if(postResponse != null) {
						postResponse.close();
					}
				}
			} finally {
				if(httpclient != null) {
					httpclient.close();
				}
			}
			
		} else {
			// subsequent challenge request with otp
			// Create HTTP request via ssl port (https) and pass post parameters
			CloseableHttpClient httpclient = HttpClients
					.custom()
					.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
					.build();
			try {
				HttpPost httpPost = new HttpPost(requestURL);
				HttpSession session = request.getSession();
				username = (String) session.getAttribute(LINOTP_USERNAME);
				String transactionId = (String) session.getAttribute(LINOTP_TRANSACTION);
				if(username == null || username.isEmpty() || transactionId == null || transactionId.isEmpty()) {
					returnMap.put(Constants.ERROR_MESSAGE, "The user must re-enter their username and password before proceeding to enter their 2FA pin");
					linotpResponse.setResponseCode(401);
					return linotpResponse;
				}

				List  nvps = new ArrayList ();
				nvps.add(new BasicNameValuePair("user", username));
				nvps.add(new BasicNameValuePair("pass", otp));
				nvps.add(new BasicNameValuePair("realm", realm));
				nvps.add(new BasicNameValuePair("transactionid", transactionId));
				httpPost.setEntity(new UrlEncodedFormEntity(nvps));
				CloseableHttpResponse postResponse = httpclient.execute(httpPost);
				HttpEntity entity = null;
				try {
					entity = postResponse.getEntity();
					String s_response = EntityUtils.toString(entity);
					JsonObject j_response = JsonParser.parseString(s_response).getAsJsonObject();
					//parse json response for result value
					JsonObject j_result = j_response.getAsJsonObject("result");
					boolean authenticated = false;
					if(j_result.has("value")) {
						authenticated = j_result.get("value").getAsBoolean();
					}
					if (authenticated) {
						AccessToken token = new AccessToken();
						token.setProvider(AuthProvider.LINOTP);
						token.setId(username);
						token.setUsername(username);
						
						// if we did an AD login might as well grab those properties
						adAuthToken = (AccessToken) session.getAttribute(AD_ACCESS_TOKEN);
						if(adAuthToken != null) {
							token.setEmail(adAuthToken.getEmail());
							token.setLastPasswordReset(adAuthToken.getLastPasswordReset());
							session.removeAttribute(AD_ACCESS_TOKEN);
						}
						
						returnMap.put("success", "true");
						returnMap.put("username", username);
						
						linotpResponse.setToken(token);
						linotpResponse.setResponseCode(200);
						
						// this is just an attempt to reset counter
						// still allow user to login
						try {
							resetCounter(new LinOTPResponse(), username);
						} catch(Exception e) {
							classLogger.error(Constants.STACKTRACE, e);
						}
						
						return linotpResponse;
					}
					else {
						returnMap.put(Constants.ERROR_MESSAGE, "The username or one-time passcode are invalid.");
						linotpResponse.setResponseCode(401);
						return linotpResponse;
					}
				} finally {
					// consume will release the entity
					if(entity != null) {
						EntityUtils.consume(entity);
					}
					if(postResponse != null) {
						postResponse.close();
					}
				}
			} finally {
				if(httpclient != null) {
					httpclient.close();
				}
			}
		}
	}
	
	/**
	 * 
	 * @param request
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static LinOTPResponse resetCounter(HttpServletRequest request) throws ClientProtocolException, IOException {
		User thisUser = (User) request.getSession().getAttribute(Constants.SESSION_USER);
		String username = request.getParameter("username");
		return resetCounter(thisUser, username);
	}
	
	/**
	 * 
	 * @param thisUser
	 * @param username
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static LinOTPResponse resetCounter(User thisUser, String username) throws ClientProtocolException, IOException {
		Map returnMap = new HashMap<>();
		LinOTPResponse linotpResponse = new LinOTPResponse();
		linotpResponse.setReturnMap(returnMap);
		
		if(thisUser == null) {
			returnMap.put(Constants.ERROR_MESSAGE, "User must be logged in to invoke this endpoint");
			linotpResponse.setResponseCode(500);
			return linotpResponse;
		}
		if(SecurityAdminUtils.getInstance(thisUser) == null) {
			returnMap.put(Constants.ERROR_MESSAGE, "User must be an admin");
			linotpResponse.setResponseCode(500);
			return linotpResponse;
		}
		
		return resetCounter(linotpResponse, username);
	}
	
	/**
	 * 
	 * @param linotpResponse
	 * @param username
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	private static LinOTPResponse resetCounter(LinOTPResponse linotpResponse, String username) throws ClientProtocolException, IOException {
		SocialPropertiesUtil socialData = SocialPropertiesUtil.getInstance();
		Map returnMap = linotpResponse.getReturnMap();
		
		if(socialData.getLoginsAllowed().get("linotp")==null || !socialData.getLoginsAllowed().get("linotp")) {
			returnMap.put(Constants.ERROR_MESSAGE, "LinOTP login is not allowed");
			linotpResponse.setResponseCode(400);
			return linotpResponse;
		}

		final String adminUser = socialData.getProperty("linotp_adminuser");
		final String adminPass = socialData.getProperty("linotp_adminpassword");

		if(adminUser == null || adminPass == null || adminUser.isEmpty() || adminPass.isEmpty()) {
			returnMap.put(Constants.ERROR_MESSAGE, "Admin user/pass is not setup to invoke this endpoint");
			linotpResponse.setResponseCode(500);
			return linotpResponse;
		}

		// https://YOUR_LINOTP_SERVER/admin/reset?user=USERNAME
		final String hostname = socialData.getProperty("linotp_hostname"); 
		final String realm = socialData.getProperty("linotp_realm");

		String cleanHostname = hostname;
		if(cleanHostname.startsWith("https://")) {
			cleanHostname = cleanHostname.substring("https://".length());
		} else if(cleanHostname.startsWith("http://")) {
			cleanHostname = cleanHostname.substring("http://".length());
		}
		
		String controller = "admin";
		String action = "reset";
		String requestURL = hostname + "/" + controller + "/" + action;

		if( username == null || username.isEmpty() ) {
			returnMap.put(Constants.ERROR_MESSAGE, "The user name cannot be null or empty.");
			linotpResponse.setResponseCode(401);
			return linotpResponse;
		}

		SecureRandom random = new SecureRandom();
		byte bytes[] = new byte[32];
		random.nextBytes(bytes);
		Encoder encoder = Base64.getUrlEncoder().withoutPadding();
		String token = encoder.encodeToString(bytes);
		
		// LinOTP does not check user authorization when accessing the administrative API
		// but relies on the web server running LinOTP (as a WSGI app) to do this.
		// context is passed into the httpclient.execute() method
		HttpClientContext context = HttpClientContext.create();
		CredentialsProvider credsProvider = new BasicCredentialsProvider();
		credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(adminUser, adminPass));
		AuthCache authCache = new BasicAuthCache();
		authCache.put(new HttpHost(cleanHostname), new DigestScheme());
		context.setAuthCache(authCache);
		context.setCredentialsProvider(credsProvider);
		
		// must send the token in the cookie as well as the session key in the body
		CookieStore cstore = new BasicCookieStore();
		BasicClientCookie cookie = new BasicClientCookie("admin_session", token);
		cookie.setDomain(cleanHostname);
	    cookie.setAttribute(ClientCookie.DOMAIN_ATTR, "true");
		cstore.addCookie(cookie);
		context.setCookieStore(cstore);
		
		// Create HTTP request via ssl port (https) and pass post parameters
		CloseableHttpClient httpclient = HttpClients
				.custom()
				.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
				.build();
		try {
			HttpPost httpPost = new HttpPost(requestURL);

			List  nvps = new ArrayList ();
			nvps.add(new BasicNameValuePair("user", username));
			nvps.add(new BasicNameValuePair("realm", realm));
			nvps.add(new BasicNameValuePair("session", token));
			httpPost.setEntity(new UrlEncodedFormEntity(nvps));
			CloseableHttpResponse postResponse = httpclient.execute(httpPost, context);
			HttpEntity entity = null;
			try {
				entity = postResponse.getEntity();
				String s_response = EntityUtils.toString(entity);
				JsonObject j_response = JsonParser.parseString(s_response).getAsJsonObject();
				//parse json response for result value
				JsonObject j_result = j_response.getAsJsonObject("result");
				int validReset = 0;
				if(j_result.has("value")) {
					validReset = j_result.get("value").getAsInt();
				}
				if (validReset == 1) {
					returnMap.put("success", "true");
					returnMap.put("message", "Successful reset");
					linotpResponse.setResponseCode(200);
					return linotpResponse;
				} else {
					JsonObject detailJson = j_response.getAsJsonObject("detail");
					if(detailJson != null) {
						String errorMessage = detailJson.get("message").getAsString();
						if(errorMessage != null) {
							returnMap.put(Constants.ERROR_MESSAGE, "Unsuccessful reset - " + errorMessage);
							linotpResponse.setResponseCode(500);
							return linotpResponse;
						}
					}
					
					JsonObject resultJson = j_response.getAsJsonObject("result");
					if(resultJson != null) {
						JsonObject errorJson = resultJson.getAsJsonObject("error");
						if(errorJson != null) {
							String errorMessage = errorJson.get("message").getAsString();
							if(errorMessage != null) {
								returnMap.put(Constants.ERROR_MESSAGE, "Unsuccessful reset - " + errorMessage);
								linotpResponse.setResponseCode(500);
								return linotpResponse;
							}
						}
					}
					
					// could not parse a specific message - return json response for debugging
					returnMap.put(Constants.ERROR_MESSAGE, "Unsuccessful reset - unable to parse error details. Full error: " + j_response.toString());
					linotpResponse.setResponseCode(500);
					return linotpResponse;
				}
			} finally {
				// consume will release the entity
				if(entity != null) {
					EntityUtils.consume(entity);
				}
				if(postResponse != null) {
					postResponse.close();
				}
			}
		} finally {
			if(httpclient != null) {
				httpclient.close();
			}
		}
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy