<commit_before>package us.kbase.auth;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.codehaus.jackson.map.ObjectMapper;

/**
 * A fairly simple Auth service client for KBase.
 * Usage:
 * 
 * AuthUser user = AuthService.login(user, password);
 * if (AuthService.validateToken(user.getToken())) {
 * 		// There's a valid token! Return the valid user, or just the token, and move along.
 * }
 * 
 * Thus, this provides code for a user to log in to KBase, retrieve a valid Auth token, and
 * optionally validate it.
 * 
 * All tokens seen by this class are cached by the
 * {@link us.kbase.auth.TokenCache} class.
 * 
 * @author wjriehl
 * @author <EMAIL>
 */
public class AuthService {
	private static URL AUTH_URL;
	
	
	private static String GLOBUS_USER_URL_STRING =
			"https://www.globusonline.org/service/graph/search?query=type%3A+user+";
	private static String AUTH_URL_STRING = "https://www.kbase.us/services/authorization/Sessions/Login";
	private static TokenCache tc = new TokenCache();
	private static StringCache uc = new StringCache();
	private static Pattern INVALID_USERNAME = Pattern.compile("[^a-zA-Z0-9_-]");

	static {
		try {
			AUTH_URL = new URL(AUTH_URL_STRING);
		}
		catch (MalformedURLException e) {
			throw new RuntimeException("Something that should never occur, has occurred. Look for nearby holes in reality.");
		}
	}
	
	/**
	 * Logs in a user and returns an AuthUser object, which is more or less a POJO containing basic user attributes,
	 * along with the generated AuthToken.
	 * 
	 * @param userName the username
	 * @param password the password
	 * @param expiry the desired expiration time for the token in seconds.
	 * @return an AuthUser that has been successfully logged in.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser login(String userName, String password, long expiry) throws AuthException,
																					   IOException {
		// This is the data that will be POSTed to the service.
		// By default (not sure if we *really* need to change it), it fetches all the fields.
		try {
			String dataStr = "user_id=" + URLEncoder.encode(userName, "UTF-8") + 
							 "&password=" + URLEncoder.encode(password, "UTF-8") + 
							 "&cookie=1&fields=user_id,name,email,groups,kbase_sessionid,token,verified,opt_in,system_admin";
			return fetchUser(dataStr, expiry);
		}
		catch (UnsupportedEncodingException e) {
			throw new RuntimeException("An unexpected URL encoding exception occurred: " + e.getLocalizedMessage());
		}
	}
	

	/**
	 * Logs in a user and returns an AuthUser object, which is more or less a POJO containing basic user attributes,
	 * along with the generated AuthToken.
	 * 
	 * @param userName the username
	 * @param password the password
	 * @return an AuthUser that has been successfully logged in.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser login(String userName, String password) throws AuthException,
																		  IOException {
		return login(userName, password, AuthToken.DEFAULT_EXPIRES);
	}

	/**
	 * Given an AuthToken object for a logged in user, this returns the AuthUser object representing that user's
	 * profile.
	 * 
	 * @param token the token
	 * @return an AuthUser associated with the given token.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser getUserFromToken(AuthToken token) throws AuthException,
																	IOException {
		String dataStr = "token=" + token.toString() +
				 "&fields=user_id,name,email,groups,kbase_sessionid,token,verified,opt_in,system_admin";

		return fetchUser(dataStr, token.getExpiryTime());
	}
	
	/**
	 * Checks whether strings are a valid user names.
	 * Checks the local cache first to avoid an http call.
	 * @param usernames the usernames
	 * @param token a valid token
	 * @return a mapping of username to validity.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 * @throws IllegalArgumentException if a username is invalid.
	 */
	public static Map<String, Boolean> isValidUserName(List<String> usernames,
			AuthToken token) throws IOException, AuthException {
		final List<String> badlist = new ArrayList<String>();
		final Map<String, Boolean> result = new HashMap<String, Boolean>();
		for (String user: usernames) {
			if (user == null) {
				continue;
			}
			if (uc.hasString(user)) {
				result.put(user, true);
			} else {
				badlist.add(user);
			}
		}
		Map<String, UserDetail> uds = fetchUserDetail(badlist, token);
		for (String user: uds.keySet()) {
			result.put(user, uds.get(user) != null);
		}
		return result;
	}
	
	/**
	 * Get information about users.
	 * @param usernames the user names of the user that is the subject of the request
	 * @param token a valid token.
	 * @return a mapping of username to user details.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 * @throws IllegalArgumentException if a username is invalid.
	 */
	public static Map<String, UserDetail> fetchUserDetail(List<String> usernames,
			AuthToken token) throws IOException, AuthException {
		final Map<String, UserDetail> result = new HashMap<String, UserDetail>();
		if (usernames.size() == 0) {
			return result;
		}
		for (String un: usernames) {
			if (un == null) {
				continue;
			}
			result.put(un, null);
			final Matcher m = INVALID_USERNAME.matcher(un);
			if (m.find()) {
				throw new IllegalArgumentException(<commit_msg>Add some todos as reminders for auth service updates<commit_after>package us.kbase.auth;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.codehaus.jackson.map.ObjectMapper;

/**
 * A fairly simple Auth service client for KBase.
 * Usage:
 * 
 * AuthUser user = AuthService.login(user, password);
 * if (AuthService.validateToken(user.getToken())) {
 * 		// There's a valid token! Return the valid user, or just the token, and move along.
 * }
 * 
 * Thus, this provides code for a user to log in to KBase, retrieve a valid Auth token, and
 * optionally validate it.
 * 
 * All tokens seen by this class are cached by the
 * {@link us.kbase.auth.TokenCache} class.
 * 
 * @author wjriehl
 * @author <EMAIL>
 */
public class AuthService {
	private static URL AUTH_URL;
	
	
	private static String GLOBUS_USER_URL_STRING =
			"https://www.globusonline.org/service/graph/search?query=type%3A+user+";
	private static String AUTH_URL_STRING = "https://www.kbase.us/services/authorization/Sessions/Login";
	private static TokenCache tc = new TokenCache();
	private static StringCache uc = new StringCache();
	private static Pattern INVALID_USERNAME = Pattern.compile("[^a-zA-Z0-9_-]");

	static {
		try {
			AUTH_URL = new URL(AUTH_URL_STRING);
		}
		catch (MalformedURLException e) {
			throw new RuntimeException("Something that should never occur, has occurred. Look for nearby holes in reality.");
		}
	}
	
	/**
	 * Logs in a user and returns an AuthUser object, which is more or less a POJO containing basic user attributes,
	 * along with the generated AuthToken.
	 * 
	 * @param userName the username
	 * @param password the password
	 * @param expiry the desired expiration time for the token in seconds.
	 * @return an AuthUser that has been successfully logged in.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser login(String userName, String password, long expiry) throws AuthException,
																					   IOException {
		// This is the data that will be POSTed to the service.
		// By default (not sure if we *really* need to change it), it fetches all the fields.
		try {
			String dataStr = "user_id=" + URLEncoder.encode(userName, "UTF-8") + 
							 "&password=" + URLEncoder.encode(password, "UTF-8") + 
							 "&cookie=1&fields=user_id,name,email,groups,kbase_sessionid,token,verified,opt_in,system_admin";
			return fetchUser(dataStr, expiry);
		}
		catch (UnsupportedEncodingException e) {
			throw new RuntimeException("An unexpected URL encoding exception occurred: " + e.getLocalizedMessage());
		}
	}
	

	/**
	 * Logs in a user and returns an AuthUser object, which is more or less a POJO containing basic user attributes,
	 * along with the generated AuthToken.
	 * 
	 * @param userName the username
	 * @param password the password
	 * @return an AuthUser that has been successfully logged in.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser login(String userName, String password) throws AuthException,
																		  IOException {
		return login(userName, password, AuthToken.DEFAULT_EXPIRES);
	}

	/**
	 * Given an AuthToken object for a logged in user, this returns the AuthUser object representing that user's
	 * profile.
	 * 
	 * @param token the token
	 * @return an AuthUser associated with the given token.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 */
	public static AuthUser getUserFromToken(AuthToken token) throws AuthException,
																	IOException {
		String dataStr = "token=" + token.toString() +
				 "&fields=user_id,name,email,groups,kbase_sessionid,token,verified,opt_in,system_admin";

		return fetchUser(dataStr, token.getExpiryTime());
	}
	
	/**
	 * Checks whether strings are a valid user names.
	 * Checks the local cache first to avoid an http call.
	 * @param usernames the usernames
	 * @param token a valid token
	 * @return a mapping of username to validity.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 * @throws IllegalArgumentException if a username is invalid.
	 */
	public static Map<String, Boolean> isValidUserName(List<String> usernames,
			AuthToken token) throws IOException, AuthException {
		//TODO when auth service supports, just query auth service for this
		final List<String> badlist = new ArrayList<String>();
		final Map<String, Boolean> result = new HashMap<String, Boolean>();
		for (String user: usernames) {
			if (user == null) {
				continue;
			}
			if (uc.hasString(user)) {
				result.put(user, true);
			} else {
				badlist.add(user);
			}
		}
		Map<String, UserDetail> uds = fetchUserDetail(badlist, token);
		for (String user: uds.keySet()) {
			result.put(user, uds.get(user) != null);
		}
		return result;
	}
	
	/**
	 * Get information about users.
	 * @param usernames the user names of the user that is the subject of the request
	 * @param token a valid token.
	 * @return a mapping of username to user details.
	 * @throws AuthException if the credentials are invalid
	 * @throws IOException if there is a problem communicating with the server.
	 * @throws IllegalArgumentException if a username is invalid.
	 */
	public static Map<String, UserDetail> fetchUserDetail(List<String> usernames,
			AuthToken token) throws IOException, AuthException {
		//TODO when auth service supports, just query auth service for this
		final Map<String, UserDetail> result = new HashMap<String, UserDetail>();
		if (usernames.size() == 0) {
			return result;
		}
		for (String un: usernames) {
			if (un == null) {
				continue;
			}
			result.put(un, null);
			final Matcher m = INVALID_USERNAME.matcher(un);
			if (m.find()) {
				throw new IllegalArgumentException(
{'fraction_non_alphanumeric': 0.09286747354799421, 'fraction_numerical': 0.00197914287889168, 'mean_word_length': 3.95212966453072, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 4, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5757780', 'n_tokens_mistral': 4112, 'n_tokens_neox': 3684, 'n_words': 1339}