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

base.jee.api.cassandra.Authenticate Maven / Gradle / Ivy

/**
 * Creative commons Attribution-NonCommercial license.
 *
 * http://creativecommons.org/licenses/by-nc/2.5/au/deed.en_GB
 *
 * NO WARRANTY IS GIVEN OR IMPLIED, USE AT YOUR OWN RISK.
 */
package base.jee.api.cassandra;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;

import base.KeyValue;
import base.Query;
import base.jee.api.Settings;
import base.jee.api.model.Location;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.naming.NamingException;

import base.jee.Constants;
import base.json.Json;
import base.ldap.LdapHelper;
import base.security.PermissionException;
import base.security.User;
import base.text.Password;
import base.text.StringHelper;

import static base.jee.api.cassandra.util.AddPerson.addPerson;
import static base.jee.api.cassandra.util.CreateSession.createSession;
import static base.jee.api.cassandra.util.IpLocation.ipLocation;
import static base.jee.api.cassandra.util.IsThrottled.isThrottled;
import static base.jee.api.cassandra.util.Log.log;
import static base.jee.api.cassandra.util.MarkForThrottling.markForThrottling;
import static base.jee.api.cassandra.util.UpdatePersonFromLdap.updatePersonFromLdap;

/**
 */
public class Authenticate extends Query {

	private CassandraAPI api;
	private String currentToken;
	private String username;
	private String password;
	private String ip;

	public Authenticate() {
	}

	public Authenticate(CassandraAPI api, String currentToken, String username, String password, String ip) {

		if(api == null) {
			throw new IllegalArgumentException("Invalid parameter: api");
		}
		if(username == null) {
			throw new IllegalArgumentException("Invalid parameter: username");
		}
		if(password == null) {
			throw new IllegalArgumentException("Invalid parameter: password");
		}

		if(currentToken != null && currentToken.trim().length() > Constants.MAX_TOKEN_LENGTH) {
			throw new IllegalArgumentException("Invalid token.");
		}
		if(username.trim().length() > Constants.MAX_USERNAME_LENGTH) {
			throw new IllegalArgumentException("Invalid username. Usernames should not have more than " + Constants.MAX_USERNAME_LENGTH + " characters.");
		}
		if(password.trim().length() > Constants.MAX_PASSWORD_LENGTH) {
			throw new IllegalArgumentException("Invalid password. Passwords should not have more than " + Constants.MAX_PASSWORD_LENGTH + " characters.");
		}
		if(ip != null && ip.trim().length() > Constants.MAX_IP_ADDRESS_LENGTH) {
			throw new IllegalArgumentException("Invalid IP address. IP address should not have more than " + Constants.MAX_IP_ADDRESS_LENGTH + " characters.");
		}

		this.api = api;
		this.currentToken = currentToken;
		this.username = username.trim().toLowerCase();
		this.password = password.trim();
		this.ip = ip == null?null:ip.trim();
	}

	@Override
	public Query newWithParameters(Map parameters) throws IOException, PermissionException {
		return new Authenticate(
				(CassandraAPI)parameters.get("api"),
				(String)parameters.get("current_token"),
				(String)parameters.get("username"),
				(String)parameters.get("password"),
				((User)parameters.get("user")).getIp());
	}

	public List execute() throws IOException {
		List results = new LinkedList<>();
		String token = null;

		if(password.trim().length() < Constants.MIN_PASSWORD_LENGTH) {
			results.add(new KeyValue("error", "Invalid password."));
			return results;
		}

		try {
			Session s = api.getCassandraSession();
			Settings settings = api.getSettingsCache();

			if(isThrottled(s, ip, "ip")) {
				log(s, "SEVERE", User.userWithIp(ip), "Blocked authentication for throttled IP address: " + ip);
				results.add(new KeyValue("error", "Sign-in from this IP address is temporarily disabled due to repated sign in failures. Please try again shortly."));
				return results;
			}

			if(isThrottled(s, username, "auth")) {
				log(s, "SEVERE", User.userWithIp(ip), "Blocked authentication for throttled username: " + username);
				results.add(new KeyValue("error", "Sign-in using this account is temporarily disabled due to repated sign in failures. Please try again shortly."));
				return results;
			}

			UUID personUuid = null;
			String firstName = null;
			String lastName = null;

			PreparedStatement p = s.prepare("select uuid, password, first_name, last_name from person where " + (username.contains("@") ? "email" : "username") + "=?");
			for(Row r : s.execute(p.bind(username))) {
				personUuid = r.getUUID(0);
				firstName = r.getString(2);
				lastName = r.getString(3);

				// If password hash does not match password, we forget this search result,
				// this enables a lookup on LDAP (below).
				if(!Password.verifyPassword(r.getString(1), password)) {
					personUuid = null;
				}

			}

			// Internal password check failed. Do LDAP lookup if this feature is configured to be enabled.
			if(personUuid == null) {
				boolean ldapEnabled = false;
				String ldapUrl = null;
				String ldapUserDn = null;

				if(!username.contains("@")) {
					String e = settings.get("ldap.enabled", "false");
					ldapEnabled = e.equalsIgnoreCase("true") || e.equalsIgnoreCase("y") || e.equalsIgnoreCase("yes");
					ldapUrl = settings.get("ldap.url");
					ldapUserDn = settings.get("ldap.userdn");
				}

				if(ldapEnabled) {
					String user = ldapUserDn.replace("{u}", username);
					LdapHelper ldap;
					Map attributes;
					try {
						ldap = new LdapHelper(ldapUrl, user, password, true);
						attributes = ldap.getAttributes(user);
					} catch (NamingException | IOException e) {
						if(e.getCause() instanceof javax.naming.AuthenticationException) {
							log(s, "FINE", User.userWithIp(ip), "Invalid internal and/or ldap username or password. Username=" + username);
							results.add(new KeyValue("error", "Invalid username or password."));
							return results;
						} else {
							log(s, "SEVERE", User.userWithIp(ip), "Problem communicating with LDAP server. user=" + user + " - " + StringHelper.exceptionToString(e, "\n    "));
							results.add(new KeyValue("error", "Authentication temporarily unavailable."));
							return results;
						}
					}
					firstName = attributes.get("givenName");
					lastName = attributes.get("sn");
					personUuid = createOrUpdatePersonUsingLdapAttributes(s, username, attributes);
				}

				if(personUuid == null) {
					if(ip != null) {
						markForThrottling(s, ip, "ip");
					}
					markForThrottling(s, username, "auth");
					log(s, "FINE", User.userWithIp(ip), "Invalid username or password. Username: " + username);
					results.add(new KeyValue("error", "Invalid username or password."));
					return results;
				}
			}

			if(password.length() == 0) {
				log(s, "SEVERE", User.userWithUuidAndIp(personUuid, ip), "Blocked authentication for account with no password: Username: " + username);
				results.add(new KeyValue("error", "This user account has no password."));
				return results;
			}

			p = s.prepare("update person set last_auth=?,last_auth_ip=? where uuid=?");
			s.execute(p.bind(new Date().getTime(), ip, personUuid));

			token = createSession(s, personUuid, firstName, lastName, currentToken, ip, Long.parseLong(settings.get("session.expiry")));

			Location l = ipLocation(s, ip);
			log(s, "INFO", User.userWithUuidAndIp(personUuid, ip), "Authentication success. Location: " + (l == null?"unknown":l.toString()));

		} catch(NoSuchAlgorithmException e) {
			throw new IOException(e);
		}
		results.add(new KeyValue("token", token));
		return results;
	}

	private UUID createOrUpdatePersonUsingLdapAttributes(Session s, String username, Map attributes) throws IOException {
		UUID personUuid = null;

		String firstName = attributes.get("givenName");
		String lastName = attributes.get("sn");
		String email = attributes.get("mail");

		PreparedStatement p = s.prepare("select uuid, first_name, last_name from person where username=?");
		ResultSet rs = s.execute(p.bind(username));
		for(Row r : rs) {
			personUuid = r.getUUID(0);
			if((email != null && !email.equals(r.getString(4)))
				|| (firstName != null && !firstName.equals(r.getString(2)))
				|| (lastName != null && !lastName.equals(r.getString(3)))
					) {
				if(email.equals(r.getString(4))) {
					email = null;
				}
				if(firstName.equals(r.getString(2))) {
					firstName = null;
				}
				if(lastName.equals(r.getString(3))) {
					lastName = null;
				}
				updatePersonFromLdap(s, User.userWithUuidAndIp(personUuid, ip), firstName, lastName, email);
			}
			return personUuid;
		}
		personUuid = addPerson(s, firstName, lastName, email, username, null, null);
		log(s, "INFO", User.userWithUuidAndIp(personUuid, ip), "Auto created user account using LDAP date for username=" + username);

		return personUuid;
	}

	@Override
	public String getJsonParameters() {
		return "{" +
				"\"current_token\":\"" + Json.escape(currentToken) + "\"," +
				(ip == null?"":("\"ip\":\"" + Json.escape(ip) + "\",")) +
				"\"username\":\"" + Json.escape(username) + "\"" +
				"}";
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy