Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for Pre-Configured HTTP Client and Basic Auth Changes #31

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 0 additions & 5 deletions .gitignore

This file was deleted.

58 changes: 52 additions & 6 deletions src/main/java/com/rallydev/rest/RallyRestApi.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package com.rallydev.rest;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;

import com.google.gson.JsonArray;
import com.rallydev.rest.client.ApiKeyClient;
import com.rallydev.rest.client.BasicAuthClient;
import com.rallydev.rest.client.HttpClient;
import com.rallydev.rest.request.*;
import com.rallydev.rest.response.*;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import com.rallydev.rest.request.BulkUserPermissionRequest;
import com.rallydev.rest.request.CollectionUpdateRequest;
import com.rallydev.rest.request.CreateRequest;
import com.rallydev.rest.request.DeleteRequest;
import com.rallydev.rest.request.GetRequest;
import com.rallydev.rest.request.QueryRequest;
import com.rallydev.rest.request.UpdateRequest;
import com.rallydev.rest.response.CollectionUpdateResponse;
import com.rallydev.rest.response.CreateResponse;
import com.rallydev.rest.response.DeleteResponse;
import com.rallydev.rest.response.GetResponse;
import com.rallydev.rest.response.QueryResponse;
import com.rallydev.rest.response.UpdateResponse;

/**
* <p>The main interface to the Rest API.</p>
Expand All @@ -30,6 +41,19 @@ public class RallyRestApi implements Closeable {
public RallyRestApi(URI server, String userName, String password) {
this(new BasicAuthClient(server, userName, password));
}

/**
* Creates a new instance for the specified server using the specified credentials.
*
* @param server The server to connect to, e.g. {@code new URI("https://rally1.rallydev.com")}
* @param userName The username to be used for authentication.
* @param password The password to be used for authentication.
* @param httpClient The pre-configured httpClient.
* @deprecated Use the api key constructor instead.
*/
public RallyRestApi(URI server, String userName, String password, org.apache.http.client.HttpClient httpClient) {
this(new BasicAuthClient(server, userName, password, httpClient));
}

/**
* Creates a new instance for the specified server using the specified API Key.
Expand All @@ -41,6 +65,17 @@ public RallyRestApi(URI server, String apiKey) {
this(new ApiKeyClient(server, apiKey));
}

/**
* Creates a new instance for the specified server using the specified API Key and a pre-configured httpClient.
*
* @param server The server to connect to, e.g. {@code new URI("https://rally1.rallydev.com")}
* @param apiKey The API Key to be used for authentication.
* @param httpClient The pre-configured httpClient.
*/
public RallyRestApi(URI server, String apiKey, org.apache.http.client.HttpClient httpClient) {
this(new ApiKeyClient(server, apiKey, httpClient));
}

protected RallyRestApi(HttpClient httpClient) {
this.client = httpClient;
}
Expand Down Expand Up @@ -199,6 +234,17 @@ public GetResponse get(GetRequest request) throws IOException {
return new GetResponse(client.doGet(request.toUrl()));
}

/**
* Bulk update a given user's project permissions.
*
* @param request request the {@link BulkUserPermissionRequest} specifying the object to be retrieved.
* @return the resulting {@link CollectionUpdateResponse}
* @throws IOException if an error occurs during the retrieval.
*/
public CollectionUpdateResponse bulkUpdate(BulkUserPermissionRequest request) throws IOException {
return new CollectionUpdateResponse(client.doPost(request.toUrl(), ""));
}

/**
* Release all resources associated with this instance.
*
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/com/rallydev/rest/client/ApiKeyClient.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.rallydev.rest.client;

import org.apache.http.client.methods.HttpRequestBase;

import java.io.IOException;
import java.net.URI;

import org.apache.http.client.methods.HttpRequestBase;

/**
* A HttpClient which authenticates using an API Key.
*/
Expand All @@ -23,6 +23,17 @@ public ApiKeyClient(URI server, String apiKey) {
this.apiKey = apiKey;
}

/**
* Construct a new client with a pre-configured HttpClient.
*
* @param server the server to connect to
* @param apiKey the key to be used for authentication
*/
public ApiKeyClient(URI server, String apiKey, org.apache.http.client.HttpClient httpClient) {
super(server, httpClient);
this.apiKey = apiKey;
}

/**
* Execute a request against the WSAPI
*
Expand Down
54 changes: 39 additions & 15 deletions src/main/java/com/rallydev/rest/client/BasicAuthClient.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.rallydev.rest.client;

import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.rallydev.rest.response.GetResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.auth.Credentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.auth.BasicScheme;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.rallydev.rest.response.GetResponse;

/**
* A HttpClient which authenticates using basic authentication (username/password).
Expand All @@ -24,6 +26,8 @@ public class BasicAuthClient extends HttpClient {
protected String securityToken;
protected Credentials credentials;

private Object privateLock = new Object();

/**
* Construct a new client.
* @param server the server to connect to
Expand All @@ -34,17 +38,36 @@ public BasicAuthClient(URI server, String userName, String password) {
super(server);
credentials = setClientCredentials(server, userName, password);
}

/**
* Construct a new client with a pre-configured HttpClient.
*
* @param server the server to connect to
* @param userName the username to be used for authentication
* @param password the password to be used for authentication
*/
public BasicAuthClient(URI server, String userName, String password, org.apache.http.client.HttpClient client) {
super(server, client);
credentials = setClientCredentials(server, userName, password);
}

/**
* Execute a request against the WSAPI
* Execute a request against the WSAPI.
*
* Always attaches the Basic Authentication header to avoid HTTP Spec handling.
*
* Traditionally, all requests are attempted without authorization,
* however we know that all resources are protected, so we can force pre-authentication.
*
* @param request the request to be executed
* @return the JSON encoded string response
* @throws java.io.IOException if a non-200 response code is returned or if some other
* problem occurs while executing the request
* problem occurs while executing the request
*/
@Override
protected String doRequest(HttpRequestBase request) throws IOException {
request.addHeader(BasicScheme.authenticate(credentials, "utf-8", false));

if(!request.getMethod().equals(HttpGet.METHOD_NAME) &&
!this.getWsapiVersion().matches("^1[.]\\d+")) {
try {
Expand All @@ -70,13 +93,14 @@ protected String doRequest(HttpRequestBase request) throws IOException {
protected void attachSecurityInfo(HttpRequestBase request) throws IOException, URISyntaxException {
if (!SECURITY_ENDPOINT_DOES_NOT_EXIST.equals(securityToken)) {
try {
if (securityToken == null) {
HttpGet httpGet = new HttpGet(getWsapiUrl() + SECURITY_TOKEN_URL);
httpGet.addHeader(BasicScheme.authenticate(credentials, "utf-8", false));
GetResponse getResponse = new GetResponse(doRequest(httpGet));
JsonObject operationResult = getResponse.getObject();
JsonPrimitive securityTokenPrimitive = operationResult.getAsJsonPrimitive(SECURITY_TOKEN_KEY);
securityToken = securityTokenPrimitive.getAsString();
synchronized (privateLock) {
if (securityToken == null) {
HttpGet httpGet = new HttpGet(getWsapiUrl() + SECURITY_TOKEN_URL);
GetResponse getResponse = new GetResponse(doRequest(httpGet));
JsonObject operationResult = getResponse.getObject();
JsonPrimitive securityTokenPrimitive = operationResult.getAsJsonPrimitive(SECURITY_TOKEN_KEY);
securityToken = securityTokenPrimitive.getAsString();
}
}
request.setURI(new URIBuilder(request.getURI()).addParameter(SECURITY_TOKEN_PARAM_KEY, securityToken).build());
} catch (IOException e) {
Expand Down
31 changes: 23 additions & 8 deletions src/main/java/com/rallydev/rest/client/HttpClient.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package com.rallydev.rest.client;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.*;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DecompressingHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

/**
* A HttpClient implementation providing connectivity to Rally. This class does not
* provide any authentication on its own but instead relies on a concrete subclass to do so.
Expand All @@ -28,7 +32,7 @@ public class HttpClient extends DefaultHttpClient

protected URI server;
protected String wsapiVersion = "v2.0";
protected DecompressingHttpClient client;
protected org.apache.http.client.HttpClient client;

private enum Header {
Library,
Expand All @@ -51,6 +55,17 @@ protected HttpClient(URI server) {
client = new DecompressingHttpClient(this);
}

/**
* Allow the user to specify a compliant HttpClient
*
* @param server The URI of the remote server
* @param client A pre-configured HttpClient implementation
*/
public HttpClient(URI server, org.apache.http.client.HttpClient client) {
this.server = server;
this.client = client;
}

/**
* Set the unauthenticated proxy server to use. By default no proxy is configured.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.rallydev.rest.request;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

/**
* Represents a WSAPI request to bulk provision a user.
*/
public class BulkUserPermissionRequest extends Request {

private static final String PROJECTPERMISSION_BULKUPDATE = "/projectpermission/bulkupdate";

private String userOID;
private Collection<String> excludedRootProjectOIDs;
private String rootProjectOID;
private String permission;
private boolean forceDowngradePermissions;

/**
*
* @param userOID
* The OID (ObjectID) of the User who will be granted new project permissions
* @param excludedRootProjectOIDs
* The OIDs of any child Project (or a child of a child, or any ancestor to any level) under the root project which are to be excluded from the
* permission change operation.
* @param rootProjectOID
* The OID of the root of the Project tree of which to change the permissions for the given user. The user's Project permission for all Projects
* rooted at this one will be changed, unless (see below for further explanation)
* the Project is on the exclusions list, or
* the operation would result in a downgrade but the force downgrade parameters was not set to tree.
* @param permission
* The permission to grant. Must be one of No Access, Viewer, Editor, or Project Admin.
* @param forceDowngradePermissions
* If you intend to downgrade any existing project permissions, set this to true.
*/
public BulkUserPermissionRequest(String userOID, Collection<String> excludedRootProjectOIDs, String rootProjectOID, String permission,
boolean forceDowngradePermissions) {
super();
this.userOID = userOID;
this.excludedRootProjectOIDs = excludedRootProjectOIDs;
this.rootProjectOID = rootProjectOID;
this.permission = permission;
this.forceDowngradePermissions = forceDowngradePermissions;
}

@Override
public String toUrl() {
List<NameValuePair> params = new ArrayList<NameValuePair>(getParams());
params.add(new BasicNameValuePair("userOID", userOID));
params.add(new BasicNameValuePair("rootProjectOID", rootProjectOID));
params.add(new BasicNameValuePair("permission", permission));
params.add(new BasicNameValuePair("forceDowngradePermissions", Boolean.toString(forceDowngradePermissions)));

if (excludedRootProjectOIDs != null && !excludedRootProjectOIDs.isEmpty()) {
params.add(new BasicNameValuePair("excludedRootProjectOIDs", String.join(",", excludedRootProjectOIDs)));
}

return String.format("%s?%s", PROJECTPERMISSION_BULKUPDATE, URLEncodedUtils.format(params, "utf-8"));
}

}
13 changes: 8 additions & 5 deletions src/main/java/com/rallydev/rest/response/Response.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package com.rallydev.rest.response;

import java.util.ArrayList;
import java.util.List;

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

import java.util.ArrayList;
import java.util.List;

/**
* Represents a WSAPI response.
*/
public abstract class Response {

private static final String ERRORS = "Errors";
private static final String WARNINGS = "Warnings";

protected JsonObject result;
protected String raw;

Expand Down Expand Up @@ -40,7 +43,7 @@ public boolean wasSuccessful() {
* @return the response errors
*/
public String[] getErrors() {
return parseArray("Errors");
return result.has(ERRORS) ? parseArray(ERRORS) : new String[0];
}

/**
Expand All @@ -49,7 +52,7 @@ public String[] getErrors() {
* @return the response warnings
*/
public String[] getWarnings() {
return parseArray("Warnings");
return result.has(WARNINGS) ? parseArray(WARNINGS) : new String[0];
}

/**
Expand Down
Loading