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

org.labkey.remoteapi.Connection Maven / Gradle / Ivy

Go to download

The client-side library for Java developers is a separate JAR from the LabKey Server code base. It can be used by any Java program, including another Java web application.

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright (c) 2008-2018 LabKey Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.labkey.remoteapi;

import org.apache.hc.client5.http.auth.AuthenticationException;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.Cookie;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.labkey.remoteapi.security.EnsureLoginCommand;
import org.labkey.remoteapi.security.ImpersonateUserCommand;
import org.labkey.remoteapi.security.LogoutCommand;
import org.labkey.remoteapi.security.StopImpersonatingCommand;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Represents connection information for a particular LabKey Server.
 * 

* Create an instance of this class for each server you wish to interact with. * If the commands you execute require a login, you must also configure * authentication via one of the supported methods: retrieving email address * and password from a .netrc/_netrc file, providing an api key, or providing * email address and password directly (which could be obtained from the * program's environment, such as via command-line parameters, environment * variables, a properties file, etc. See the individual constructors and * implementations of CredentialsProvider for more details. *

* After creating and initializing the Connection instance, pass it to the * Command.execute() method. *

Example: *

*

 *     Connection cn = new Connection("https://www.labkey.org");
 *     SelectRowsCommand cmd = new SelectRowsCommand("study", "Physical Exam");
 *     SelectRowsResponse response = cmd.execute(cn, "Home/Study/demo");
 *     for(Map<String, Object> row : response.getRows())
 *     {
 *         System.out.println(row.get("ParticipantId") + " weighs " + row.get("Weight"));
 *     }
 * 
*

* Example using Authentication *

*
 * 
 *     //get the user email and password from command-line arguments,
 *     //environment variables, a file, or some other mechanism.
 *     String user = getUser();
 *     String password = getPassword();
 *
 *     //create a new connection passing the user credentials
 *     Connection cn = new Connection("https://localhost:8080/labkey", user, password);
 *     SelectRowsCommand cmd = new SelectRowsCommand("lists", "People");
 *     SelectRowsResponse response = cmd.execute(cn, "Api Test");
 * 
 * 
*

* Note that this class is not thread-safe. Do not share instances of Connection * between threads. *

*/ public class Connection { public static final String X_LABKEY_CSRF = "X-LABKEY-CSRF"; public static final String JSESSIONID = "JSESSIONID"; private static final int DEFAULT_TIMEOUT = 60000; // 60 seconds private static final HttpClientConnectionManager CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(); private static HttpClientConnectionManager CONNECTION_MANAGER_SELF_SIGNED = null; // Will be initialized if needed private final URI _baseURI; private final CredentialsProvider _credentialsProvider; private final HttpClientContext _httpClientContext; private CloseableHttpClient _client; private boolean _acceptSelfSignedCerts; private int _timeout = DEFAULT_TIMEOUT; private String _proxyHost; private Integer _proxyPort; private String _csrf; private String _sessionId; private boolean _firstRequest = true; // The user email when impersonating a user private String _impersonateUser; private String _impersonatePath; private String _userAgent = "LabKey Java API"; /** * Constructs a new Connection object given a base URL and a credentials provider. *

* The baseURI parameter should include the protocol, domain name, port, * and LabKey web application context path (if configured). For example * in a typical localhost configuration, the base URL would be: *

* new URI("http://localhost:8080/labkey") *

* Note that https may also be used for the protocol. By default the * Connection is configured to deny self-signed SSL certificates. * If you want to accept self-signed certificates, use * setAcceptSelfSignedCerts(false) to enable this behavior. *

* @param baseURI Base URI for this Connection * @param credentialsProvider A credentials provider */ public Connection(URI baseURI, CredentialsProvider credentialsProvider) { if (baseURI.getHost() == null || baseURI.getScheme() == null) { throw new IllegalArgumentException("Invalid server URL: " + baseURI); } _baseURI = baseURI; _credentialsProvider = credentialsProvider; _httpClientContext = HttpClientContext.create(); _httpClientContext.setCookieStore(new BasicCookieStore()); setAcceptSelfSignedCerts(false); } /** * Constructs a new Connection object given a base URL and a credentials provider. *

* The baseUrl parameter should include the protocol, domain name, port, * and LabKey web application context path (if configured). For example * in a typical localhost configuration, the base URL would be: *

* http://localhost:8080/labkey *

* @param baseUrl The base URL * @param credentialsProvider A credentials provider * @see #Connection(URI, CredentialsProvider) */ public Connection(String baseUrl, CredentialsProvider credentialsProvider) { this(toURI(baseUrl), credentialsProvider); } /** * Constructs a new Connection object with a base URL that attempts authentication via .netrc/_netrc entry, if present. * If not present, connects as guest. * @param baseUrl The base URL * @throws IOException if there are problems reading the credentials * @see NetrcCredentialsProvider * @see #Connection(URI, CredentialsProvider) */ public Connection(String baseUrl) throws IOException { this(toURI(baseUrl), new NetrcCredentialsProvider(toURI(baseUrl))); } /** * Constructs a new Connection object for a base URL that attempts basic authentication. *

* This is equivalent to calling Connection(baseUrl, new BasicAuthCredentialsProvider(email, password)). * The email and password should correspond to a valid user email * and password on the target server. * @param baseUrl The base URL * @param email The user email address to pass for authentication * @param password The user password to send for authentication * @see BasicAuthCredentialsProvider#BasicAuthCredentialsProvider(String, String) * @see #Connection(URI, CredentialsProvider) */ public Connection(String baseUrl, String email, String password) { this(toURI(baseUrl), new BasicAuthCredentialsProvider(email, password)); } /** * Returns the base URI for this connection. * @return The target LabKey instance's base URI */ public URI getBaseURI() { return _baseURI; } /** * Returns the CloseableHttpClient object to use for this connection. * @return The CloseableHttpClient object to use. */ public CloseableHttpClient getHttpClient() { if (null == _client) { _client = clientBuilder().build(); } return _client; } /** * Create the HttpClientBuilder based on this Connection's configuration options. * @return The builder for an HttpClient */ private HttpClientBuilder clientBuilder() { final HttpClientConnectionManager connectionManager = _acceptSelfSignedCerts ? getSelfSignedConnectionManager() : CONNECTION_MANAGER; HttpClientBuilder builder = HttpClientBuilder.create() .setConnectionManager(connectionManager) .setDefaultRequestConfig(RequestConfig.custom().setResponseTimeout(getTimeout(), TimeUnit.MILLISECONDS).build()) .setDefaultCookieStore(_httpClientContext.getCookieStore()) .setDefaultCredentialsProvider(_httpClientContext.getCredentialsProvider()); if (_proxyHost != null && _proxyPort != null) builder.setProxy(new HttpHost(_proxyHost, _proxyPort)); if (null != _userAgent) builder.setUserAgent(_userAgent); return builder; } private static final Object SELF_SIGNED_LOCK = new Object(); private HttpClientConnectionManager getSelfSignedConnectionManager() { synchronized (SELF_SIGNED_LOCK) { if (null == CONNECTION_MANAGER_SELF_SIGNED) { try { SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build()); CONNECTION_MANAGER_SELF_SIGNED = PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(sslConnectionSocketFactory) .build(); } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { throw new RuntimeException(e); } } return CONNECTION_MANAGER_SELF_SIGNED; } } /** * If first request, prime the Connection by forcing authentication and populating CSRF and session info. * @param request HttpRequest that is about to be executed */ protected void beforeExecute(HttpRequest request) { if (_firstRequest) { // Make an initial request to ensure login (especially important when invoking @RequiresNoPermission actions), // get a JSESSIONID, and get a CSRF token try { _firstRequest = false; ensureAuthenticated(); } catch (Exception ignored) { } } if (null != _csrf) request.setHeader(X_LABKEY_CSRF, _csrf); if (null != _sessionId) request.setHeader(JSESSIONID, _sessionId); } protected void afterExecute() { // Always update our CSRF token as the session may be new since our last request for (Cookie c : _httpClientContext.getCookieStore().getCookies()) { if (X_LABKEY_CSRF.equals(c.getName())) _csrf = c.getValue(); if (JSESSIONID.equals(c.getName())) _sessionId = c.getValue(); } } /** * Ensures that the credentials have been used to authenticate the users and returns a client that can be used for other requests * * @throws IOException if there is an IO problem executing the command to ensure login * @throws CommandException if the server returned a non-success status code. */ public void ensureAuthenticated() throws IOException, CommandException { new EnsureLoginCommand().execute(this, "/home"); } /** * Invalidates the current HTTP session on the server, if any * * @throws IOException if there is an IO problem executing the command to ensure logout * @throws CommandException if the server returned a non-success status code. */ public void logout() throws IOException, CommandException { new LogoutCommand().execute(this, "/home"); } /** * For site-admins only, start impersonating a user. *

* Admins may impersonate other users to perform actions on their behalf. * Site admins may impersonate any user in any project. Project admins must * provide a projectPath and may only impersonate within the * project in which they have admin permission. * * @param email The user to impersonate * @see Connection#stopImpersonating() * @return this connection * @throws IOException Thrown if there was an IO problem. * @throws CommandException if the server returned a non-success status code. */ public Connection impersonate(/*@NotNull*/ String email) throws IOException, CommandException { return impersonate(email, null); } /** * For site-admins or project-admins only, start impersonating a user. *

* Admins may impersonate other users to perform actions on their behalf. * Site admins may impersonate any user in any project. Project admins must * provide a projectPath and may only impersonate within the * project in which they have admin permission. * * @param email The user to impersonate * @param projectPath The project path within which the user will be impersonated. * @see Connection#stopImpersonating() * @return this connection * @throws IOException Thrown if there was an IO problem. * @throws CommandException if the server returned a non-success status code. */ public Connection impersonate(/*@NotNull*/ String email, /*@Nullable*/ String projectPath) throws IOException, CommandException { Objects.requireNonNull(email, "email"); CommandResponse resp = new ImpersonateUserCommand(email).execute(this, projectPath); if (resp.getStatusCode() != 200) throw new CommandException("Failed to impersonate user"); _impersonateUser = email; _impersonatePath = projectPath; return this; } /** * Stop impersonating a user. * @return this connection * @throws IOException Thrown if there was an IO problem. * @throws CommandException if the server returned a non-success status code. */ public Connection stopImpersonating() throws IOException, CommandException { if (_impersonateUser != null) { CommandResponse resp = new StopImpersonatingCommand().execute(this, _impersonatePath); // on success, a 302 response is returned (this command disables redirects) if (resp.getStatusCode() != 302) throw new CommandException("Failed to stop impersonating"); _impersonateUser = null; _impersonatePath = null; } return this; } @Deprecated // Will be removed soon. Use stopImpersonating() instead. public Connection stopImpersonate() throws IOException, CommandException { return stopImpersonating(); } CloseableHttpResponse executeRequest(HttpUriRequest request, Integer timeout) throws IOException, AuthenticationException { // Delegate authentication setup to CredentialsProvider _credentialsProvider.configureRequest(getBaseURI(), request, _httpClientContext); CloseableHttpClient client = getHttpClient(); // Set the timeout on the request if it is different the client's default if (request instanceof HttpUriRequestBase r && timeout != null && timeout != getTimeout()) { RequestConfig base = r.getConfig(); if (base == null) base = RequestConfig.DEFAULT; r.setConfig(RequestConfig.copy(base).setResponseTimeout(timeout, TimeUnit.MILLISECONDS).build()); } beforeExecute(request); CloseableHttpResponse response = client.execute(request, _httpClientContext); afterExecute(); return response; } /** * Set a default timeout for Commands that have not established their own timeouts. Null resets the Connection to the * default timeout (60 seconds). 0 means the request should never timeout. * NOTE: Changing this setting will force the underlying http client to be recreated. * * @param timeout the length of the timeout waiting for the server response, in milliseconds * @return this connection */ public Connection setTimeout(Integer timeout) { _timeout = timeout == null ? DEFAULT_TIMEOUT : timeout; _client = null; return this; } /** * The timeout used for Commands that have not established their own timeouts. 0 means the request should never timeout. * @return the length of the timeout waiting for the server response, in milliseconds */ public int getTimeout() { return _timeout; } /** * Sets the proxy host and port for this Connection.
* NOTE: Changing this setting will force the underlying http client to be recreated. * @param host the proxy host * @param port the proxy port * @return this connection */ public Connection setProxy(String host, Integer port) { _proxyHost = host; _proxyPort = port; _client = null; return this; } /** * Returns true if the connection should accept a self-signed * SSL certificate when using HTTPS, false otherwise. Defaults * to true. * @return true or false */ public boolean isAcceptSelfSignedCerts() { return _acceptSelfSignedCerts; } /** * Sets the accept-self-signed certificates option. Set to false * to disable automatic acceptance of self-signed SSL certificates * when using HTTPS.
* NOTE: Changing this setting will force the underlying http client to be recreated. * * @param acceptSelfSignedCerts set to false to not accept self-signed certificates * @return this connection */ public Connection setAcceptSelfSignedCerts(boolean acceptSelfSignedCerts) { _acceptSelfSignedCerts = acceptSelfSignedCerts; _client = null; return this; } public String getUserAgent() { return _userAgent; } public void setUserAgent(String userAgent) { _userAgent = userAgent; } /** * @param name The cookie name * @param value The cookie value * @param domain The domain to which the cookie is visible * @param path The path to which the cookie is visible * @param expiry The cookie's expiration date * @param isSecure Whether the cookie requires a secure connection * @return this connection */ public Connection addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure) { BasicClientCookie cookie = new BasicClientCookie(name, value); cookie.setDomain(domain); cookie.setPath(path); cookie.setExpiryDate(expiry); cookie.setSecure(isSecure); _httpClientContext.getCookieStore().addCookie(cookie); // Don't use session info from different server if (combineHostPath(_baseURI.getHost(), _baseURI.getPath()).equals(combineHostPath(domain, path))) { if (X_LABKEY_CSRF.equals(name)) _csrf = value; if (JSESSIONID.equals(name)) _sessionId = value; } return this; } private String combineHostPath(String host, String path) { String hostPath = (host == null ? "" : host.trim()) + (path == null ? "" : path.trim()); return hostPath.replaceAll("//+", "/").replaceFirst("/$", ""); } /** * Utility method to construct a URI without modifying constructor signature */ private static URI toURI(String baseUrl) { try { return new URI(baseUrl); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid target server URL: " + baseUrl); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy